/* =========================================================================
   AUTHORITATIVE HEADER / LOGO SIZING — loaded LAST on every page.

   Each build agent inlined its own white-header patch (a reasonable defensive
   choice while design/tokens.css was still in flux), but they picked three
   different logo heights: 44/56px, 56/68px and 56/74px. The logo therefore
   changed size depending on which page you landed on.

   This file is the single source of truth for the header crest. It loads
   after every other stylesheet and after the inline <style> blocks are
   parsed, so it wins on cascade order without editing five files.

   The mark is a near-square badge (1391x1175, ~1.184:1), not a wide wordmark
   — it needs real height to read. David asked for it prominently displayed.
   ========================================================================= */

/* The base reset sets `img { max-width: 100% }` but never `height: auto`, so a
   constrained image kept its declared pixel height while its width shrank to
   the container — the blog rendered 1800x1013 photos as 260x1013 slivers.
   Element-selector specificity (0,0,0,1) means any component that genuinely
   needs a fixed height (.hero__photo, .video-thumb__img) still wins. */
img { height: auto; }

.site-header__logo-mark {
  height: 92px;
  width: auto;
  display: block;
  flex: none;
}

@media (max-width: 900px) {
  .site-header__logo-mark { height: 76px; }
}

@media (max-width: 480px) {
  .site-header__logo-mark { height: 58px; }
}

/* Three header markup variants shipped: 8 city pages render the crest alone,
   5 render crest + tagline, and 20 render crest + wordmark + tagline. Hiding
   both the wordmark and the tagline collapses all three to one identical
   lockup, and frees the bar space that was making "About" collide with the
   phone number. Nothing is lost: "EST. 2001" appears in the hero proof chips
   ("25 Years in Business") and the footer NAP block. */
.site-header__logo-tag { display: none; }

/* Real gutter between the last nav item and the phone block. */
.site-header__bar {
  column-gap: var(--space-8);
}

/* Some page groups render a "The Hardwood Guys" text wordmark beside the crest
   and some don't, so the header lockup was a different width depending on which
   page you landed on. The crest already contains the wordmark graphically, so
   the text is redundant and costs ~180px of bar. Hidden visually, kept in the
   accessibility tree. */
.site-header__logo-word {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Nav stays on one row at desktop — wrapping to two lines pushed the bar to
   130px tall and looked broken. */
.site-nav ul {
  flex-wrap: nowrap;
  white-space: nowrap;
}

@media (max-width: 1100px) {
  .site-nav ul { gap: var(--space-4); }
}

/* components.css turns the full desktop nav on at 1024px, but the bar needs
   roughly 1200px to fit crest + 6 nav items + phone + hours + CTA. In the
   1024-1199 band the nav overflowed its flex item and printed straight over
   the phone number ("About"/"Blog" on top of 470-826-3581).

   Below 1200px the NAV collapses to the hamburger — but the phone block and
   the Free Estimate button stay visible, because the phone is the primary
   conversion element on this site and tablet users should not lose it. */
@media (max-width: 1199px) {
  .site-nav { display: none; }
  .hamburger { display: inline-flex; }
  /* .site-nav was the flex:1 spacer. With it hidden, the phone and CTA
     collapse back against the logo, so push them to the right instead.
     .hamburger already carries margin-left:auto — leaving both auto margins
     in play splits the free space and strands a ~266px hole mid-bar, so the
     hamburger's is zeroed and the meta block owns the push. */
  .site-header__meta { margin-left: auto; }
  .hamburger { margin-left: 0; }
}

/* Last-resort guard: even if something else re-enables the nav early, never
   let it paint over a sibling.

   The `overflow: hidden` that used to sit on the <ul> here has been removed.
   It clipped the dropdown panels to the 44px height of the nav row, so the
   menus opened invisibly. It was guarding against a nav wider than its box,
   which the sizing block at the end of this file has now fixed properly --
   the nav needs 532px and has 611px. Clipping was treating the symptom. */
.site-nav { min-width: 0; }

/* ---------------------------------------------------------------------
   HEADER OUTLINE BUTTON — "Text Us a Photo"

   .btn--outline in components.css is built for the DARK hero scrim, so its
   text and border are both --white. The header band is --off-white (that was
   the settled brand decision: the logo carries a white glow and a chrome
   badge and needs a light surface). The result was white text on a near-white
   bar — an invisible call-to-action on all 186 pages.

   Navy on --off-white measures 12.04:1 (AAA). Hover fills navy and flips the
   label white, which is the same 13.60:1 pairing used everywhere else, and it
   deliberately avoids red-on-light-grey, one of the documented traps.
   --------------------------------------------------------------------- */
.site-header .btn--outline {
  color: var(--navy);
  border-color: var(--navy);
  background: transparent;
}
.site-header .btn--outline:hover,
.site-header .btn--outline:focus-visible {
  background: var(--navy);
  border-color: var(--navy);
  color: var(--white);
}

/* ---------------------------------------------------------------------
   MOBILE ALIGNMENT — hero proof chips and CTAs

   Measured on a 393px viewport (iPhone 15/16/17 class): the container is
   361px, but the four proof chips rendered at 281 / 273 / 204 / 164px and
   the two CTAs at 246 / 282px. Everything shared a left edge at 16px and
   nothing shared a right edge, so the hero read as a ragged staircase.

   On a narrow screen the fix is to stop letting content size itself: one
   column, every element the full width of the container. Chips, buttons and
   the form card then share both edges, which is what makes a stack look
   deliberate rather than accidental.

   Kept to <=560px. Above that the chips SHOULD flow inline -- full-width
   pills on a desktop hero would look absurd.
   --------------------------------------------------------------------- */
@media (max-width: 560px) {
  .proof-chips {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-2);
  }
  .proof-chip {
    width: 100%;
    justify-content: flex-start;
  }
  .hero__actions {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-3);
  }
  .hero__actions .btn {
    width: 100%;
    justify-content: center;
  }

  /* The CTA band reuses .hero__actions but nests it in a narrower centred
     .cta-band__inner, so width:100% resolved to 282px while the hero's
     buttons were 361px. Two button widths on one page reads as a mistake
     even when each pair is individually centred. Let the band's inner
     container run full width on mobile so every button on every page
     shares the same two edges. */
  .cta-band__inner { max-width: none; width: 100%; }
  .cta-band .hero__actions { width: 100%; }
}

/* ---------------------------------------------------------------------
   KEN BURNS — slow drift on hero photography

   A 20s scale from 1.00 to 1.08 with a slight pan. Long and shallow on
   purpose: a hero that visibly moves competes with the headline and the
   phone number, which are the only two things on this page that matter.
   The intent is that a visitor feels the image is alive without being able
   to point at the motion.

   transform only -- it runs on the compositor, so it costs no layout or
   paint work and will not fight the LCP image for main-thread time.

   Disabled entirely under prefers-reduced-motion. Vestibular disorders are
   real and a slow zoom is a common trigger; the site loses nothing without
   it.
   --------------------------------------------------------------------- */
@keyframes thg-kenburns {
  from { transform: scale(1) translate3d(0, 0, 0); }
  to   { transform: scale(1.08) translate3d(-1.5%, -1%, 0); }
}
.hero__media,
.hero__photo img,
.hero--lite .hero__media {
  will-change: transform;
  animation: thg-kenburns 20s ease-out forwards;
  transform-origin: center center;
}
/* The scrim must not drift with the photo or the edges would show. */
.hero__scrim { animation: none; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .hero__media,
  .hero__photo img,
  .hero--lite .hero__media {
    animation: none;
    transform: none;
    will-change: auto;
  }
}

/* ---------------------------------------------------------------------
   MOBILE HEADER — stop wasting the bar

   components.css sets .site-header__meta{display:none} at base and only
   restores it at min-width:1024px, so on a phone the header was a logo, a
   hamburger, and roughly 200px of dead white. The most valuable strip of a
   contractor's site was showing nothing actionable.

   Below 1024px the phone number and a tap-to-text button move into that gap.
   Hours and the Free Estimate button stay hidden -- they do not fit, and the
   sticky bottom bar already carries Free Estimate.

   Yes, this duplicates the bottom bar. That is deliberate: the bar is fixed
   to the bottom of the viewport and a first-time visitor's eye starts at the
   top. Tap-to-call in the header converts people who never scroll.
   --------------------------------------------------------------------- */
@media (max-width: 1023px) {
  .site-header__meta {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--space-3);
    margin-left: auto;
    margin-right: var(--space-2);
  }
  .site-header__meta .site-header__hours { display: none; }
  .site-header__meta .btn--outline {
    padding: 0.45rem 0.7rem;
    font-size: var(--text-sm);
    min-height: 44px;
  }
  /* The label is too wide next to a phone number on a 393px screen; the
     speech-bubble icon carries the meaning and the link is still labelled
     for screen readers. */
  .site-header__meta .btn--outline .btn__label { display: none; }
  .site-header__phone {
    font-size: var(--text-base);
    font-weight: var(--weight-bold);
    color: var(--navy);
    white-space: nowrap;
  }
}
@media (max-width: 359px) {
  /* Only the very narrowest phones (iPhone SE) drop the text button. At
     393px (iPhone 15/16/17) the logo, number, icon button and hamburger all
     fit, and click-to-text is worth more than the breathing room. */
  .site-header__meta .btn--outline { display: none; }
}


/* =========================================================================
   HEADER DROPDOWNS  (added 2026-08-03)

   No JavaScript. Desktop opens on :hover and :focus-within; mobile is a
   native <details>. See build-nav-dropdown.py for why.
   ========================================================================= */

.site-nav .has-sub { position: relative; }

.site-nav .has-sub > a {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
}

.nav-caret {
  width: 0.7em;
  height: 0.7em;
  flex: none;
  transition: transform var(--motion-base, .2s) var(--motion-ease, ease);
}

.site-nav .has-sub:hover > a .nav-caret,
.site-nav .has-sub:focus-within > a .nav-caret { transform: rotate(180deg); }

.site-nav .nav-sub {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 60;
  display: block;
  min-width: 16rem;
  margin: 0;
  padding: var(--space-2, .5rem);
  background: var(--white, #fff);
  border: 1px solid var(--color-border, #e2e5ea);
  border-radius: var(--radius-base, 8px);
  box-shadow: var(--shadow-lg, 0 12px 32px rgba(16, 32, 56, .16));

  /* Hidden with opacity+visibility rather than display:none so the caret
     rotation and fade can animate, and so the submenu keeps its box for
     :focus-within to land on. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity .16s ease, transform .16s ease, visibility .16s;
}

/* The 8px gap between the nav link and the panel would close the menu the
   moment the pointer crossed it. This invisible bridge spans the gap. */
.site-nav .nav-sub::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -10px;
  height: 10px;
}

.site-nav .has-sub:hover > .nav-sub,
.site-nav .has-sub:focus-within > .nav-sub {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

/* The parent <ul> is a nowrap flex row; the submenu must not inherit that. */
.site-nav .nav-sub { white-space: normal; }
.site-nav .nav-sub li { display: block; }

.site-nav .nav-sub a {
  display: block;
  padding: var(--space-2, .5rem) var(--space-3, .75rem);
  border-radius: var(--radius-sm, 6px);
  font-size: var(--text-sm, .9375rem);
  font-weight: var(--weight-medium, 500);
  line-height: 1.35;
  color: var(--color-heading, #12233d);
  text-decoration: none;
}

.site-nav .nav-sub a:hover,
.site-nav .nav-sub a:focus-visible {
  background: var(--color-surface-alt, #f3f5f8);
  color: var(--blue, #1c6fd0);
}

/* Right-hand dropdowns would otherwise overflow the viewport on a 1024px
   screen where the nav sits far right. */
@media (max-width: 1200px) {
  .site-nav .has-sub:nth-last-child(n + 5) .nav-sub { left: auto; right: 0; }
}

/* ---- mobile: native <details> ---------------------------------------- */

.site-header__mobile-nav .mnav-group details { border: 0; }

/* Match .site-header__mobile-nav a exactly. A <summary> is not an <a>, so it
   inherited the body's dark navy text colour and rendered invisible against
   the navy panel -- the row was there and tappable, just unreadable. */
.site-header__mobile-nav .mnav-group summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3, .75rem);
  padding: var(--space-4) 0;
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--color-text-inverse);
  border-bottom: 1px solid rgba(255, 255, 255, 0.18);
  cursor: pointer;
  list-style: none;
}

/* An open group owns its children, so the divider under the summary would
   read as a separator between the label and its own list. */
.site-header__mobile-nav .mnav-group details[open] > summary { border-bottom-color: transparent; }

/* Safari draws its own disclosure triangle unless this is killed. */
.site-header__mobile-nav .mnav-group summary::-webkit-details-marker { display: none; }

.site-header__mobile-nav .mnav-group details[open] > summary .nav-caret {
  transform: rotate(180deg);
}

.site-header__mobile-nav .mnav-group summary .nav-caret {
  width: 0.7em;
  height: 0.7em;
  opacity: .75;
}

/* The list's last link and the meta block's top rule drew two horizontal
   lines 64px apart with nothing between them, which reads as an empty menu
   row rather than a section break. One line is the separator. */
.site-header__mobile-nav > ul > li:last-child > a,
.site-header__mobile-nav > ul > li:last-child > details:not([open]) > summary {
  border-bottom-color: transparent;
}

.site-header__mobile-nav .mnav-group ul {
  margin: var(--space-2, .5rem) 0 var(--space-3, .75rem);
  padding-left: var(--space-4, 1rem);
  border-left: 2px solid rgba(255, 255, 255, .22);
}

.site-header__mobile-nav .mnav-group ul a {
  font-size: var(--text-sm, .9375rem);
  font-weight: var(--weight-normal, 400);
  opacity: .92;
}

/* =========================================================================
   HEADER NAV FIT  (added 2026-08-03)

   Found while adding the dropdowns, but it is NOT caused by them: the
   desktop nav has been overflowing its box at every screen width since the
   header was built, and "Blog" was clipped mid-word for every desktop
   visitor. The nav needed 593px and never had more than 539px, because
   .site-header__bar is a .container capped at 1200px -- so making the window
   wider never gave the nav another pixel. Nothing surfaced it because
   white-space: nowrap clips silently instead of wrapping.

   Three changes, measured on the live page rather than guessed:

   1. Nav sizing: 17px -> 16px, item gap --space-5 -> 14px, and a caret that
      costs 0.62em instead of 0.7em + 0.3em of gap. Need drops 593 -> 532.
   2. Header bar gap --space-6 (32px) -> --space-5 (20px), which hands the
      nav back the room it was short. Available rises 539 -> 575. Padding is
      left at 40px on purpose so the logo stays flush with the page content
      below it.
   The 1024-1199 band was already handled further up this file (the nav
   collapses to the hamburger there), so this only has to fix 1200px and up.

   Result at 1200px+: 611px available for 532px of nav. 79px of slack, which
   is the margin for font rendering that differs on Windows.
   ========================================================================= */

@media (min-width: 1024px) {
  .site-header__bar { gap: var(--space-5); }

  .site-nav a { font-size: 1rem; }
  .site-nav ul { gap: 14px; }
  .site-nav .has-sub > a { gap: 0.15em; }
  .nav-caret { width: 0.62em; height: 0.62em; }
}

/* =========================================================================
   OUTLINE BUTTONS ON LIGHT BACKGROUNDS  (added 2026-08-03)

   .btn--outline is a DARK-background variant: --color-btn-outline-text and
   --color-btn-outline-border are both --white, which is correct on the hero
   scrim and inside the navy .cta-band, where nearly all 400+ of them live.

   On a light section it is white on off-white: 1.09:1. Not "low contrast" --
   invisible. The gallery's Instagram fallback shipped that way, so the
   "Open Instagram" button was a ghost inside an empty dashed box.

   Scoped to the placeholder rather than made global: a blanket
   `.section .btn--outline` override would also hit the ~221 buttons that sit
   on the navy .cta-band INSIDE a light <section>, and turn those invisible
   in the other direction. verify-site.py now fails the build if an outline
   button appears with neither a .cta-band nor a .hero ancestor.
   ========================================================================= */

.ig-feed__placeholder .btn--outline {
  color: var(--navy);
  border-color: var(--navy);
  background: transparent;
}
.ig-feed__placeholder .btn--outline:hover,
.ig-feed__placeholder .btn--outline:focus-visible {
  background: var(--navy);
  border-color: var(--navy);
  color: var(--white);
}


/* =========================================================================
   HEADER LOGO — belt and braces  (added 2026-08-04)

   The four city pages added on 2026-08-03 shipped with a header <img> that
   carried no class and no page-local sizing rule. Nothing constrained it, so
   `img { height: auto }` let a 320x270 PNG render at 260x219 and eat the
   whole header on a phone.

   Sizing by CLASS alone assumes every page remembers the class. This rule
   targets the image by its position in the header instead, so a forgotten
   class costs a few pixels of accuracy rather than the entire layout. The
   .site-header__logo-mark rules above still win where they apply -- they are
   more specific -- so nothing that works today changes.
   ========================================================================= */
.site-header__logo img { height: 92px; width: auto; display: block; }
@media (max-width: 900px) { .site-header__logo img { height: 76px; } }
@media (max-width: 480px) { .site-header__logo img { height: 58px; } }

/* =========================================================================
   HERO PHOTO LAYER  (added 2026-08-04)

   canton-hardwood-flooring.html carried these two rules in a page-local
   <style> block, and the four city pages added on 2026-08-03 were built from
   its markup but not its CSS. The photo therefore rendered as an ordinary
   inline image -- 191px tall inside a 914px hero -- with the headline
   printed over it and ~700px of white space beneath.

   Hoisted here so the markup pattern <div class="hero__media"><img></div>
   works on any page that uses it, present or future. Pages that already
   declare these locally are unaffected: the declarations are identical.

   The ::after carries the scrim. It has to be a pseudo-element rather than a
   background on .hero__media, because an absolutely positioned <img> covers
   its parent's background but not a later-painted pseudo-element.
   ========================================================================= */

.hero--photo .hero__media { background: var(--color-bg-header); }

.hero--photo .hero__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.hero--photo .hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(rgba(13, 13, 13, .66), rgba(13, 13, 13, .5));
}
