/* ==========================================================================
   Luxury Interiors LLC — shared stylesheet
   Modern monochrome system (Option E): pure black/white/gray by default,
   with a single gold accent restricted to exactly three touchpoints —
   primary buttons, kicker labels, and link/hover states. Everything else
   (headings, body copy, dividers, stat numbers, badges, icons) stays
   grayscale on purpose. Fraunces display serif + Manrope for UI/body.
   Buttons use a soft-rounded corner (see --radius). Mobile-first. */

:root {
  --color-black: #0e0e0e;
  --color-ink: #16171a;
  --color-charcoal: #1c1c1c;

  /* Gold — restricted to buttons, kicker labels, and link/hover states.
     Three shades on purpose: a mid-tone for button fills (tested against
     black text), a darker shade for labels/links on light backgrounds, and
     a lighter shade for labels/links on dark backgrounds — each chosen to
     clear 4.5:1 text contrast against its background. Do not use
     --color-gold-on-light/dark for anything decorative (rules, borders,
     icons, stat numbers) — those stay neutral. See README.md. */
  --color-gold: #cc9a3c;
  --color-gold-hover: #d9ad55;
  --color-gold-on-light: #8a6a1a;
  --color-gold-on-dark: #e0bb5f;

  --color-stone: #f1f1f0;
  --color-stone-deep: #e9e9e7;
  --color-white: #ffffff;
  --color-ink-soft: #45464a;
  --color-mute: #79797a;
  --hairline: rgba(17, 17, 17, 0.12);
  --hairline-dark: rgba(255, 255, 255, 0.14);

  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --font-body: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  --max-width: 1240px;
  --text-width: 60ch;
  --radius: 14px; /* soft-rounded, iOS-button proportions — buttons only */
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
}

*:focus-visible {
  outline: 2px solid var(--color-gold);
  outline-offset: 3px;
}

section[id] { scroll-margin-top: 96px; }

* { box-sizing: border-box; }
html {
  scroll-behavior: smooth;
  /* Without this, mobile Safari/Chrome silently boost font-size on text
     sitting in narrow columns ("text autosizing") — invisible on desktop and
     in devtools mobile emulation, but on a real phone it was inflating the
     Call Now button's text well past its box, which is what made the button
     look "giant" with the copy stretching edge to edge no matter how much
     padding was added. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

.skip-link {
  position: absolute;
  left: -999px;
  top: auto;
  background: var(--color-gold);
  color: var(--color-black);
  padding: 0.75rem 1.25rem;
  z-index: 100;
  font-weight: 700;
  text-decoration: none;
}
.skip-link:focus { left: 0; top: 0; }

body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--color-ink-soft);
  background: var(--color-white);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  font-size: 16px;
}

h1, h2, h3 {
  font-family: var(--font-display);
  line-height: 1.08;
  letter-spacing: -0.01em;
  margin: 0 0 0.5em;
  color: var(--color-black);
  font-weight: 500;
}
h4 {
  font-family: var(--font-body);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--color-black);
  margin: 0 0 0.5em;
}

h1 { font-size: clamp(2.25rem, 5vw + 1rem, 3.75rem); }
h2 { font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem); }
h3 { font-size: 1.3rem; font-weight: 500; }
p { margin: 0 0 1.25em; max-width: var(--text-width); }
a { color: inherit; }
img, svg { max-width: 100%; display: block; }
ul { margin: 0; }
.icon { width: 1.4em; height: 1.4em; stroke: currentColor; fill: none; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }

.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 1.5rem;
}

.section { padding: clamp(3.5rem, 7vw, 6.5rem) 0; }
.section--tight { padding: clamp(2.5rem, 5vw, 4rem) 0; }

.section--dark { background: var(--color-black); color: rgba(255,255,255,0.72); }
.section--dark h1, .section--dark h2, .section--dark h3 { color: var(--color-white); }
.section--dark p { color: rgba(255,255,255,0.6); }
.section--dark .eyebrow { color: var(--color-gold-on-dark); }

.section--stone { background: var(--color-stone); }

.eyebrow {
  display: block;
  font-family: var(--font-body);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-gold-on-light);
  font-weight: 700;
  margin-bottom: 1rem;
}

.lede { font-size: 1.1rem; color: var(--color-mute); }
.section--dark .lede { color: rgba(255,255,255,0.6); }

/* ---------- Scroll reveal ----------
   Progressive enhancement, on purpose: .reveal elements are fully visible by
   default. Only script.js's ".reveal-armed" class (added only after JS has
   confirmed it's running) switches them to a hidden pre-state for the
   IntersectionObserver to animate in. If JS fails to load, errors out, is
   blocked, or the visitor has reduced-motion/no IntersectionObserver
   support, content simply stays visible with no animation — it can never
   get stuck invisible. (An earlier version set opacity:0 unconditionally in
   CSS with only a JS-added visible state; if the script ever failed, most of
   the page's content was permanently blank. Do not revert to that pattern.)

   Do NOT add .reveal to a container that script.js populates/resizes
   dynamically (the gallery grid, testimonials, gallery filters, etc.).
   Confirmed bug: on a reload where the browser restores scroll position to
   mid-page (common on plain F5, not just back/forward nav), the
   IntersectionObserver's one-shot check can land before that container has
   its final populated size, conclude it isn't in view yet, and then never
   re-fire since scroll position doesn't change again — leaving it stuck at
   opacity:0 indefinitely. Static text (headings/paragraphs) is fine; those
   never change size after paint. */
.reveal {
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
.reveal.reveal-armed { opacity: 0; transform: translateY(18px); }
.reveal.reveal-armed.is-visible { opacity: 1; transform: none; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  min-height: 48px;
  padding: 0 2.5rem;
  border-radius: var(--radius);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.82rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background 0.25s var(--ease), color 0.25s var(--ease), border-color 0.25s var(--ease), transform 0.15s var(--ease);
  white-space: nowrap;
}
.btn:active { transform: scale(0.98); }
.btn--block {
  width: 100%;
  /* Keeps the original tighter padding on purpose: this variant carries the
     longer "Call Now — 615-589-9540" string in the full-width mobile sticky
     bar. The wider padding above (added for the compact pill buttons, e.g.
     the nav CTA) would eat into the already-tight available width for that
     string on narrow phones (~360px) and risk overflow, since .btn uses
     white-space:nowrap. */
  padding: 0 1.25rem;
}

.btn--gold {
  background: var(--color-gold);
  border-color: var(--color-gold);
  color: var(--color-black);
}
.btn--gold:hover { background: var(--color-gold-hover); border-color: var(--color-gold-hover); }

.btn--outline {
  background: transparent;
  border-color: var(--color-ink);
  color: var(--color-ink);
}
.btn--outline:hover { background: var(--color-ink); color: var(--color-white); }

.btn--outline-light {
  background: transparent;
  border-color: rgba(255,255,255,0.4);
  color: var(--color-white);
}
.btn--outline-light:hover { background: var(--color-white); color: var(--color-black); border-color: var(--color-white); }

.btn-row { display: flex; flex-wrap: wrap; gap: 0.9rem; }

/* Understated text link with animated underline, used instead of boxed
   "learn more" buttons for a lighter, editorial feel. */
.text-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--color-black);
  text-decoration: none;
  font-weight: 700;
  font-size: 0.88rem;
  padding-bottom: 2px;
  background-image: linear-gradient(var(--color-gold-on-light), var(--color-gold-on-light));
  background-size: 0% 1px;
  background-repeat: no-repeat;
  background-position: left bottom;
  transition: background-size 0.3s var(--ease);
}
.text-link:hover { background-size: 100% 1px; }
.section--dark .text-link { color: var(--color-white); background-image: linear-gradient(var(--color-gold-on-dark), var(--color-gold-on-dark)); }

/* ---------- Header / Nav ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  /* Solid background on purpose — NOT backdrop-filter/transform/filter/contain.
     Any of those on this element would create a new containing block for the
     position:fixed .site-nav mobile panel nested inside it, which silently
     collapses that panel's height to ~0 (top/bottom insets resolve against
     the header's own 76px box instead of the viewport). Confirmed bug from a
     previous pass — do not reintroduce backdrop-filter here without moving
     .site-nav out of .site-header first. */
  background: var(--color-black);
  border-bottom: 1px solid var(--hairline-dark);
}

.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 76px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  text-decoration: none;
  color: var(--color-white);
}
.brand img { width: 36px; height: 36px; }
.brand span {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.82rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}

.nav-toggle {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0;
}
.nav-toggle span {
  display: block;
  width: 24px;
  height: 1px;
  background: var(--color-white);
  transition: transform 0.2s var(--ease), opacity 0.2s var(--ease);
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* Dimmed scrim behind the mobile/narrow-window nav drawer — click to close,
   and it's what makes the drawer read as a deliberate panel instead of a
   jarring full-viewport takeover on a resized (but still >900px-narrow)
   desktop window. */
.nav-scrim {
  display: none;
  position: fixed;
  inset: 0;
  top: 76px;
  background: rgba(0,0,0,0.5);
  z-index: 49;
  border: none;
  padding: 0;
  cursor: default;
}
.nav-scrim.is-open { display: block; }

.site-nav {
  position: fixed;
  top: 76px;
  left: 0;
  width: 100%;
  max-width: 420px; /* a bounded drawer, not a full-bleed takeover — the mobile
    portrait case (≈390px) is effectively unchanged, but a resized desktop
    window no longer stretches this into a mostly-empty full-width overlay */
  bottom: 0;
  background: var(--color-black);
  padding: 2.5rem 1.5rem;
  display: none;
  flex-direction: column;
  gap: 0;
  overflow-y: auto;
  box-shadow: 8px 0 32px rgba(0,0,0,0.35);
}
.site-nav.is-open { display: flex; }

.site-nav a {
  color: var(--color-white);
  text-decoration: none;
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 400;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--hairline-dark);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease), color 0.2s ease;
}
.site-nav.is-open a {
  opacity: 1;
  transform: none;
}
.site-nav.is-open a:nth-child(1) { transition-delay: 0.05s; }
.site-nav.is-open a:nth-child(2) { transition-delay: 0.1s; }
.site-nav.is-open a:nth-child(3) { transition-delay: 0.15s; }
.site-nav.is-open a:nth-child(4) { transition-delay: 0.2s; }
.site-nav a:hover { color: var(--color-gold-on-dark); }
.site-nav a[aria-current="page"] { color: var(--color-gold-on-dark); } /* which page you're on — same treatment as :hover, just permanent */

.site-nav .nav-contact {
  margin-top: 2rem;
  display: flex;
  flex-direction: column;
  align-items: center; /* centers the button under the stacked nav links */
}
/* Found the actual root cause of every "giant Call Now button, text
   touching the edges" report: .site-nav a (above, for the plain Home/
   Services/About/Contact links) is a MORE specific selector than .btn or
   .btn--gold alone (.site-nav a = 0-1-1 vs .btn = 0-1-0), so it was
   silently winning the cascade on this button too — since the button is
   technically also an <a> inside .site-nav. It was overriding font-size to
   2rem (vs the button's real 0.82rem), font-family to the serif display
   font, font-weight to 400, and padding to "0.6rem 0" — zero horizontal
   padding. That's the exact combination that produces "huge serif text
   with no side padding": every previous fix (bigger padding, width:100%,
   text-size-adjust) targeted symptoms without touching this. Reclaiming
   the real button styles here at higher specificity (.site-nav .nav-contact
   .btn = 0-3-0) so .site-nav a can never leak onto it again. */
.site-nav .nav-contact .btn {
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-black); /* .site-nav a also forces white text — wrong
    on a gold-fill button */
  padding: 0 2.5rem;
  border-bottom: none;
}

@media (min-width: 900px) {
  .nav-toggle { display: none; }
  .site-nav {
    position: static;
    width: auto;
    max-width: none; /* undo the mobile-drawer width cap — without this the
      whole desktop nav row (links + phone + button) gets squeezed into the
      420px drawer width and wraps. Confirmed regression from that fix. */
    overflow: visible; /* undo the mobile drawer's overflow-y:auto — setting
      only one axis forces the other to compute as 'auto' too (a CSS
      overflow quirk), turning the whole desktop nav row into an accidental
      scroll container. A sub-pixel rendering shift on :hover (e.g. the
      button's background-color transition repainting) was just enough to
      tip it into showing a real scrollbar, which ate ~15px of width and
      reflowed the phone number/button next to it. Confirmed via computed
      styles — do not drop this without re-checking that regression. */
    display: flex;
    flex-direction: row;
    align-items: center;
    background: transparent;
    padding: 0;
    gap: 2.5rem;
  }
  .site-nav a {
    font-size: 0.8rem;
    font-family: var(--font-body);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    border-bottom: none;
    padding: 0;
    opacity: 1;
    transform: none;
    position: relative;
  }
  .site-nav a::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: -6px;
    width: 0;
    height: 1px;
    background: var(--color-gold-on-dark);
    transition: width 0.25s var(--ease);
  }
  .site-nav a:hover::after, .site-nav a[aria-current="page"]::after { width: 100%; }
  .site-nav .nav-contact {
    margin: 0;
    padding-left: 1.5rem;
    border-left: 1px solid var(--hairline-dark); /* separates the Call Now
      button from the plain nav links, so it still reads as a distinct CTA
      instead of blending into the link row */
  }
}

/* ---------- Hero (home) ----------
   A flat black rectangle at ~86vh with left-aligned text was mostly empty
   dead space — the single biggest "looks unfinished" spot on the site. Fixed
   by giving the hero the same text+placeholder-photo split every other
   section on the site already uses, instead of being the one place that
   broke that pattern. Height is now content-driven (generous padding, no
   forced min-height), so it doesn't manufacture empty space to fill. */
.hero {
  background: var(--color-black);
  color: var(--color-white);
  /* Smaller on mobile on purpose — the full 6.5rem top padding (set below,
     desktop only) left a big dead gap of plain black between the header and
     the eyebrow text once the layout stacks to one column. */
  padding: 3rem 0 2.5rem;
}
@media (min-width: 860px) {
  .hero { padding: 6.5rem 0 4rem; }
}
.hero .container {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
  align-items: center;
}
@media (min-width: 860px) {
  .hero .container { grid-template-columns: 1.2fr 0.8fr; gap: 4rem; } /* text
    column widened from 1.05fr — at 1.05fr, right around the 860px
    breakpoint the column got too narrow for Call Now + See Our Services to
    fit on one line and they wrapped */
}

.hero .eyebrow { color: var(--color-gold-on-dark); }
.hero h1 { color: var(--color-white); max-width: 14ch; }
.hero .lede { color: rgba(255,255,255,0.62); font-size: 1.15rem; max-width: 46ch; margin-top: 1.5rem; }
.hero .btn-row { margin-top: 2.25rem; }
/* Call Now + See Our Services need tighter padding/gap than the site
   defaults to actually fit side by side on a phone — measured: at the
   standard 2.5rem padding + 0.9rem gap they need ~390px combined and wrap
   on every phone size tested, down to the largest (430px, iPhone Pro Max).
   At 1rem padding + 0.6rem gap they need ~294px, which comfortably fits
   360px-wide phones and up (wraps only on ~320px devices, a vanishingly
   rare screen size at this point). Desktop has plenty of room either way,
   so this is scoped to mobile only. */
@media (max-width: 859px) {
  .hero .btn-row { gap: 0.6rem; }
  .hero .btn-row .btn { padding: 0 1rem; }
}
.hero__photo {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border: 1px solid var(--hairline-dark);
  border-radius: var(--radius); /* same rounded treatment as the gallery tiles */
}

.hero-page {
  padding: 6.5rem 0 3.5rem;
  background: var(--color-black);
  color: var(--color-white);
}
.hero-page .eyebrow { color: var(--color-gold-on-dark); }
.hero-page h1 { color: var(--color-white); }
.hero-page p { color: rgba(255,255,255,0.62); }
.hero-page .btn-row { margin-top: 2rem; }

/* ---------- Facts strip ---------- */
.facts-strip {
  background: var(--color-charcoal);
  border-top: 1px solid var(--hairline-dark);
  border-bottom: 1px solid var(--hairline-dark);
}
.facts-strip .container {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
}
.facts-strip .fact {
  flex: 1 1 auto;
  padding: 1.1rem 1.5rem;
  border-left: 1px solid var(--hairline-dark);
  font-size: 0.74rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.65);
  font-weight: 700;
  text-align: center;
}
.facts-strip .fact:first-child { border-left: none; }
@media (max-width: 700px) {
  .facts-strip .fact { flex: 1 1 50%; border-left: none; border-top: 1px solid var(--hairline-dark); }
  .facts-strip .fact:nth-child(1), .facts-strip .fact:nth-child(2) { border-top: none; }
  .facts-strip .fact:nth-child(odd) { border-right: 1px solid var(--hairline-dark); }
}

/* ---------- Media placeholder ----------
   Deliberately minimal (thin hairline frame + small tracked caption) so it
   reads as an intentional placeholder, not a fake photo. Swap the element
   for a real <img> when job photos come in — see README.md. */
.media-placeholder {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--color-stone-deep);
  border: 1px solid var(--hairline);
  display: flex;
  align-items: flex-end;
  padding: 1.25rem;
  overflow: hidden;
  --art-color: rgba(17,17,17,0.13);
}
.media-placeholder span {
  position: relative;
  z-index: 1; /* sit above the ::before pattern/icon layers below */
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-mute);
  font-weight: 700;
}
.media-placeholder--dark {
  background: var(--color-charcoal);
  border-color: var(--hairline-dark);
  --art-color: rgba(255,255,255,0.14);
}
.media-placeholder--dark span { color: rgba(255,255,255,0.45); }
.media-placeholder--square { aspect-ratio: 1 / 1; }
.media-placeholder--tall { aspect-ratio: 3 / 4; }
.media-placeholder--wide { aspect-ratio: 16 / 9; }

/* ---------- Placeholder "photo" art ----------
   Illustrated stand-ins for real job photos — a plank-line pattern for
   flooring, a grid for tile, a simple line-art icon for trim/carpentry.
   Explicitly illustrative (not attempting to pass as real photography), and
   colored via --art-color so the same class works on both the light and
   dark placeholder variants. Swap the whole element for a real <img> when
   job photos come in — see README.md / gallery-data.js. */
.media-placeholder--flooring::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(180deg, transparent 0 33px, var(--art-color) 33px calc(33px + 1px));
}
.media-placeholder--tile::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    repeating-linear-gradient(180deg, transparent 0 45px, var(--art-color) 45px calc(45px + 1px)),
    repeating-linear-gradient(90deg, transparent 0 45px, var(--art-color) 45px calc(45px + 1px));
}
.media-placeholder__icon {
  position: absolute;
  inset: 0;
  bottom: 2.75rem; /* clear the caption */
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-mute);
  opacity: 0.55;
}
.media-placeholder--dark .media-placeholder__icon { color: rgba(255,255,255,0.4); }
.media-placeholder__icon svg { width: 42%; min-width: 100px; max-width: 190px; height: auto; }

/* ---------- Trio (three-up minimal columns) ---------- */
.trio {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
}
@media (min-width: 760px) {
  .trio { grid-template-columns: repeat(3, 1fr); gap: 0; }
  .trio__item { padding: 0 2.5rem; border-left: 1px solid var(--hairline); }
  .trio__item:first-child { padding-left: 0; border-left: none; }
}
.trio__item h3 { font-size: 1.15rem; margin-bottom: 0.6rem; }
.trio__item p { color: var(--color-mute); margin-bottom: 0.9rem; }

/* ---------- Service list (Services page service-info panel) ---------- */
.service-list { list-style: none; padding: 0; margin: 0 0 1.5rem; }
.service-list li {
  position: relative;
  padding: 0.65rem 0 0.65rem 1.5rem;
  border-bottom: 1px solid var(--hairline);
  color: var(--color-ink-soft);
}
.service-list li:first-child { border-top: 1px solid var(--hairline); }
.service-list li::before {
  content: '—';
  position: absolute;
  left: 0;
  color: var(--color-mute);
}

.tag {
  display: inline-block;
  border: 1px solid var(--color-ink);
  color: var(--color-ink);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.5rem 0.9rem;
  border-radius: 999px;
}

/* City list (Contact page service area) — reuses .tag for each city */
.city-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 1.25rem;
}

/* ---------- Quote block ---------- */
.quote-block { max-width: 44ch; margin: 0 auto; text-align: center; }
.quote-block .rule {
  width: 40px;
  height: 1px;
  background: rgba(255,255,255,0.35);
  margin: 0 auto 2rem;
}
.quote-block p {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.4rem, 2.5vw, 1.9rem);
  color: var(--color-white);
  line-height: 1.45;
  margin: 0;
}

/* ---------- Testimonials ---------- */
.testimonials { display: grid; gap: 3rem; }
@media (min-width: 860px) { .testimonials--grid { grid-template-columns: repeat(3, 1fr); } }
.testimonial { border-top: 1px solid var(--hairline); padding-top: 1.5rem; }
.testimonial .stars { color: var(--color-ink); letter-spacing: 3px; font-size: 0.85rem; margin-bottom: 0.9rem; }
.testimonial p { font-family: var(--font-display); font-size: 1.15rem; color: var(--color-black); font-style: italic; }
.testimonial .name { font-family: var(--font-body); font-weight: 700; font-size: 0.78rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-mute); }

.empty-note {
  text-align: center;
  padding: 2.5rem 1rem;
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.empty-note p:last-child { margin-bottom: 0; }
.section--dark .empty-note { border-color: var(--hairline-dark); }

/* ---------- Skeleton loading placeholders ----------
   Default static markup for the two sections whose real content is
   rendered client-side from testimonials-data.js / gallery-data.js
   (script.js, on DOMContentLoaded) — covers the gap between the page
   painting and those small local scripts finishing, most noticeable on a
   slow connection. script.js overwrites this the instant it runs; for
   visitors with JS disabled, a <noscript> block next to each hides the
   skeleton (via an injected <style>, since noscript can't remove sibling
   markup on its own) and shows a plain static fallback instead — so it can
   never get stuck shimmering forever. Animation already respects
   prefers-reduced-motion via the global rule near the top of this file. */
@keyframes skeleton-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.skeleton-block {
  background: linear-gradient(100deg, var(--color-stone-deep) 30%, var(--color-stone) 50%, var(--color-stone-deep) 70%);
  background-size: 300% 100%;
  animation: skeleton-shimmer 1.6s ease-in-out infinite;
  border-radius: 6px;
}
.testimonial-skel { border-top: 1px solid var(--hairline); padding-top: 1.5rem; }
.testimonial-skel .skeleton-block + .skeleton-block { margin-top: 0.6rem; }
.media-placeholder--skeleton {
  background: linear-gradient(100deg, var(--color-stone-deep) 30%, var(--color-stone) 50%, var(--color-stone-deep) 70%);
  background-size: 300% 100%;
  animation: skeleton-shimmer 1.6s ease-in-out infinite;
}

/* ---------- Stats row ---------- */
.stats-row { display: grid; gap: 2.5rem; grid-template-columns: 1fr; text-align: center; }
@media (min-width: 700px) { .stats-row { grid-template-columns: repeat(3, 1fr); } }
.stat .num { font-family: var(--font-display); font-size: clamp(2.4rem, 4vw, 3.2rem); color: var(--color-ink); display: block; line-height: 1; margin-bottom: 0.5rem; }
.stat .label { font-size: 0.76rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-mute); font-weight: 700; }

/* ---------- Footer ---------- */
.site-footer {
  background: var(--color-black);
  color: rgba(255,255,255,0.55);
  padding: 3.5rem 0 6rem; /* extra bottom padding clears the sticky call bar on mobile */
  font-size: 0.9rem;
  border-top: 1px solid var(--hairline-dark);
}
.site-footer a { color: rgba(255,255,255,0.85); text-decoration: none; }
.site-footer a:hover { color: var(--color-gold-on-dark); }
.footer__top {
  display: grid;
  gap: 2.5rem;
  grid-template-columns: 1fr;
  padding-bottom: 2.5rem;
  margin-bottom: 2rem;
  border-bottom: 1px solid var(--hairline-dark);
}
@media (min-width: 700px) {
  .footer__top { grid-template-columns: 1.3fr 1fr 1fr; }
  .site-footer { padding-bottom: 3.5rem; }
}
.footer__brand { display: flex; align-items: center; gap: 0.7rem; margin-bottom: 1rem; }
.footer__brand img { width: 32px; height: 32px; }
.footer__brand span { font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; font-size: 0.8rem; color: var(--color-white); }
.site-footer h4 { color: rgba(255,255,255,0.9); font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; margin-bottom: 1rem; }
.footer__bottom { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; font-size: 0.78rem; color: rgba(255,255,255,0.4); }

/* ---------- Sticky mobile call bar ----------
   Hidden until script.js confirms the visitor has scrolled past the hero
   (see the scroll listener in script.js) — showing it immediately on every
   page load duplicated the hero's own Call Now button with nothing to
   scroll past yet. Defaults to visible via <noscript> below for visitors
   without JS, same "never get stuck in the wrong state" approach as the
   skeleton loaders. */
.sticky-call {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  background: var(--color-black);
  border-top: 1px solid var(--hairline-dark);
  padding: 0.6rem 0.9rem;
  transform: translateY(100%);
  transition: transform 0.3s var(--ease);
}
.sticky-call.is-visible { transform: translateY(0); }
@media (min-width: 900px) { .sticky-call { display: none; } .site-footer { padding-bottom: 3.5rem; } }
body.no-sticky .sticky-call { display: none; }
body.no-sticky .site-footer { padding-bottom: 3.5rem; }
body.nav-open .sticky-call { transform: translateY(100%); } /* avoid a second "Call Now" showing over the open drawer */

/* ---------- Contact page (no nav) ---------- */
.contact-hero {
  background: var(--color-black);
  color: var(--color-white);
  padding: 4.5rem 0 4rem;
  text-align: center;
}
.contact-hero .brand-standalone { display: flex; flex-direction: column; align-items: center; gap: 1.1rem; margin-bottom: 1.75rem; }
.contact-hero .brand-standalone img { width: 56px; height: 56px; }
.contact-hero .brand-standalone .wordmark { font-size: 0.85rem; letter-spacing: 0.16em; text-transform: uppercase; font-weight: 700; color: rgba(255,255,255,0.85); }
.contact-hero h1 { color: var(--color-white); max-width: 20ch; margin: 0 auto 0.75rem; }
.contact-hero .lede { max-width: 40ch; margin: 0 auto; }
.contact-hero .btn-row { justify-content: center; margin-top: 2rem; }

.info-row {
  display: grid;
  gap: 2.5rem;
  grid-template-columns: 1fr;
  margin-top: 1rem;
}
@media (min-width: 700px) {
  .info-row { grid-template-columns: repeat(3, 1fr); }
  .info-item { padding: 0 2rem; border-left: 1px solid var(--hairline); }
  .info-item:first-child { padding-left: 0; border-left: none; }
}
.info-item .icon { color: var(--color-ink); margin-bottom: 1rem; width: 1.6em; height: 1.6em; }
.info-item h3 { font-size: 1rem; font-family: var(--font-body); font-weight: 700; letter-spacing: 0.04em; margin-bottom: 0.5rem; }

/* ---------- Project gallery ---------- */
.gallery-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 2rem 0 2rem;
}

/* Service info panel — shows what's included in the selected category,
   right above its filtered photos, so the service list and the portfolio
   read as one connected thing. Empty/hidden ([hidden]) when "All" is
   selected, since there's no single service list to show. */
.service-info { margin: 0 0 2rem; padding-bottom: 2rem; border-bottom: 1px solid var(--hairline); }
.service-info h3 { font-size: 1.15rem; margin-bottom: 0.9rem; }
.pill {
  display: inline-block;
  font-family: var(--font-body);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  padding: 0.55rem 1.1rem;
  border: 1px solid var(--color-ink);
  background: transparent;
  color: var(--color-ink);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.pill:hover { background: rgba(17,17,17,0.06); }
.pill.is-active { background: var(--color-ink); color: var(--color-white); }

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.25rem;
}
@media (min-width: 700px) { .gallery-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1000px) { .gallery-grid { grid-template-columns: repeat(4, 1fr); } }

/* Shown when a selected category (e.g. Painting) has no photos yet, instead
   of just leaving a blank grid. */
.gallery-grid.is-empty {
  display: block;
  padding: 2.5rem 1rem;
  text-align: center;
  border: 1px dashed var(--hairline);
}
.gallery-grid.is-empty::after {
  content: 'No photos posted for this one yet — give us a call and we can show you examples: 615-589-9540.';
  color: var(--color-mute);
  font-size: 0.95rem;
}

.gallery-item {
  display: flex;
  flex-direction: column;
  background: none;
  border: none;
  font-family: inherit;
  text-align: left;
  padding: 0;
  cursor: pointer;
}
.gallery-item__media {
  /* resets on top of the .media-placeholder base it's combined with in markup */
  transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
  /* Rounded for a modern, card-like look, at the site owner's request —
     real photos (this grid, the Home hero) now get the same soft-rounded
     treatment as buttons; illustrated CSS placeholders elsewhere stay
     sharp-cornered. */
  border-radius: var(--radius);
  /* Shimmers until the photo's onload fires (see gridArtFor() in script.js)
     — a not-yet-loaded <img> renders nothing, so this shows through on its
     own with no extra JS needed to "reveal" it. */
  background: linear-gradient(100deg, var(--color-stone-deep) 30%, var(--color-stone) 50%, var(--color-stone-deep) 70%);
  background-size: 300% 100%;
  animation: skeleton-shimmer 1.6s ease-in-out infinite;
}
.gallery-item:hover .gallery-item__media { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(17,17,17,0.1); }
.gallery-item.is-loaded .gallery-item__media { animation: none; background: var(--color-stone-deep); }
.gallery-item img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Caption lives below the photo as its own line, not overlaid on top of it
   — the overlay treatment was for the old illustrated placeholders' short
   tracked labels, and reads poorly over a real photo. */
.gallery-item__caption {
  margin-top: 0.6rem;
  font-size: 0.78rem;
  color: var(--color-mute);
}

/* ---------- Lightbox ---------- */
.lightbox {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(10,10,10,0.92);
  padding: 1.5rem;
  align-items: center;
  justify-content: center;
}
.lightbox.is-open { display: flex; }
body.lightbox-open { overflow: hidden; }
.lightbox__stage {
  /* ~3/4 of the screen (both width and height) on desktop — overridden to
     a true full-screen takeover on mobile below, with swipe/pinch/pan
     gestures (see script.js). touch-action:none hands all touch handling
     to that JS instead of fighting it with the browser's own native
     pan/zoom. */
  width: min(1400px, 75vw);
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}
.lightbox__stage figure {
  margin: 0;
  min-height: 0; /* lets this flex child actually shrink to fit max-height below, instead of forcing the stage taller */
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 100%;
}
.lightbox__stage .media-placeholder {
  /* Was forcing every photo into a 16:9 frame (via .media-placeholder--wide)
     with overflow:hidden — cropped anything that wasn't actually widescreen.
     Real photos should show in full, at their own aspect ratio. */
  aspect-ratio: auto;
  width: auto;
  max-width: 100%;
  max-height: 75vh;
  border: none;
  background: none;
  padding: 0;
}
.lightbox__stage img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 75vh;
  object-fit: contain;
  transform-origin: center center; /* pinch-zoom/pan in script.js scales+translates around this point */
}
.lightbox__stage figcaption {
  color: rgba(255,255,255,0.7);
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-top: 1rem;
  text-align: center;
}
.lightbox__close,
.lightbox__nav {
  position: fixed;
  background: transparent;
  border: 1px solid rgba(255,255,255,0.3);
  color: var(--color-white);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease);
}
.lightbox__close:hover, .lightbox__nav:hover { background: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.6); }
.lightbox__close { top: 1.5rem; right: 1.5rem; width: 44px; height: 44px; font-size: 1.5rem; }
.lightbox__nav { top: 50%; transform: translateY(-50%); width: 48px; height: 48px; font-size: 1.1rem; }
.lightbox__nav--prev { left: 1.5rem; }
.lightbox__nav--next { right: 1.5rem; }
@media (max-width: 700px) {
  .lightbox__nav { width: 40px; height: 40px; }
  .lightbox__nav--prev { left: 0.5rem; }
  .lightbox__nav--next { right: 0.5rem; }

  /* Full-screen takeover, like the iOS Photos app — swipe left/right to
     browse, pinch to zoom, drag to pan while zoomed, swipe down or tap the
     × to dismiss (see the touch handlers in script.js). */
  .lightbox { padding: 0; overscroll-behavior: contain; }
  .lightbox__stage {
    width: 100vw;
    max-height: 100vh;
    height: 100%;
    justify-content: center;
  }
  .lightbox__stage .media-placeholder,
  .lightbox__stage img {
    max-height: calc(100vh - 84px); /* leaves room for the caption below */
  }
}
