/* Tax with Integrity: site chrome additions.
   Enqueued GLOBALLY, after the design system, on every page.

   Why this file exists: the design system's header specimen is a desktop
   composition with no mobile drawer and no scroll state, and its comparison
   panel styles h4 headings. Real pages need a mobile nav and, per the
   wireframes, the comparison blocks are H3 (an H2 must be followed by H3
   children). These rules add that behavior without forking the design system,
   so design-system/ stays a faithful copy of the cloud design project. */

/* ---------- Mobile nav toggle and drawer ---------- */
.nav-toggle {
  display: none;
  background: none; border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 8px; cursor: pointer; color: var(--navy-700);
  line-height: 0; touch-action: manipulation;
}
.nav-toggle:hover { border-color: var(--royal-300); }
.nav-toggle:focus-visible { outline: none; box-shadow: var(--focus-shadow); }
.nav-toggle .lucide { transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1); }
.nav-toggle[aria-expanded="true"] .lucide { transform: rotate(90deg); }

.site-header.is-condensed .bar { height: 62px; }
.site-header.is-condensed .brand img { height: 36px; }
.site-header .bar, .site-header .brand img { transition: height var(--transition-base); }

/* Nav items must never wrap. Archivo is materially wider than the system-sans
   fallback, so once the real brand font loads the five nav labels no longer fit
   on one line at 1440px and each breaks in two. The design system was specced
   against the fallback, which hid this. Keep the labels on one line and buy the
   room back from the gaps before the mobile breakpoint takes over. */
.site-header nav a { white-space: nowrap; }

@media (max-width: 1180px) {
  .site-header .bar { gap: var(--space-4); }
  .site-header nav { gap: var(--space-4); margin-left: var(--space-2); }
  .site-header nav a { font-size: 14px; }
}

@media (max-width: 1040px) {
  /* Below this the five labels plus both CTAs genuinely do not fit. Drop the
     ghost text button; the primary phone button and the mobile call bar keep
     the number reachable. */
  .site-header .header-actions .btn--ghost { display: none; }
}

@media (max-width: 900px) {
  html, body { overflow-x: clip; }
  .nav-toggle {
    display: inline-flex; align-items: center; justify-content: center;
    order: 3; margin-left: auto; position: relative; z-index: 72;
  }
  .site-header .bar { gap: var(--space-4); height: auto; padding-top: var(--space-3); padding-bottom: var(--space-3); }
  .site-header.is-condensed .bar { height: auto; }
  .site-header .brand { position: relative; z-index: 72; }
  .site-header .spacer { display: none; }
  /* The header call and text buttons are redundant on mobile: the sticky
     bottom call bar carries them within thumb reach. */
  .site-header .header-actions { display: none; }

  .site-header nav {
    display: flex;
    position: fixed; z-index: 70;
    top: var(--mobile-header-height, 69px); right: 0; bottom: auto;
    height: calc(100dvh - var(--mobile-header-height, 69px));
    width: min(86vw, 380px);
    flex-direction: column; gap: 0; margin-left: 0;
    padding: var(--space-4) var(--space-6) var(--space-6);
    overflow-y: auto; overscroll-behavior: contain;
    background: var(--surface-card);
    border-left: 1px solid var(--border-subtle);
    box-shadow: -18px 0 44px rgba(2, 26, 98, 0.18);
    visibility: hidden;
    transform: translate3d(105%, 0, 0);
    transition:
      transform 240ms cubic-bezier(0.22, 1, 0.36, 1),
      visibility 0s linear 240ms;
    will-change: transform;
  }
  .site-header nav.is-open {
    visibility: visible;
    transform: translate3d(0, 0, 0);
    transition-delay: 0s;
  }
  .site-header nav a { padding: var(--space-3) 0; font-size: var(--fs-body); border-bottom: 1px solid var(--border-subtle); }
  .site-header nav a:last-child { border-bottom: none; }

  .nav-scrim {
    position: fixed; z-index: 45;
    top: var(--mobile-header-height, 69px); right: 0; bottom: 0; left: 0;
    background: rgba(2, 18, 68, 0.52);
    opacity: 0; visibility: hidden; pointer-events: none;
    transition:
      opacity 180ms ease,
      visibility 0s linear 180ms;
  }
  .nav-scrim.is-visible {
    opacity: 1; visibility: visible; pointer-events: auto;
    transition-delay: 0s;
  }
  html.nav-drawer-open {
    overflow: hidden;
    overscroll-behavior: none;
  }
  body.nav-drawer-open { overscroll-behavior: none; }
}

/* Anchor destinations clear the sticky header instead of landing beneath it. */
html { scroll-padding-top: 92px; }
[id] { scroll-margin-top: 92px; }

/* ---------- Keep the sticky header clear of the WordPress admin bar ----------
   The header is `position: sticky; top: 0`. The admin bar is `position: fixed;
   top: 0; z-index: 99999`, so for a logged-in user the header pins UNDERNEATH it
   the moment the page scrolls and loses its top 32px (46px on tablet). At scroll
   0 nothing looks wrong, which is why this only shows up once you scroll.

   Values are read from WordPress core's own wp-includes/css/admin-bar.min.css,
   not assumed:
     #wpadminbar { height: 32px; position: fixed; top: 0; z-index: 99999 }
     html { --wp-admin--admin-bar--height: 32px }
     @media screen and (max-width: 782px) { html { --wp-admin--admin-bar--height: 46px }
                                            html #wpadminbar { height: 46px } }
     @media screen and (max-width: 600px) { #wpadminbar { position: absolute } }

   Rather than hardcode 32 and 46 and re-declare WordPress's breakpoints, the
   offset reads core's own custom property, so the tablet switch comes for free
   and the site follows if WordPress ever changes the height. The `0px` fallback
   covers the logged-out case, where admin-bar.css is not enqueued at all and the
   property is undefined; `body.admin-bar` (a class WordPress adds only when the
   bar renders) is the second guard.

   `body.admin-bar .site-header` is specificity (0,2,1) against the design
   system's `.site-header` (0,1,0), so it wins without !important. */
body.admin-bar .site-header { top: var(--wp-admin--admin-bar--height, 0px); }

@media screen and (max-width: 600px) {
  /* At 600px and below core switches the bar to `position: absolute`, so it
     scrolls away with the document and the header can pin flush again. Offsetting
     here would leave a permanent 46px gap under the bar. This is WordPress's
     breakpoint, deliberately not the design system's 900px. */
  body.admin-bar .site-header { top: 0; }
}

/* ---------- Comparison panel: H3 parity with the design system's H4 ---------- */
.compare .compare-col h3 { margin-bottom: var(--space-4); font-size: var(--fs-h4); }
.compare .compare-col--plain h3 { color: var(--slate-600); }
.compare .compare-col--brand h3 { color: var(--royal-300); }

/* ---------- Responsive layer for the shared design-system components ----------
   design-system/styles.css contains ZERO media queries: it was authored as
   desktop specimen cards, so every multi-column component is a hard grid that
   never collapses. On a 390px viewport the 4-column footer alone pushed the
   document to 595px wide, which then dragged the fixed call bar out with it.

   These are the three hard grids plus the one side-by-side flex component in
   the system. Fixing them here rather than in the page CSS means all 25 pages
   inherit the fix, and design-system/ stays a faithful copy of the cloud
   design project. Verified against styles.css: .compare (1fr 1fr),
   .site-footer .grid (1.4fr 1fr 1fr 1fr), .blog-grid (repeat(3, 1fr)),
   .author-bio (flex row). */
@media (max-width: 900px) {
  .compare { grid-template-columns: 1fr; }
  .site-footer .grid { grid-template-columns: 1fr 1fr; gap: var(--space-6); padding-top: var(--space-8); }
  .site-footer .footer-brand { grid-column: 1 / -1; }
  .blog-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 620px) {
  .site-footer .grid { grid-template-columns: 1fr; }
  .site-footer .legal { flex-direction: column; gap: var(--space-3); }
  .blog-grid { grid-template-columns: 1fr; }
  .author-bio { flex-direction: column; align-items: flex-start; }
}

/* ==================================================================
   SHARED PAGE-LAYOUT PRIMITIVES
   ==================================================================
   Promoted here from twi-homepage.css on 2026-08-06, before the second
   page was built. These are the layout primitives every one of the 25
   CSA pages composes from: the hero shell, the trust bar, the generic
   multi-column text grids, the prose column, the FAQ list, the final
   CTA band, and the sticky mobile call bar.

   They were authored inside twi-homepage.css, which is enqueued only on
   templates/homepage.php, so no other page could see them. Leaving them
   there meant either duplicating ~20 rules into all 24 remaining page
   stylesheets, or building pages that silently lost their layout. The
   second failure was already latent and would have shipped: the
   .mobile-callbar MARKUP lives in the shared site-footer.php, but its
   styling lived here in the homepage's CSS, so on every non-homepage
   page the sticky call bar would have rendered as two unstyled buttons
   in normal flow at the end of the document instead of a fixed bar.

   What stays page-scoped in twi-homepage.css: only the three-card service
   router (.svc-grid). The full-bleed photo hero treatment was promoted here
   too (see the next block) once a second page needed it.

   The design system itself (design-system/styles.css) is deliberately
   NOT touched: it stays a faithful copy of the cloud design project. */

/* ---------- HERO shell ----------
   Structure only. The navy wash is the shared fallback, so a hero always
   reads as a dark surface under white copy even if its own background
   never arrives. */
.hero { position: relative; overflow: hidden; background: var(--wash-navy); }

/* ---------- Hero on the brand gradient, for pages with no photograph ----------
   Promoted here from twi-business-tax-strategy.css on 2026-08-07, when the
   second page needed it. The content-replacement pages have no photography of
   their own: the client's real team photos are not in the repo, and the only
   production images wide enough are AI stock of people who are not on the TWI
   team, which the SBP photography direction rules out.

   --wash-royal IS the brand gradient, navy #021A62 to royal #365DBE, the same
   ramp as the A and the checkmark in the logo. Contrast against white copy at
   the lightest end of the ramp (#365DBE) is 6.06:1, which already clears the
   4.5:1 body floor across the whole gradient, so this needs no overlay and no
   per-element override beyond the shared .hero .lead rule below.

   That 6.06 is the conservative bound, not the real reading. Measured on the
   live pages with contrast-on-photo.js (which hides the text and samples the
   LIGHTEST composited pixel actually behind it), the heroes on /about/,
   /business-tax-strategy/ and /s-corp-election/ come back at 8.70:1 for the
   lead paragraph and 8.98 to 9.11:1 for the H1, because the copy sits in the
   darker left portion of the ramp rather than at its lightest end.

   Behaviour-neutral for every already-shipped page: .hero--brand is a NEW class
   and nothing that was live before 2026-08-07 carries it. */
.hero--brand { background: var(--wash-royal); }

/* ---------- Running prose that closes a comparison panel ----------
   Promoted here from twi-business-tax-strategy.css on 2026-08-07, when
   /multi-business-real-estate-tax/ needed the identical treatment.

   The comparison panel is two full-height cards; a third heading sitting under
   it is running prose and needs the section's own rhythm rather than the tight
   margin .prose-block uses when it OPENS a section. */
.prose-block--after-compare { margin-top: var(--space-7); }

/* ---------- Full-bleed photo surface behind a brand overlay ----------
   The site-wide treatment for any band built on a photograph: the hero on
   every page, plus the CTA bands that production builds on a background
   image. Promoted here from twi-homepage.css on 2026-08-06, when the second
   page turned out to need the identical treatment.

   The image is a real <img> rather than a CSS background so it can carry alt
   text, which Rank Math checks for the focus-keyword literal and which a CSS
   background cannot express. */
.hero-bg, .band-bg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center 30%;
  /* If the photo is missing or still loading, a browser paints the alt text
     into the layout, which lands as grey copy across the top of the band.
     Zeroing the font keeps the alt attribute intact for assistive tech and for
     Rank Math while the section degrades cleanly to the navy wash. */
  font-size: 0; color: transparent;
  /* The photo sits under a dark overlay, so it can carry a touch more presence
     than it would under a white wash. */
  filter: saturate(1.05);
}

/* Brand-blue overlay, operator decision 2026-08-06. On the homepage this is a
   deliberate deviation from the approved wireframe, which specifies a white
   overlay with navy copy; the blue overlay inverts the band to a dark surface,
   so the copy goes white via section--navy on the band element.

   The 0.75 base opacity is measured, not chosen by eye: overlay opacities from
   0.55 to 0.88 were rendered and the background pixels behind the headline
   sampled for WCAG contrast against white text. 0.75 is the lowest value where
   the worst-case (lightest) pixel still clears 4.5:1 for body copy. Below it
   only large headings pass and the lead paragraph fails. Average contrast at
   0.75 is 8.9:1.

   The gradient runs navy to royal left-to-right, matching the brand gradient
   direction, and eases off toward the right where there is no copy so the
   photograph still reads. */
.hero-overlay {
  position: absolute; inset: 0;
  background: linear-gradient(
    100deg,
    rgba(2, 26, 98, 0.75) 0%,
    rgba(2, 26, 98, 0.705) 45%,
    rgba(54, 93, 190, 0.645) 100%
  );
}
.hero .container {
  position: relative;
  padding-top: var(--space-10); padding-bottom: var(--space-10);
  min-height: 600px; display: flex; flex-direction: column; justify-content: center;
}
.hero-copy { max-width: 780px; }
.hero h1 { font-size: var(--fs-display); line-height: var(--lh-display); }
.hero-ctas { display: flex; gap: var(--space-4); flex-wrap: wrap; margin: var(--space-6) 0 var(--space-5); }

/* MEASURED CONTRAST RULE, do not relax without re-measuring.
   .section--navy p resolves to --text-inverse-muted (#B9C5E2), which is tuned
   for a FLAT navy panel. Over a photograph the composited background is far
   lighter in places and that tone fails the 4.5:1 body-copy floor. Sampled on
   /business-tax-planning/ with the text hidden so anti-aliasing could not
   contaminate the reading:

     hero H1, white          5.73:1 against worst pixel rgb(87,100,149)   PASS
     hero lead, white        6.21:1 against rgb(78,95,147)                PASS
     hero tagline, muted     3.62:1 against rgb(76,95,145)                FAIL

   So every text element inside a band built on a photograph is forced to full
   white. The muted tone stays correct in every other navy section, where the
   background really is a flat gradient.

   The selectors are deliberately specific enough to beat `.section--navy p`
   (0,1,1); a bare `.hero-tagline` (0,1,0) loses to it, which is exactly how the
   tagline ended up failing in the first place. */
.hero .lead,
.hero p,
.cta-band--photo p,
.cta-band--photo .prose-block p { color: var(--text-inverse); }
.hero .promise span em { color: rgba(255, 255, 255, 0.88); }

/* ---------- TRUST BAR: verifiable proof badge cards ----------
   The wireframes call for a "visually distinct band (navy or light-gray)"
   carrying badge cards (icon plus short text). Light gray is used rather
   than navy so the band separates the hero from the section beneath it
   without stacking two dark blocks in a row. */
.trust-bar { background: var(--surface-section); border-top: 1px solid var(--border-subtle); padding-bottom: var(--space-6); }
/* .trust-bar's own bottom padding plus the default .section top padding on
   whatever follows it stacked into a ~192px gap that read as an empty band
   before the next section's content started (flagged 2026-08-12 on
   /s-corp-election/ and /irs-compliance-documentation/). Tightened both
   sides of that one seam instead of touching .section globally, so every
   other section-to-section transition sitewide keeps its normal rhythm. */
.trust-bar + .section { padding-top: var(--space-6); }
.trust-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-5); margin-top: var(--space-6); margin-bottom: var(--space-6); }
.trust-badge {
  display: flex; gap: var(--space-3); align-items: flex-start;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  box-shadow: var(--shadow-card);
}
.trust-badge .lucide { flex: 0 0 auto; width: 24px; height: 24px; color: var(--accent); margin-top: 1px; }
.trust-badge span { font-size: var(--fs-small); line-height: 1.55; color: var(--text-body); }

/* ---------- Generic multi-column text sections ---------- */
.two-col { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-6) var(--space-7); margin-top: var(--space-6); }
.three-col { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-6); margin-top: var(--space-6); }
.stack-block h3 { margin-bottom: var(--space-3); }
.prose-block { max-width: 760px; }
.prose-block h3 { margin-top: var(--space-6); margin-bottom: var(--space-3); }
.prose-block h3:first-child { margin-top: 0; }

/* ---------- Text + photo split: .two-col with a framed image column ---------- */
.split-media { align-items: start; }
.split-media .media-col .photo-frame { aspect-ratio: 4 / 5; }

/* ---------- Integrity Tax Blueprint: compact six-marker strip ---------- */
.bp-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); margin-top: var(--space-6); }

/* ---------- Comparison panel wrapper ---------- */
.compare-wrap { margin-top: var(--space-6); }

/* ---------- FAQ accordion ---------- */
.faq-list { margin-top: var(--space-6); max-width: 860px; }
.faq-item summary h3 { display: inline; font-size: var(--fs-h4); margin: 0; }
.faq-item.is-animating { overflow: hidden; }

/* ---------- Final CTA band ---------- */
.cta-band { text-align: center; }
.cta-band .prose-block { margin-left: auto; margin-right: auto; }
.cta-band .hero-ctas { justify-content: center; }
.cta-band .two-col { text-align: left; }

/* ---------- Sticky mobile call and text bar ----------
   Markup lives in the shared template-parts/layout/site-footer.php, so
   this styling has to be global too. The wireframes require the number
   to stay one thumb-tap away on mobile. */
.mobile-callbar { display: none; }

@media (max-width: 900px) {
  .trust-grid { grid-template-columns: repeat(2, 1fr); }
  .two-col, .three-col, .bp-grid { grid-template-columns: 1fr; }
  .split-media .media-col .photo-frame { aspect-ratio: auto; height: 300px; }
  /* On narrow viewports the copy spans the full width, including the area where
     the gradient is weakest, so it reverts to a flat wash at higher opacity. */
  .hero-overlay { background: rgba(2, 26, 98, 0.82); }
  .hero .container { min-height: 0; padding-top: var(--space-8); padding-bottom: var(--space-8); text-align: left; }
  .hero-ctas { flex-direction: column; align-items: stretch; }
  .hero-ctas .btn { justify-content: center; }
  /* Below 900px the CTA buttons are full-width blocks, so a long label must be
     allowed to wrap. The design system sets `white-space: nowrap` on .btn, which
     forces the button's min-content width to the full label: "Ready to Talk Tax
     Strategy? Contact Us" measured 360px and overflowed a 360px viewport. Short
     labels are unaffected, since they never reach the wrap point. */
  .hero-ctas .btn { white-space: normal; text-align: center; }

  .mobile-callbar {
    display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2);
    position: fixed; bottom: 0; left: 0; right: 0; z-index: 60;
    padding: var(--space-3); background: var(--white);
    box-shadow: 0 -2px 12px rgba(13, 29, 92, 0.14);
    opacity: 0; visibility: hidden; pointer-events: none;
    transform: translate3d(0, calc(100% + 8px), 0);
    transition:
      opacity 240ms ease,
      transform 340ms cubic-bezier(0.22, 1, 0.36, 1),
      visibility 0s linear 340ms;
  }
  .mobile-callbar.is-visible {
    opacity: 1; visibility: visible; pointer-events: auto;
    transform: translate3d(0, 0, 0);
    transition-delay: 0s;
  }
  body.nav-drawer-open .mobile-callbar {
    opacity: 0; visibility: hidden; pointer-events: none;
    transform: translate3d(0, calc(100% + 8px), 0);
  }
  .mobile-callbar .btn { justify-content: center; }
  body { transition: padding-bottom 260ms ease; }
  body.has-mobile-callbar { padding-bottom: 76px; }
}

/* Subtle entrance motion is attached by site-nav.js only below the fold, so
   core content remains visible if JavaScript is unavailable. */
.motion-ready [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  transition:
    opacity 560ms ease var(--reveal-delay, 0ms),
    transform 620ms cubic-bezier(0.22, 1, 0.36, 1) var(--reveal-delay, 0ms);
}
.motion-ready [data-reveal].is-revealed {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .nav-toggle .lucide,
  .site-header nav,
  .nav-scrim,
  .mobile-callbar,
  body,
  .motion-ready [data-reveal] {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
  .motion-ready [data-reveal] { opacity: 1; transform: none; }
}

@media (max-width: 560px) {
  .trust-grid { grid-template-columns: 1fr; }
}

/* ==================================================================
   SHARED PAGE COMPONENTS (production re-skin)
   ==================================================================
   Promoted here from twi-business-tax-planning.css on 2026-08-06, once the
   second, third and fourth re-skinned pages turned out to need the identical
   components. Production builds all 21 non-homepage pages from the same small
   vocabulary (a photo hero, image-and-copy splits, card grids, list-as-boxes,
   an accordion, two CTA bands), so these belong in the global layer.

   Leaving them page-scoped would have duplicated the block into 21 stylesheets
   and let the pages drift apart, which is the same trap the layout primitives
   above were rescued from earlier in the day. With these here, a typical
   re-skinned page needs NO page-specific CSS at all.
   ================================================================== */

/* ---------- Hero tagline ----------
   Production carries a short line between the H1 and the lead paragraph. It is
   a heading widget with header_size set to 'p', so it stays a <p> and is styled
   as an overline-weight line rather than promoted to a heading, which keeps the
   heading outline identical to production.

   Colour is deliberately NOT set here. It comes from the measured contrast rule
   in twi-site-chrome.css, which forces full white on every text element inside
   a photo-backed band. An earlier version set --royal-300 on this class and it
   measured 3.62:1 against the worst background pixel, under the 4.5:1 body
   floor, because the bare class (0,1,0) also lost the cascade to
   `.section--navy p` (0,1,1) and rendered in the muted tone instead. */
.hero-tagline {
  font-family: var(--font-heading);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-body-lg);
  margin-top: var(--space-3);
}

/* ---------- MEDIA SPLIT: copy one side, photograph the other ----------
   Production builds three of its content bands as two-column containers and
   alternates which side the image sits on:

     container 32feb8b  image LEFT   "What Is Business Tax Planning?"
     container 2b95823  image RIGHT  "Offering Tailored ... Every Industry"
     container 421a025  image LEFT   "Why Strategic Tax Planning Matter"

   Copy comes first in the DOM by default; --media-first flips the visual order
   with `order` rather than by reordering the markup, so on mobile every section
   still reads heading-then-image regardless of its desktop side. */
.media-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-8);
  align-items: center;
}
.media-split__copy > h2:first-child { margin-top: 0; }
.media-split__copy > p + p { margin-top: var(--space-4); }
.media-split__copy .check-list { margin-top: var(--space-5); }
.media-split .photo-frame { aspect-ratio: 4 / 3; }

.media-split--media-first .photo-frame { order: -1; }

/* 60/40 split, operator layout 2026-08-06: heading and copy get the wider
   column, the photograph the narrower one.

   When the photograph sits on the LEFT (--media-first), the column widths have
   to swap too. `order` moves the items between grid cells but the cells keep
   their declared widths, so without this the image would land in the 60 percent
   cell and the copy in the 40 percent one. */
/* fr units, NOT percentages. This is load-bearing and it shipped wrong once.
   `grid-template-columns: 60% 40%` resolves to 100% of the container PLUS the
   column `gap`, so the track total always exceeds the container. At 1440 the
   container's max-width leaves enough slack to hide it; once the container goes
   fluid (roughly 901px to 1280px) the overflow becomes real and the whole
   DOCUMENT grows wider than the viewport.

   The visible symptom was not in the split at all: because the document became
   horizontally scrollable, the sticky header (100% of the VIEWPORT) and the
   footer no longer lined up with the content underneath, so the nav and footer
   looked broken on scroll on every re-skinned page while the homepage, which
   uses no media split, stayed clean. Measured across 19 widths on 5 pages.

   1.5fr / 1fr is the same 60/40 ratio, but fr distributes what is left AFTER
   the gap is subtracted, so the tracks can never overflow. */
.media-split--60-40 { grid-template-columns: 1.5fr 1fr; }
.media-split--60-40.media-split--media-first { grid-template-columns: 1fr 1.5fr; }
.media-split--60-40 .photo-frame { aspect-ratio: 1 / 1; }

/* ---------- INFO BOXES: a list rendered as small cards ----------
   Operator layout 2026-08-06: the two lists that used to sit inside the copy
   column now run full width beneath their section as small boxes.

   Still a <ul>/<li> underneath, so the list stays a list for assistive tech and
   the page's heading outline is untouched (1 H1, 7 H2, 11 H3). The <strong>
   lead-ins that production renders on the four-item benefit list are preserved
   as <strong>, never promoted to headings. */
.info-boxes {
  list-style: none;
  margin: var(--space-7) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-5);
}
.info-boxes > li {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--text-body);
}
.info-boxes .svc-icon { flex: 0 0 auto; width: 44px; height: 44px; }
.info-boxes .svc-icon .lucide { width: 22px; height: 22px; }
.info-boxes strong {
  font-family: var(--font-heading);
  font-weight: var(--fw-semibold);
  color: var(--navy-700);
  font-size: var(--fs-h4);
  line-height: var(--lh-subheading);
  display: block;
  margin-bottom: var(--space-2);
}

/* Compact variant for the eight short industry labels: four across, a bare
   check icon rather than the boxed one, and vertically centred since each label
   is a single line. */
.info-boxes--compact { grid-template-columns: repeat(4, 1fr); gap: var(--space-4); }
.info-boxes--compact > li { align-items: center; gap: var(--space-3); padding: var(--space-4) var(--space-5); }
.info-boxes--compact .lucide { flex: 0 0 auto; width: 20px; height: 20px; color: var(--success); }

/* Link variant: the same compact boxes, but each one is a navigation
   destination rather than a claim. Three across, because the lists that use it
   are six items (the /startups/ closing band), so four across would leave an
   orphaned pair on the second row.

   The arrow replaces the green check on purpose: a check reads as "we do this",
   an arrow reads as "go here", and these boxes are links. */
.info-boxes--links { grid-template-columns: repeat(3, 1fr); }
.info-boxes--links .lucide { color: var(--royal-600); }
.info-boxes--links a {
  font-family: var(--font-heading);
  font-weight: var(--fw-semibold);
  color: var(--navy-700);
  text-decoration: none;
}
.info-boxes--links a:hover { color: var(--royal-600); text-decoration: underline; }
/* The cards stay white on a navy band, so the link colour above is correct
   there too and must not inherit the band's inverse text colour. */
.section--navy .info-boxes--links a { color: var(--navy-700); }

/* ---------- Strategy cards: H3 plus paragraph ----------
   Production uses image-box widgets with title_size h3 and no image set, so
   these render as heading-plus-description cards. */
.strategy-card h3 { margin-bottom: var(--space-3); }
.strategy-card p { margin: 0; }

/* ---------- CTA bands built on a photograph ----------
   Production puts a background image behind both CTA bands (container 876b9ba
   and container 84f8c07). The band needs a positioning context and a raised
   content layer so the shared .band-bg and .hero-overlay sit behind the copy;
   both of those classes live in twi-site-chrome.css. */
.cta-band--photo { position: relative; overflow: hidden; }
.cta-band--photo > .container { position: relative; }

/* ---------- Any other content section built on a photograph ----------
   Same positioning context as .cta-band--photo, generalized for non-CTA
   sections (e.g. the homepage Integrity Tax Blueprint band) so .band-bg and
   .hero-overlay work there too without duplicating the CTA-specific rule. */
.section--photo { position: relative; overflow: hidden; }
.section--photo > .container { position: relative; }

/* Heavier overlay for .section--photo specifically (scoped override of the
   shared .hero-overlay, does not touch the hero or the five .cta-band--photo
   pages). The hero's copy sits only in a left-aligned 780px column, so its
   0.75-to-0.645 gradient deliberately eases off to the right, where there is
   no text. Here the six blueprint-step cards and the closing paragraph run
   the full container width, so that same easing left the right-hand cards
   sitting on the lightest, least-tested part of the gradient. Flattened to a
   near-uniform 0.90-to-0.85 band instead. Bumped 2026-08-11 after Edward
   flagged the body copy as hard to read against this (brighter, paper-heavy)
   photo; not yet re-measured pixel-by-pixel like the hero's documented 0.75,
   but 0.90 over near-white paper still resolves close to solid navy. */
.section--photo .hero-overlay {
  background: linear-gradient(
    100deg,
    rgba(2, 26, 98, 0.90) 0%,
    rgba(2, 26, 98, 0.88) 50%,
    rgba(2, 26, 98, 0.85) 100%
  );
}

/* ---------- Inline CTA under a card grid ---------- */
.hero-ctas--inline { margin-top: var(--space-7); margin-bottom: 0; }

@media (max-width: 1100px) {
  .info-boxes--compact { grid-template-columns: repeat(2, 1fr); }
  .info-boxes--links { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 900px) {
  /* Copy first, image second, on every split regardless of its desktop side.
     Resetting `order` is what makes that work without touching the markup. */
  .media-split,
  .media-split--60-40,
  .media-split--60-40.media-split--media-first { grid-template-columns: 1fr; gap: var(--space-7); }
  .media-split--media-first .photo-frame { order: 0; }
  .info-boxes { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
  .info-boxes--compact { grid-template-columns: 1fr; }
}

/* ---------- NOTICE BAND ----------
   A thin full-width strip carrying one line of copy and an internal link.
   Production adds one to /taxes/ (hand-built container `twi_taxes_md_link`,
   background #F0F4FA) pointing at the missed-deadline recovery guide, and the
   same pattern shows up on other pages in the set, so it lives in the shared
   layer rather than in one page's stylesheet.

   --royal-50 is the design system's nearest token to production's #F0F4FA. */
.notice-band {
  background: var(--royal-50);
  border-bottom: 1px solid var(--border-subtle);
  padding: var(--space-4) 0;
}
.notice-band p {
  margin: 0;
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--text-body);
}
.notice-band strong {
  font-family: var(--font-heading);
  font-weight: var(--fw-semibold);
  color: var(--navy-700);
}
.notice-band a { font-weight: var(--fw-medium); }

/* ============================================================
   THE 2025 PRODUCTION PAGE SHAPE
   /llc-formation-services/, /business-tax-planning-services-florida/ and
   /business-tax-services-florida/ are newer builds than the rest of the site and
   share one layout: photo hero, intro split, a six-card solutions grid, an
   optional six-card benefits grid on solid navy, a five-step process with
   graduating navy, testimonials, a numbered FAQ, and a closing band with two
   CTAs. Every rule below is used by all three, which is why it lives in the
   shared layer rather than in three page stylesheets.
   ============================================================ */

/* ---------- Accent rule under a section heading ----------
   Production draws this with an Elementor `divider` widget: 100px wide, 2px,
   centred, directly beneath the H2. It carries no text, so it is decorative and
   correctly invisible to assistive technology. */
.section-rule {
  width: 100px;
  height: 2px;
  margin: var(--space-4) auto var(--space-5);
  border: 0;
  background: var(--royal-500);
}
.section--navy .section-rule { background: var(--royal-300); }

/* Centred section intro: heading, rule, one paragraph of lead-in. */
.section-intro { text-align: center; max-width: 760px; margin: 0 auto var(--space-7); }
.section-intro p { margin: 0; }

/* ---------- Cards on a solid navy band ----------
   Production styles these icon-boxes as white at 22% alpha over #071B5C. A
   solid white card would fight the band, so the translucent treatment is kept
   and the text goes inverse.

   The border is what keeps the card readable: at 22% alpha over navy the fill
   alone does not separate the card from the band on a dim screen. */
.card--onnavy {
  background: rgba(255, 255, 255, 0.10);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: none;
}
.card--onnavy h3 { color: #fff; }
.card--onnavy p { color: var(--text-inverse-muted); margin: 0; }
.card--onnavy .lucide { color: var(--royal-300); width: 26px; height: 26px; margin-bottom: var(--space-3); }

/* ---------- Five-step process ladder ----------
   Production stacks five full-width containers whose backgrounds graduate from
   #1E2946 to #5671B7. The graduation is the whole point: it reads as progress
   through the steps, so the exact five values are carried across rather than
   flattened to one navy. Laid out as a row so the progression is visible at a
   glance instead of only on scroll.

   `align-content: start` and the flex column keep the five cards equal height
   with their copy top-aligned, which is what stops a one-line step from
   floating in the middle of a tall card. */
.process-steps {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--space-4);
}
.process-steps > li {
  list-style: none;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  color: #fff;
}
.process-steps > li:nth-child(1) { background: #1E2946; }
.process-steps > li:nth-child(2) { background: #2D3E69; }
.process-steps > li:nth-child(3) { background: #394E85; }
.process-steps > li:nth-child(4) { background: #455EA0; }
.process-steps > li:nth-child(5) { background: #5671B7; }
.process-steps h3 {
  color: #fff;
  font-size: var(--fs-h4);
  line-height: var(--lh-subheading);
  margin: 0 0 var(--space-3);
}
.process-steps p { color: rgba(255, 255, 255, 0.86); font-size: var(--fs-small); line-height: 1.6; margin: 0; }

/* ---------- Testimonials carried as display copy ----------
   Production renders these in a testimonial-carousel and marks them up with no
   Review or AggregateRating node. They stay display copy here for the same
   reason: a business publishing its own reviews as structured data is exactly
   what the self-serving-review policy prohibits. Do NOT add Review schema to
   these blocks. */
.review-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-5); align-items: start; }
.review-grid .quote { white-space: pre-line; }

/* ---------- Closing band with two CTAs side by side ---------- */
.cta-pair { display: flex; gap: var(--space-4); justify-content: center; flex-wrap: wrap; }

@media (max-width: 1100px) {
  .process-steps { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 900px) {
  .review-grid { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  .process-steps { grid-template-columns: 1fr; }
}

/* ============================================================
   FLUENT FORMS OVERRIDES
   Adapted from the wp-fluent-form-deploy skill's template and re-pointed at
   this site's design tokens, so a Fluent form renders as the design system's
   own .field / .control / .btn components rather than Fluent's defaults.

   Scoped to .form-shell (the wrapper every TWI template puts around a
   shortcode) rather than to a body class: three different pages embed a form
   and they do not share a body class, while every one of them uses this
   wrapper.

   Two lessons are baked in and must not be "simplified" away:
     1. The <fieldset> is the real flex parent of the field groups, NOT
        .frm-fluent-form. Putting the flex on the form leaves zero gap between
        fields and looks like the CSS never loaded.
     2. .ff-el-is-required / .asterisk-right append a "*" through ::after. The
        design system marks required state on the label text itself, so the
        pseudo-element is switched off rather than left to double up.
   ============================================================ */

.form-shell {
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-card);
}

.form-shell .fluentform,
.form-shell .ffs_default_wrap,
.form-shell .frm-fluent-form,
.form-shell .frm-fluent-form fieldset,
.form-shell .ff-el-form-control,
.form-shell .ff-el-group {
  background: transparent !important;
  padding: 0 !important;
  margin: 0 !important;
  border: 0 !important;
  box-shadow: none !important;
}
.form-shell .ff_screen_reader_title { display: none !important; }

.form-shell .frm-fluent-form fieldset {
  min-inline-size: 100% !important;
  display: flex !important;
  flex-direction: column !important;
  gap: var(--space-5) !important;
}

.form-shell .ff-t-cell,
.form-shell .ff-el-group .ff-el-input--label,
.form-shell .ff-el-group .ff-el-input--content {
  display: block !important;
  width: 100% !important;
  padding: 0 !important;
}

.form-shell .ff-el-input--label { margin: 0 0 var(--space-2) !important; }
.form-shell .ff-el-input--label label {
  font-family: var(--font-heading) !important;
  font-weight: var(--fw-semibold) !important;
  font-size: var(--fs-small) !important;
  color: var(--navy-700) !important;
  margin: 0 !important;
}
.form-shell .ff-el-input--label.asterisk-right label::after,
.form-shell .ff-el-input--label.ff-el-is-required label::after,
.form-shell .ff-el-is-required.asterisk-right::after {
  display: none !important;
  content: none !important;
}

.form-shell .frm-fluent-form input[type="text"],
.form-shell .frm-fluent-form input[type="email"],
.form-shell .frm-fluent-form input[type="tel"],
.form-shell .frm-fluent-form input[type="number"],
.form-shell .frm-fluent-form textarea,
.form-shell .frm-fluent-form select {
  width: 100% !important;
  font-family: var(--font-body) !important;
  font-size: var(--fs-body) !important;
  color: var(--ink) !important;
  background: #fff !important;
  border: 1.5px solid var(--border) !important;
  border-radius: var(--radius-md) !important;
  padding: 12px 14px !important;
  box-shadow: none !important;
  height: auto !important;
}
.form-shell .frm-fluent-form textarea { min-height: 108px !important; resize: vertical !important; }
.form-shell .frm-fluent-form ::placeholder { color: #98A4B5 !important; }
.form-shell .frm-fluent-form input:focus,
.form-shell .frm-fluent-form textarea:focus {
  outline: none !important;
  border-color: var(--royal-500) !important;
  box-shadow: var(--focus-shadow) !important;
}

/* The international phone widget renders its own wrapper; make it fill the row
   so the phone field does not sit narrower than every field around it. */
.form-shell .iti, .form-shell .iti__tel-input { width: 100% !important; }

/* Submit button: the design system's primary button, not Fluent's. */
.form-shell .ff-btn-submit,
.form-shell .ff_btn_style {
  font-family: var(--font-heading) !important;
  font-weight: var(--fw-semibold) !important;
  font-size: var(--fs-body) !important;
  background: var(--royal-600) !important;
  color: #fff !important;
  border: 0 !important;
  border-radius: var(--radius-md) !important;
  padding: 14px 26px !important;
  width: 100% !important;
  cursor: pointer !important;
  transition: background var(--transition-fast) !important;
}
.form-shell .ff-btn-submit:hover { background: var(--navy-700) !important; }

/* Validation and success states, so an error is legible rather than raw. */
.form-shell .ff-el-is-error input,
.form-shell .ff-el-is-error textarea { border-color: var(--error) !important; background: var(--error-bg) !important; }
.form-shell .text-danger, .form-shell .error { color: var(--error) !important; font-size: var(--fs-small) !important; }
.form-shell .ff-message-success {
  background: var(--success-bg) !important;
  color: var(--success) !important;
  border-radius: var(--radius-md) !important;
  padding: var(--space-5) !important;
}

/* Two-up rows for the name pair, which every one of these forms opens with. */
.form-shell .frm-fluent-form fieldset > .ff-el-group.twi-half { width: 100%; }
@media (min-width: 640px) {
  .form-shell .frm-fluent-form fieldset { flex-flow: row wrap !important; }
  .form-shell .frm-fluent-form fieldset > .ff-el-group { flex: 1 1 100%; }
  .form-shell .frm-fluent-form fieldset > .ff-el-group.twi-field:nth-child(1),
  .form-shell .frm-fluent-form fieldset > .ff-el-group.twi-field:nth-child(2) { flex: 1 1 calc(50% - var(--space-5) / 2); }
}

/* Contact page: copy column beside the form, form column narrower. */
.contact-split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-8); align-items: start; }
.contact-details { display: grid; gap: var(--space-5); margin-top: var(--space-6); }
@media (max-width: 900px) { .contact-split { grid-template-columns: 1fr; } }

/* Guide and thank-you: a single centred column, no second surface. */
.narrow-col { max-width: 640px; margin: 0 auto; }
.narrow-col .form-shell { margin-top: var(--space-6); text-align: left; }

/* ============================================================
   REVIEW FUNNEL
   /rate-your-service/ and the three star pages. Production builds these out of
   Elementor `spacer` widgets carrying a star image as a container background,
   and a `rating` widget. Both become real star icons here so they scale, stay
   crisp, and can be links.

   Star colour: this brand is blue with no gold anywhere
   (core-documents/brand-assets/BRAND-TOKENS.md), but an unfilled/filled star
   pair has to read as a rating control at a glance, and a navy star on white
   does not. --star-fill is therefore the one warm value on the site, scoped to
   this component only, never promoted to a token.
   ============================================================ */

.star-row {
  display: flex;
  justify-content: center;
  gap: var(--space-4);
  margin: var(--space-7) 0;
  flex-wrap: wrap;
}
.star-row .star {
  display: inline-grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border-radius: var(--radius-md);
  color: #D8DEE9;
  background: transparent;
  border: 0;
}
.star-row .star .lucide { width: 44px; height: 44px; fill: currentColor; stroke: none; }
.star-row .star.is-filled { color: #E8A33D; }

/* Only the linked stars get affordance. The unlinked ones are deliberately
   inert: see the template comment on /rate-your-service/. */
a.star {
  text-decoration: none;
  transition: transform var(--transition-fast), color var(--transition-fast);
}
a.star:hover, a.star:focus-visible { color: #E8A33D; transform: translateY(-2px); }
a.star:focus-visible { outline: 2px solid var(--royal-500); outline-offset: 2px; }

.review-page { max-width: 720px; margin: 0 auto; text-align: center; }
.review-page .form-shell { text-align: left; margin-top: var(--space-6); }
.review-page .sign-off { margin-top: var(--space-7); color: var(--text-muted); }

@media (max-width: 560px) {
  .star-row { gap: var(--space-2); }
  .star-row .star { width: 52px; height: 52px; }
  .star-row .star .lucide { width: 34px; height: 34px; }
}

/* ==================================================================
   BLOG ARTICLE BODY
   ==================================================================
   Added 2026-08-07 with the blog. The design system already ships
   .layout-post, .blog-grid and .post-card; these are the three things the
   one article carried over from production actually needs and the system
   did not have. All three are NEW class names, so nothing shipped before
   this date can be affected by them.

   The article is a single readable column (.layout-post caps at 760px), so
   these all inherit that measure rather than setting their own. */

/* The post opens with a self-hosted video. It is a real <video> rather than
   an iframe because production self-hosts the file, and `preload="metadata"`
   so an 18 MB QuickTime does not download itself on page load just to draw a
   poster frame. */
.layout-post .post-video { margin: var(--space-6) 0; }
.layout-post .post-video video {
  width: 100%; height: auto; display: block;
  border-radius: var(--radius-lg);
  background: var(--navy-700);
  box-shadow: var(--shadow-card);
}

/* Pull quote for the case-study paragraph.
   Deliberately NOT a left accent border: rules/no-left-accent-border.md
   forbids a border-left stripe on any container and names blockquotes
   specifically. The accent is a top bar in TWI's OWN brand gradient
   (--wash-royal, the navy-to-royal ramp from the logo), not Sozo's. */
.layout-post .post-quote {
  position: relative;
  margin: var(--space-6) 0;
  padding: var(--space-6);
  background: var(--surface-section);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.layout-post .post-quote::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: var(--wash-royal);
}
.layout-post .post-quote p { margin: 0; font-size: var(--fs-body-lg); color: var(--ink); }
.layout-post .post-quote p + p { margin-top: var(--space-3); }

/* "Read the post" / "Back to all posts". The arrow sits on the baseline with
   the text and slides on hover, which is the only motion on the card. */
.post-more { margin-top: auto; padding-top: var(--space-4); }
.post-more a {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-weight: var(--fw-semibold); font-size: var(--fs-small);
}
.post-more .lucide { width: 16px; height: 16px; transition: transform var(--transition-base); }
.post-more a:hover .lucide { transform: translateX(3px); }

/* The archive card's title is a link; it should read as a heading, not as a
   run of link-blue text. */
.post-card h2, .post-card h3 { font-size: var(--fs-h4); margin-bottom: var(--space-3); }
.post-card h2 a, .post-card h3 a { color: inherit; text-decoration: none; }
.post-card h2 a:hover, .post-card h3 a:hover { color: var(--royal-600); }

/* ---------- Ghost buttons on EVERY dark surface, not just .section--navy ----------
   Added 2026-08-07 after the operator spotted the "Text us" button in the blog
   post's CTA box rendering navy-on-navy, i.e. invisible.

   Root cause: the design system lights up ghost buttons with
   `.section--navy .btn--ghost { color: #fff }`, scoped to that one class. But
   --wash-navy is applied by FOUR selectors, and the other three get no such
   treatment, so any ghost button dropped inside one inherits the light-surface
   default of `color: var(--navy-700)` on a navy gradient:

     .section--navy            <- has the rule
     .layout-post .post-cta    <- did not (this is the bug that shipped)
     .compare .compare-col--brand
     .service-card--feature

   Fixing only .post-cta would have left the same trap armed for the other two,
   so all four are covered here. Verified this is the whole set by grepping every
   `var(--wash-navy)` usage in both stylesheets, and verified the bug itself was
   isolated by sweeping all 28 live URLs for any control whose text luminance is
   dark while its nearest painted background is dark: exactly one hit, this one.

   `.promise` and `.contact-line` get the same treatment, because the design
   system also scopes those to `.section--navy` and `.site-footer` only, and the
   post CTA is the kind of block that will grow one. */
.post-cta .btn--ghost,
.compare-col--brand .btn--ghost,
.service-card--feature .btn--ghost {
  color: #fff;
  border-color: rgba(255, 255, 255, 0.35);
}
.post-cta .btn--ghost:hover,
.compare-col--brand .btn--ghost:hover,
.service-card--feature .btn--ghost:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: #fff;
}
.post-cta .promise,
.post-cta .contact-line { color: #fff; }

/* ==================================================================
   HEADER: SERVICES DROPDOWN
   ==================================================================
   Added 2026-08-07. The design system's navigation component has no dropdown
   specimen, so this is authored here rather than forked out of it.

   Why a dropdown at all: staging now has eleven service pages plus /about/, and
   eleven top-level nav items do not fit. The header already drops its ghost CTA
   at 1040px and shrinks the nav font at 1180px just to hold seven labels on one
   line. Grouping the services takes the top level from seven items to five. */

.site-header .nav-item { position: relative; display: flex; align-items: center; }

/* The trigger is a real <button> so it is keyboard-operable and announces its
   state. It has to look exactly like a nav link, and the design system styles
   `.site-header nav a` only, so every inherited button default is reset here. */
.site-header .nav-trigger {
  display: inline-flex; align-items: center; gap: 4px;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  font-family: var(--font-heading);
  font-weight: var(--fw-medium);
  font-size: var(--fs-small);
  color: var(--slate-600);
  line-height: inherit;
}
.site-header .nav-trigger:hover { color: var(--navy-700); }
.site-header .nav-trigger:focus-visible { outline: none; box-shadow: var(--focus-shadow); border-radius: var(--radius-sm); }
.site-header .nav-trigger .lucide { width: 14px; height: 14px; transition: transform var(--transition-base); }
.site-header .nav-item[data-open="true"] .nav-trigger .lucide { transform: rotate(180deg); }

.site-header .nav-menu {
  position: absolute; top: 100%; left: 0; z-index: 60;
  min-width: 290px;
  display: none;
  flex-direction: column;
  /* --surface-card, NOT --surface. There is no `--surface` token in the design
     system (the surfaces are --surface-page / -card / -section / -navy /
     -navy-deep / -tint), so `var(--surface)` resolved to nothing and this panel
     shipped fully TRANSPARENT with the page showing through it. Nothing caught
     it: the geometry assertions passed because elementFromPoint hits a
     transparent element just the same. An undefined custom property fails in
     silence, which is why qa-tokens.js now checks for them. */
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  padding: var(--space-3);
  /* A small overlap with the trigger row, so a diagonal mouse move from the
     label into the panel does not cross a dead gap and close it. */
  margin-top: 6px;
}
.site-header .nav-menu a {
  display: block;
  padding: 9px var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--fs-small);
  color: var(--slate-600);
  white-space: nowrap;
}
.site-header .nav-menu a:hover,
.site-header .nav-menu a:focus-visible { background: var(--surface-section); color: var(--navy-700); }

/* Open on hover, on keyboard focus anywhere inside, and when the script has set
   the state. All three, because each covers a case the others do not: hover for
   a mouse, focus-within for tabbing, the attribute for click and touch. */
@media (min-width: 901px) {
  .site-header .nav-item--has-menu:hover .nav-menu,
  .site-header .nav-item--has-menu:focus-within .nav-menu,
  .site-header .nav-item[data-open="true"] .nav-menu { display: flex; }

  /* An invisible bridge across the margin-top gap. Without it the panel closes
     when the pointer crosses the 6px of nothing between label and panel. */
  .site-header .nav-item--has-menu::after {
    content: ""; position: absolute; top: 100%; left: 0; right: 0; height: 10px;
  }
}

/* In the drawer the panel is not a floating layer, it is an indented block that
   collapses. Ten extra rows in a list the user has to scroll past is worse than
   one extra tap, so it starts closed. */
@media (max-width: 900px) {
  .site-header .nav-item { display: block; }
  .site-header .nav-trigger {
    width: 100%; justify-content: space-between;
    padding: var(--space-3) 0;
    font-size: var(--fs-body);
    border-bottom: 1px solid var(--border-subtle);
    touch-action: manipulation;
  }
  .site-header .nav-menu {
    position: static; min-width: 0; display: flex;
    border: 0; border-radius: 0; box-shadow: none;
    padding: 0 0 0 var(--space-4);
    margin-top: 0;
    background: none;
    max-height: 0; opacity: 0; overflow: hidden;
    visibility: hidden; pointer-events: none;
    transition:
      max-height 220ms cubic-bezier(0.22, 1, 0.36, 1),
      opacity 140ms ease,
      visibility 0s linear 220ms,
      padding-bottom 220ms cubic-bezier(0.22, 1, 0.36, 1);
  }
  .site-header .nav-item[data-open="true"] .nav-menu {
    max-height: 620px; opacity: 1; visibility: visible;
    pointer-events: auto; padding-bottom: var(--space-2);
    transition-delay: 0s;
  }
  .site-header .nav-menu a {
    padding: 10px 0; border-radius: 0;
    border-bottom: 1px solid var(--border-subtle);
    white-space: normal;
  }
  .site-header .nav-menu a:last-child { border-bottom: none; }
}


/* Keep full-bleed trust cards generous without stretching across the viewport.
   Trust grids already inside .container retain that established content width. */
.trust-bar > .trust-grid {
  box-sizing: border-box;
  width: 85%;
  max-width: 1600px;
  margin-inline: auto;
  padding-inline: 0;
}
@media (max-width: 900px) {
  .trust-bar > .trust-grid { width: calc(100% - 48px); }
}
.trust-bar .trust-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(20px, 2vw, 32px);
}
.trust-bar .trust-badge {
  min-width: 0;
  gap: 16px;
  padding: clamp(24px, 2vw, 36px);
}
.trust-bar .trust-badge span { min-width: 0; line-height: 1.65; }
@media (min-width: 1500px) {
  .trust-bar .trust-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .trust-bar .trust-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Mobile header: visible Schedule button and drawer contact block ----------
   Added 2026-10-08. Below 900px the header used to drop every CTA and keep only
   the menu button, so phone visitors had no visible way to book. The Schedule
   button now stays in the bar beside the menu (shortened to "Schedule" where
   the full label does not fit), and the drawer ends with call and text actions,
   because the sticky call bar hides while the drawer is open. */
.header-cta-short,
.site-header nav .nav-cta { display: none; }

@media (max-width: 900px) {
  .site-header .bar { gap: var(--space-3); }
  .site-header .header-actions { display: flex; order: 2; margin-left: auto; gap: 0; }
  .nav-toggle { margin-left: 0; }
  .site-header .header-actions .header-cta {
    min-height: 42px; padding: 0 14px; gap: 6px; font-size: 14px;
  }
  .site-header .header-actions .header-cta .lucide { width: 16px; height: 16px; }

  .site-header nav .nav-cta {
    display: flex; flex-direction: column; gap: var(--space-3);
    margin-top: auto; padding-top: var(--space-5);
  }
  .site-header nav .nav-cta-note {
    margin: 0; font-family: var(--font-heading); font-weight: var(--fw-semibold);
    font-size: var(--fs-small); color: var(--navy-700);
  }
  .site-header nav .nav-cta-buttons { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }
  /* `.site-header nav a` gives every drawer link row padding, a body-size font,
     a slate color and a bottom rule; these buttons and the closing link reset that. */
  .site-header nav .nav-cta .btn {
    padding: 13px 16px; font-size: var(--fs-small);
    border: 1.5px solid transparent; justify-content: center;
  }
  .site-header nav .nav-cta .btn--primary { color: var(--accent-contrast); }
  .site-header nav .nav-cta .btn--secondary { color: #fff; }
  .site-header nav .nav-cta .nav-cta-link {
    display: inline-flex; align-items: center; gap: var(--space-2);
    margin-top: var(--space-2); padding: var(--space-4) 0 0;
    border-top: 1px solid var(--border-subtle); border-bottom: none;
    font-size: var(--fs-small); color: var(--slate-600);
  }
  .site-header nav .nav-cta .nav-cta-link:hover { color: var(--navy-700); }
  .site-header nav .nav-cta .nav-cta-link .lucide { width: 16px; height: 16px; }
}

@media (max-width: 359px) {
  .header-cta-full { display: none; }
  .header-cta-short { display: inline; }
}
