/* ============================================================
   BowerVault Home - page layout.
   Section-by-section translation of the approved Claude Design
   prototype (project/BowerVault Home.dc.html) into a real
   stylesheet: same spacing, type and colour values, driven off
   the design tokens rather than repeated inline styles.
   ============================================================ */

.page { min-height: 100%; background: var(--surface-card); }
.container { width: 100%; max-width: var(--container-lg); margin: 0 auto; }

/* ---- Navbar ---------------------------------------------------- */
.navbar {
  padding: 20px var(--page-padding);
  display: flex;
  align-items: center;
  /* Transparent at rest, same as [data-hero-nav] header in the Home
     redesign's BowerVault Home.dc.html - so every hero's own gradient
     (.hero::before above) shows straight through behind the logo/nav
     instead of stopping dead at the header's edge. Solid again once
     .is-scrolled kicks in below, once there's no hero left showing
     through it to matter. */
  background: transparent;
  position: sticky;
  top: 0;
  z-index: 20;
  /* Pulls the hero section up to fill the space directly behind the
     header instead of leaving a gap for the (now-transparent) navbar
     box to sit in front of nothing. -84px is this header's own
     unscrolled height (44px logo + 20px padding top/bottom). At
     ≤767px, .is-scrolled's own reduced padding shrinks the navbar's
     real rendered height to 68px (see below) - margin-bottom shrinks
     to match there (-68px) so the two always cancel out to the same
     net 0, whatever the current padding is. Getting this wrong (a
     fixed -84px regardless of the navbar's actual height) was a real,
     confirmed bug (26 Sep 2026): the mismatch meant toggling
     .is-scrolled genuinely moved the page content by however many
     pixels the navbar's height had just changed by - a sticky element
     above the viewport changing height is exactly what Chrome's own
     scroll anchoring compensates for, so it nudged window.scrollY to
     match, which could re-trigger the scroll listener and flip
     .is-scrolled again, right at the boundary where scrollY sits close
     to the 4px threshold - visible as the page glitching/jumping on
     its own if a visitor stopped scrolling right there. .hero's own
     padding-top adds the unscrolled 84px back (see below) so hero
     content still clears the header by its usual gap rather than
     sitting flush beneath it. */
  margin-bottom: -84px;
  /* padding and margin-bottom only actually change on scroll at mobile
     widths (see the ≤767px block below) but are listed here rather
     than there so it's one shared transition declaration, not two
     near-duplicates. Both transition together, same duration/easing,
     so the pair keeps summing to 0 at every point *during* the
     transition too, not just at its two resting ends. */
  transition: background var(--duration-header) var(--ease-standard),
              backdrop-filter var(--duration-header) var(--ease-standard),
              box-shadow var(--duration-header) var(--ease-standard),
              padding var(--duration-header) var(--ease-standard),
              margin-bottom var(--duration-header) var(--ease-standard);
}
/* Frosted glass once scrolled: a translucent version of the header's own
   background colour (not a generic dark tint) so text contrast holds up
   regardless of what's scrolling underneath, plus a blur so busy content
   behind it reads as soft shapes, not legible detail competing with the
   nav. The bottom shadow is what actually separates it from the page
   now that it's translucent instead of a flat fill. */
.navbar.is-scrolled {
  background: rgba(107, 119, 132, 0.72);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.1), 0 12px 30px rgba(12, 8, 1, 0.15);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  /* No backdrop-filter support: fall back to a solid (not translucent)
     version of the same colour rather than a half-see-through header
     with no blur to soften what shows through it. */
  .navbar.is-scrolled { background: var(--surface-page); }
}
.navbar__inner { display: flex; align-items: center; gap: 30px; width: 100%; }
.navbar__logo-link {
  position: relative;
  display: block;
  width: 217px;
  height: 44px;
  text-decoration: none;
  transform-origin: left center;
  transition: transform var(--duration-header) var(--ease-standard);
}
/* Shrinks a little once the header is sticky/scrolled - scaling the
   whole link (bv-logo included) rather than juggling the custom
   element's own height attribute, so the strapline fading out below
   doesn't leave the logo needing a separate resize pass. */
.navbar.is-scrolled .navbar__logo-link { transform: scale(0.82); }
.navbar__logo { height: 44px; }
.navbar__strapline {
  position: absolute;
  top: calc(100% + 4px);
  left: 13.6px;
  width: 203px;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 9px;
  line-height: 1.2;
  white-space: nowrap;
  color: #fff;
  transition: opacity var(--duration-header) var(--ease-standard);
}
.navbar.is-scrolled .navbar__strapline { opacity: 0; }
.nav-links { margin-left: auto; display: flex; align-items: center; gap: 24px; white-space: nowrap; }
.nav-links a {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.5;
  color: var(--text-on-dark);
  text-decoration: none;
}
/* No hover colour change - stays plain white; the keyline below is the
   only hover feedback. Needed explicitly because the generic a:hover
   rule in tokens/base.css otherwise tints it navy on hover (it only
   excludes .bv-btn, and these are plain links, not buttons). */
.nav-links a:hover { color: var(--text-on-dark); }
/* Every nav item is plain white, current page or not - what marks the
   current page out is a permanent 1px keyline underneath instead of a
   text-colour change. Absolutely positioned rather than a border/padding
   so it doesn't touch the link's box size. Other items get the same
   keyline on hover, drawn in left-to-right via a scaleX transform with a
   left transform-origin (and retracts the same way on hover out). */
.nav-links a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 1px;
  background: var(--text-on-dark);
  transform: scaleX(0);
  transform-origin: left;
  /* Deliberately slower than --duration-base (200ms, used for hover
     states elsewhere like button/card lifts) - the whole point here is
     a keyline that visibly draws across rather than just snapping in. */
  transition: transform 500ms var(--ease-standard);
}
.nav-links a:not(.is-muted)::after { transform: scaleX(1); }
.nav-links a.is-muted:hover::after,
.nav-links a.is-muted:focus-visible::after {
  transform: scaleX(1);
}
/* ---- Features dropdown (desktop) -------------------------------------
   The only dropdown in the nav - Features gains one child, Under the
   Surface, rather than becoming a second flat top-nav item. Matches
   the Claude Design header prototype's own selected variants exactly
   (dropdown="Described card", chevron="Gentle tilt", hover="Soft
   tint") rather than an invented pattern - see initFeaturesDropdown in
   main.js for the open/close timing (90ms hover-open / 260ms
   hover-close delay, plus the toggle button and Escape) that drives
   the .is-open class below. The trigger link behaves exactly like
   every other nav-links <a> (same keyline, same is-muted convention,
   driven by nav="features" on both /features and /under-the-surface:
   see build.js); the toggle button and panel are additions next to
   it, not a replacement for it, so the link still works as a plain
   link to /features on its own. */
.nav-item { position: relative; display: flex; align-items: center; gap: 4px; }
/* Sized to the icon exactly (no extra hit-area padding, unlike the
   44px mobile equivalent) - at 24x24 around a 16px icon, the 4px
   inset read as part of the gap to the next nav item rather than part
   of this one, making the Features-to-toggle gap (the flex gap alone)
   look tight while the toggle-to-next-item gap looked ~4px wider than
   every other item-to-item gap despite the flex gap being identical:
   nav items read as uneven rather than equidistant. */
.nav-item__toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-on-dark);
  cursor: pointer;
}
.nav-item__toggle .icon { width: 16px; height: 16px; transition: transform 900ms cubic-bezier(0.45, 0, 0.2, 1); }
.nav-item.is-open .nav-item__toggle .icon { transform: rotate(180deg); }
/* padding-top rather than margin-top: extends the hoverable area
   right up to the trigger row so the mouse can cross the gap between
   them without the hover-close timer firing. */
.nav-item__panel {
  position: absolute;
  top: 100%;
  left: -16px;
  padding-top: 14px;
  width: 320px;
  z-index: 60;
  /* .nav-links sets white-space: nowrap for its own single-line links
     (see above) - inherited property, so it was also forcing this
     panel's wrapping description onto one line and pushing it past
     the card's edge instead. Reset at the panel boundary. */
  white-space: normal;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity 420ms cubic-bezier(0.22, 0.61, 0.36, 1), transform 520ms cubic-bezier(0.22, 0.61, 0.36, 1), visibility 0s linear 420ms;
}
.nav-item.is-open .nav-item__panel {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
  transition: opacity 420ms cubic-bezier(0.22, 0.61, 0.36, 1), transform 520ms cubic-bezier(0.22, 0.61, 0.36, 1), visibility 0s linear 0s;
}
/* White card floating over the page rather than a toned section, so it
   takes a shadow instead of a tone change to read as raised - the
   "shadow only when white sits on white" case in CLAUDE.md, just
   against the page behind it instead of another white surface. */
.nav-item__card { border-radius: 10px; overflow: hidden; background: var(--surface-card); box-shadow: 0 24px 60px rgba(12, 8, 1, 0.18); }
.nav-item__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 20px;
  text-decoration: none;
  background: rgba(44, 62, 80, 0);
  transition: background-color 600ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.nav-item__row:hover,
.nav-item__row:focus-visible { background: rgba(44, 62, 80, 0.05); }
/* min-width: 0 overrides the flex item's default min-width: auto
   (sized to its longest word otherwise), which was pushing the
   description text past the card's edge instead of wrapping. */
.nav-item__row-text { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.nav-item__row-title { font-family: var(--font-ui); font-size: 15px; font-weight: 500; line-height: 1.4; color: var(--text-primary); }
.nav-item__row.is-current .nav-item__row-title { font-weight: 600; color: var(--color-pickled-bluewood); }
.nav-item__row-desc { font-family: var(--font-body); font-size: 14px; line-height: 1.45; color: rgba(12, 8, 1, 0.72); }
.nav-item__row-arrow { flex-shrink: 0; margin-top: 2px; color: var(--text-primary); transition: transform 700ms cubic-bezier(0.22, 0.61, 0.36, 1); }
.nav-item__row:hover .nav-item__row-arrow,
.nav-item__row:focus-visible .nav-item__row-arrow { transform: translateX(4px); }
.nav-actions { display: flex; align-items: center; gap: 16px; }
/* Hidden by default (only ever visible ≤1060px, see media query below);
   a plain 3-bar icon built from spans rather than an inline SVG, and a
   full 44px box - a touch target, not just a hover target. */
.nav-toggle {
  display: none;
  margin-left: auto;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-on-dark);
  cursor: pointer;
  /* Only actually moves at mobile widths, re-centring against the
     shrunk header on scroll (see the ≤767px block below) - transition
     lives here so it's covered by --duration-header the same as the
     rest of that shrink, not a separate one-off duration. */
  transition: transform var(--duration-header) var(--ease-standard);
}
.nav-toggle__bars { display: flex; flex-direction: column; gap: 6px; width: 24px; }
.nav-toggle__bar { display: block; height: 1.5px; background: currentColor; }

/* ---- Mobile menu: slide-out panel from the right ------------------
   Sits outside .navbar (siblings in the DOM, both fixed-positioned) so
   its own stacking/animation is independent of the header's. Only ever
   shown ≤1060px - see the media query below, which also hides .nav-toggle
   above 1060px. */
.mobile-menu-scrim {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(12, 8, 1, 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 480ms var(--ease-standard);
}
.mobile-menu-scrim.is-open { opacity: 1; pointer-events: auto; }
.mobile-menu {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 100;
  width: min(420px, 100%);
  display: flex;
  flex-direction: column;
  background: var(--surface-page);
  color: var(--text-on-dark);
  box-shadow: -24px 0 60px rgba(12, 8, 1, 0.35);
  /* translateX(100%) resolves against this element's OWN width (420px),
     not the viewport - so at rest its left edge sits exactly at the
     viewport's right edge, not safely further off past it. The 60px
     blur radius on the box-shadow above extends well past its -24px
     offset in both directions, so ~84px of blurred shadow was bleeding
     back into the visible viewport at all times, on every page (this
     partial is shared) - invisible over the dark hero (same near-black
     shadow colour), visible as an unexplained shadow over every lighter
     section below it. visibility:hidden suppresses painting (shadow
     included) while closed; transitioned at the same 620ms as the
     transform so the close animation still gets to play out before it
     actually disappears (per spec, a visibility transition to "hidden"
     applies at the end of its duration, to "visible" at the start:
     exactly the "stay visible through the exit animation, hide the
     instant the enter animation should begin" behaviour this needs). */
  visibility: hidden;
  transform: translateX(100%);
  transition: transform 620ms var(--ease-standard), visibility 620ms;
}
.mobile-menu.is-open { visibility: visible; transform: translateX(0); }
.mobile-menu__head {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 20px;
}
.mobile-menu__logo-link { position: relative; display: block; width: 180px; height: 36px; text-decoration: none; }
.mobile-menu__logo { height: 36px; }
.mobile-menu__strapline {
  position: absolute;
  top: calc(100% + 3px);
  left: 11.3px;
  width: 169px;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 7.5px;
  line-height: 1.2;
  white-space: nowrap;
  color: #fff;
}
.mobile-menu__close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.12);
  color: var(--text-on-dark);
  cursor: pointer;
}
.mobile-menu__nav { flex: 1; overflow-y: auto; padding: 24px 20px 0; display: flex; flex-direction: column; }
/* > rather than a plain descendant selector - The Vault's own link and
   Mortgage Readiness Report's child link (below) are real <a> tags too,
   but nested inside .mobile-menu__item/.mobile-menu__children wrappers
   now, not direct children of .mobile-menu__nav, so they get their own
   dedicated rules below instead of double-applying this one. */
.mobile-menu__nav > a {
  display: flex;
  align-items: center;
  min-height: 52px;
  padding: 12px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.18);
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.4;
  color: var(--text-on-dark);
  text-decoration: none;
}
/* ---- The Vault's nested child (Mortgage Readiness Report) -------------
   A parent row (link + a separate chevron toggle button, not the link
   itself) and a collapsible list underneath, closed by default. Chevron
   points right rather than rotating open/closed like the FAQ accordion's
   does - reads as "drill in" rather than "expand in place", which suits
   a single child better than a full accordion treatment would. */
.mobile-menu__item {
  display: flex;
  align-items: center;
  min-height: 52px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.18);
}
.mobile-menu__item .mobile-menu__link {
  flex: 1;
  display: flex;
  align-items: center;
  min-height: 52px;
  padding: 12px 0;
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.4;
  color: var(--text-on-dark);
  text-decoration: none;
}
.mobile-menu__expand {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-on-dark-muted);
  cursor: pointer;
  transition: color var(--duration-base) var(--ease-standard);
}
.mobile-menu__expand:hover,
.mobile-menu__expand:focus-visible,
.mobile-menu__expand[aria-expanded="true"] {
  color: var(--text-on-dark);
}
.mobile-menu__expand .icon { width: 20px; height: 20px; }
/* Same grid-template-rows 0fr->1fr trick as .field-reveal in
   components.css (so height:auto can transition) - a hardcoded
   duration rather than --duration-base since this is functional
   navigation, not a decorative flourish, so it stays animated even
   below the 768px width where --duration-base is otherwise zeroed
   (see page.css's own mobile animation-off block). */
.mobile-menu__children {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows 320ms var(--ease-standard);
}
.mobile-menu__children.is-open { grid-template-rows: 1fr; }
.mobile-menu__children > div { min-height: 0; overflow: hidden; }
.mobile-menu__child {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: 10px 0 10px 20px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.4;
  /* Not --text-on-dark-muted (60% white): against this panel's own
     background (--surface-page, rgb(107,119,132)) that composites to
     ~2.7:1, well under WCAG AA's 4.5:1 for normal text. Full white
     clears it at ~4.57:1. The smaller font size and left indent already
     carry the "nested sub-item" hierarchy this muted colour was for, so
     nothing is lost by making it solid. */
  color: var(--text-on-dark);
  text-decoration: none;
}
/* Same dark-on-dark problem as the .bv-btn exclusion in tokens/base.css
   (see the note there): the generic a:not(.bv-btn):hover rule darkens
   link text to --color-pickled-bluewood on hover, which on this panel's
   own dark background is nearly invisible. These rows shouldn't change
   colour on hover at all, so each is restated at its own resting colour.
   .mobile-menu__child needs the extra .mobile-menu__children ancestor
   class to outrank the generic rule's specificity (a:not(.bv-btn):hover
   beats a bare .mobile-menu__child:hover on type-selector count) - the
   other two already win on source order alone since their specificity
   ties with the generic rule and this block loads after base.css. */
.mobile-menu__nav > a:hover,
.mobile-menu__item .mobile-menu__link:hover,
.mobile-menu__children .mobile-menu__child:hover { color: var(--text-on-dark); }
/* ---- Mobile menu: current-page marking -------------------------------
   Which row is "current" wasn't distinguishable at all - every row read
   identically regardless of which page you were actually on. Bold text
   plus the row's own divider brightened to a full-opacity 2px line (via
   box-shadow, which paints without affecting box size/alignment, same
   reasoning as the desktop nav's own absolutely-positioned keyline)
   marks it out without disturbing the row layout. is-current is applied
   server-side by build.js from each page's nav="..."/child="..." include
   directive - see build.js and partials/header.html. */
.mobile-menu__nav > a.is-current,
.mobile-menu__item.is-current .mobile-menu__link,
.mobile-menu__child.is-current { font-weight: 600; color: var(--text-on-dark); }
.mobile-menu__nav > a.is-current,
.mobile-menu__item.is-current,
.mobile-menu__child.is-current { box-shadow: inset 0 -2px 0 0 var(--text-on-dark); }

.mobile-menu__actions { flex-shrink: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 20px; }
/* One-off padding/height for just these two - taller and centred rather
   than the standard .bv-btn sizing, to read as full-width tap targets. */
.mobile-menu__actions .bv-btn { width: 100%; justify-content: center; padding: 13px 16px; }
/* Locks background scroll while the panel is open (toggled in JS). */
html.menu-open { overflow: hidden; }

/* ---- Generic section shell -------------------------------------- */
.section {
  padding: var(--section-padding-lg) var(--page-padding);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-20);
}
/* .section's own display:flex would otherwise beat the browser's
   default [hidden] { display: none } (an author rule always outranks a
   user-agent one) - same gotcha already handled per-component for
   .bio-modal/.uts-modal/.vault-card/.form-error, needed here too for
   temporarily hiding a whole section (e.g. a testimonials section with
   no real testimonial yet) without deleting it. */
.section[hidden] { display: none; }
/* Every card/panel on the site takes its fill from --card-bg rather
   than naming a colour itself - nothing is outlined any more, so a card is
   only legible if its tone differs from the ground it happens to land
   on, and the same component appears on several different grounds
   (.vault-card on mist here and on white over on /vault, .plan-panel
   on mist on Home and on white on /pricing). Letting the section
   declare the pair means each tone flips them once, in one place,
   instead of every component carrying a per-section exception.

   Default (canvas / mint / slate - a toned ground): white cards. On
   white the ladder has nowhere up to go, so the card drops to the
   F9F9F9 block tone instead. Either way the card and the ground it
   lands on always differ, which is the whole trick - see the surfaces
   note in tokens/semantic.css.

   A new card component needs no rule here: use var(--card-bg). */
.section,
.cta {
  --card-bg: var(--surface-card);
}
.section--slate { background: var(--color-scheme-1-background); }
/* The canvas replaces the old --mist (the .fig's rgb(233,235,237)),
   retuned to the app's own #F2F2F2 so the two products read as one
   surface. Every section that carries cards uses it: white cards on a
   toned ground is the whole model, and a white card on a white
   section has nothing left to distinguish it but the outline we just
   removed. Sections of plain copy still go white, which is what keeps
   the page banded. */
.section--canvas { background: var(--surface-canvas); }
/* Kept as a backstop rather than a layout the site uses: no card sits
   on a white section today (they're all --canvas), but if one ever
   does, it drops to the block tone instead of vanishing. */
.section--white {
  background: var(--color-scheme-3-background);
  --card-bg: var(--surface-block);
}
.section--mint { background: var(--color-scheme-4-background); }
/* Solid navy. Deeper than --slate, used where a section should read as
   a block of its own rather than another tonal band. */
.section--navy { background: var(--color-pickled-bluewood); }

/* Type on the two dark grounds. Scoped to the section modifier rather
   than set per element, so anything dropped into one inherits it:
   and higher specificity than the .vault-h2 / .tick-list rules in the
   later-loading page stylesheets, which would otherwise win on source
   order (see the cascade gotcha below). Per the design system's
   standing note, an eyebrow on a dark ground is white at 72%. */
.section--slate .vault-h2,
.section--navy .vault-h2 { color: #fff; }
.section--slate .split__copy p,
.section--navy .split__copy p { color: #fff; }
.section--slate .tick-list li,
.section--navy .tick-list li { color: #fff; }
/* Full white, not a softened opacity - .section--slate's own background
   (#6B7784, --color-scheme-1-background) is a mid-tone slate, not a true
   dark navy, so even ~99% white barely clears 4.5:1 against it. Any
   softer opacity fails WCAG AA contrast outright. */
.section--slate .tagline,
.section--navy .tagline { color: #fff; }

.section-header {
  max-width: var(--max-width-lg);
  display: flex;
  flex-direction: column;
  gap: 16px;
  align-items: center;
  text-align: center;
}
/* Left-aligned variant of .section-header - that class has no explicit
   width, so a centered flex parent (.section) centers the shrink-to-fit
   text box instead of pinning it to the same left edge as a full-width
   sibling like .process-grid/.addon-grid/.shot-notes. width:100% fixes
   that; align/text-align flip it from centered to left. Lived in
   how-it-works.css until Features needed it too - three pages deep is
   shared layout, not one page's own. */
.section-header--left {
  width: 100%;
  max-width: var(--container-lg);
  align-items: flex-start;
  text-align: left;
}

.tagline {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 600;
  line-height: 1.5;
  color: var(--text-secondary);
  margin-bottom: -8px;
}
/* A couple of taglines were knocked back further than --text-secondary
   (60%) during design review, down to the lowest opacity that still
   clears WCAG AA for normal text (~58%, ~4.6:1 on white/mist). */
.tagline--muted { color: rgba(12, 8, 1, 0.58); }
.section-header h2,
.split__heading {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: var(--text-h2);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  text-wrap: pretty;
}
.section-header p {
  margin: 0;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  color: var(--text-primary);
  text-wrap: pretty;
}

/* ---- Hero -------------------------------------------------------- */
.hero {
  position: relative;
  overflow: hidden;
  min-height: 80vh;
  padding: var(--space-20) var(--page-padding);
  /* Adds .navbar's -84px margin-bottom back, on top only (not the
     shorthand above, which also sets left/right/bottom) - see that
     rule's comment. Keeps hero content clearing the now-overlapping
     transparent header by the usual --space-20 gap instead of sitting
     flush beneath it. */
  padding-top: calc(var(--space-20) + 84px);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Soft mint light texture from the Home hero redesign (Claude Design's
   texGlow option) - a shared .hero rule rather than something scoped to
   Home, so it paints every page's top banner the same way: Home's own
   hero and every sub-page's .hero--sub (Vault, How It Works, Pricing,
   About, FAQ, Contact, the Mortgage Readiness Report tool page) all
   share this base class already, so this needed no HTML changes at all
   beyond Home's own hero rebuild below. Sits behind .hero__inner (see
   its z-index just below) and above the section's own slate background. */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(62% 62% at 84% 30%, rgba(94, 208, 185, 0.30) 0%, transparent 64%),
    radial-gradient(85% 95% at 2% 60%, rgba(35, 49, 64, 0.62) 0%, transparent 68%);
  pointer-events: none;
}
.hero__inner { position: relative; z-index: 1; width: 100%; max-width: var(--container-lg); display: flex; gap: var(--space-20); align-items: center; }
.hero__content { flex: 1; display: flex; flex-direction: column; gap: 32px; }
.hero__copy { display: flex; flex-direction: column; gap: 24px; }
/* .hero__title styles the big marketing headline by class rather than
   by tag, since it's not always the same element: on pages with a
   .hero__eyebrow above it, .hero__title is the <h1> and the eyebrow is
   a plain <p> (a short page-name label isn't itself a heading - it was
   briefly marked up as the real <h1> instead, with this line demoted to
   <h2>, so every page had exactly one heading-level element before the
   real, unique, descriptive headline in its outline; reverted 1 Oct
   2026 once that was flagged as weak/duplicate H1 content for SEO -
   "Legal" was literally the H1 on all three legal pages - rather than a
   real accessibility or heading-count problem to fix). Pages with no
   eyebrow (Home, Features, Under the Surface, Mortgage Readiness
   Report) just use .hero__title as their only hero heading, already
   the <h1> either way. */
.hero__copy .hero__title {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 60px;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: #fff;
  text-wrap: pretty;
}
/* The short page-name eyebrow, styled to match the Mortgage Readiness
   Report's own pre-heading (.tool-hero__sector in
   mortgage-readiness-report.css) - same page, not reused directly,
   since that page keeps its existing markup untouched and this is a
   shared class every other hero now pulls in. A plain <p>, not a
   heading (see .hero__title above) - margin-bottom pulls .hero__title
   up close beneath it rather than leaving .hero__copy's full 24px flex
   gap between them. */
.hero__eyebrow {
  margin: 0 0 -16px;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #fff;
}
.hero__copy p {
  margin: 0;
  max-width: 560px;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  color: #fff;
  text-wrap: pretty;
}
.hero__actions { display: flex; gap: 16px; }
.hero__media { flex: 1; }
.hero__media-frame {
  display: block;
  width: 100%;
  height: min(56vh, 520px);
  min-height: 380px;
  border-radius: var(--radius-lg);
  object-fit: cover;
}

/* ---- Hero: card collage (Home) ------------------------------------
   Home's own hero layout - the vault photo is replaced with a stat row
   under the copy plus a mock "your visitor's screen" card stack, ported
   from the Card collage / texGlow variant in Claude Design's BowerVault
   Home.dc.html. Sub-pages keep the plain photo hero (.hero__media
   above) untouched - only the .hero::before gradient above is shared. */
.hero__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 48px;
  padding-top: 22px;
  border-top: 1px solid var(--color-scheme-1-border);
}
.hero__stat { display: flex; flex-direction: column; gap: 4px; }
.hero__stat-value { font-family: var(--font-heading); font-weight: 400; font-size: 28px; line-height: 1; color: #fff; }
.hero__stat-label { font-family: var(--font-body); font-size: 14px; line-height: 1.5; color: #fff; }
/* Hidden until .hero--collage's own ≤760px breakpoint swaps it in for
   .hero__stats above - see that rule for why. */
.hero__stats-line {
  display: none;
  padding-top: 16px;
  border-top: 1px solid var(--color-scheme-1-border);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.6;
  color: #fff;
}

.hero--collage .hero__content { flex: 0 1 520px; }
.hero--collage .hero__copy p { max-width: 46ch; }

/* The mock UI stack sits in .hero__media's place as the inner's second
   flex child, so it inherits the same responsive stacking behaviour
   (column below 640px, etc.) with no extra rules needed here. */
.hero-demo { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 18px; }
.hero-demo__chips { display: flex; flex-wrap: wrap; gap: 8px; padding-left: 2px; }
.hero-demo__chip {
  appearance: none;
  /* Native <button>: appearance:none hides the UA border in Chromium
     and WebKit but not in Firefox, so zero it outright (same trap as
     .filter-chip in vault.css). */
  border: 0;
  cursor: pointer;
  padding: 7px 14px;
  border-radius: 999px;
  white-space: nowrap;
  background: rgba(255, 255, 255, 0.16);
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.4;
  color: #fff;
  transition: background var(--duration-base) var(--ease-standard), color var(--duration-base) var(--ease-standard);
}
.hero-demo__chip.is-active { background: #fff; color: var(--text-primary); }

.hero-demo__brand {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 20px 22px;
  border-radius: 16px;
  background: #fff;
  margin-right: 56px;
}
.hero-demo__brand-label { font-family: var(--font-body); font-size: 14px; color: var(--text-secondary); white-space: nowrap; }
.hero-demo__swatches { display: flex; gap: 8px; }
.hero-demo__swatch { width: 24px; height: 24px; border-radius: 50%; }
.hero-demo__brand-aa { margin-left: auto; font-family: var(--font-heading); font-size: 15px; color: var(--text-primary); }

/* Cross-fades between industries (see initHeroIndustrySwitcher in
   main.js) - .is-fading drops opacity while the text underneath is
   swapped, same 300ms-out/300ms-in shape as the DC's data-fading. */
.hero-demo__card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 24px;
  border-radius: 16px;
  background: #fff;
  margin-left: 40px;
  transition: opacity 300ms ease;
}
.hero-demo__card.is-fading { opacity: 0; }
.hero-demo__eyebrow { font-family: var(--font-body); font-size: 13px; color: var(--text-secondary); }
.hero-demo__question { font-family: var(--font-heading); font-size: 22px; line-height: 1.25; color: var(--text-primary); }
.hero-demo__options { display: flex; flex-direction: column; gap: 8px; }
.hero-demo__option {
  padding: 12px 16px;
  border-radius: 999px;
  background: var(--surface-block);
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--text-primary);
}
/* Selected is a mint ground and nothing else - a ground and a ring
   together says it twice. */
.hero-demo__option--selected {
  background: var(--color-mountain-meadow-lightest);
}

.hero-demo__card--score {
  flex-direction: row;
  align-items: center;
  padding: 20px 24px;
  background: var(--color-pickled-bluewood-dark);
  margin-left: 0;
  margin-right: 32px;
}
.hero-demo__score-value {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--surface-accent);
  font-family: var(--font-heading);
  font-size: 18px;
  color: var(--color-pickled-bluewood-dark);
}
.hero-demo__score-copy { display: flex; flex-direction: column; gap: 4px; }
.hero-demo__score-title { font-family: var(--font-heading); font-size: 17px; color: #fff; }
.hero-demo__score-note { font-family: var(--font-body); font-size: 14px; color: #fff; }

/* ---- Split layout (Problem / Trust) ------------------------------ */
.split { width: 100%; max-width: var(--container-lg); display: flex; gap: var(--space-20); align-items: center; }
.split--reverse { flex-direction: row-reverse; }
/* For a .split whose second child is a mock-UI panel rather than a
   photo. Those panels declare `flex: 1 1 480px` (they have a natural
   width the design cares about), and .split__content's plain `flex: 1`
   is `1 1 0%` - so the free space is shared equally on top of a 480px
   head start and the copy ends up at 360px against the panel's 840px,
   where the design has roughly 570/630. Giving the copy a matching
   basis restores that. Photo splits don't need it: .split__media has
   no basis of its own. */
/* Only while the split is a row. Below page.css's own 960px block it
   becomes a column, and a flex-basis on a column item is a HEIGHT:
   which pinned every copy column to 420px tall and left a slab of
   empty space above the panel beneath it on every phone. Same trap
   the lead card hit; a basis has to be scoped to the axis it means. */
@media (min-width: 961px) {
  .split--panel > .split__content { flex: 1 1 420px; }
}
/* Top-aligned rather than centred, for a row whose two sides are
   different heights on purpose. Declared here, above the responsive
   block that switches .split to a column, so that block's
   `align-items: stretch` still wins on mobile - as an inline style on
   the element this beat the media query outright and left each child
   sizing to its own content, which pushed a 560px table past the
   viewport (see the inline-style and column-flex traps below). */
.split--top { align-items: flex-start; }
.split__content { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 32px; }
.split__copy { display: flex; flex-direction: column; gap: 24px; max-width: var(--max-width-md); }
.split__copy h2 { margin-top: -16px; }
.split__copy p {
  margin: 0;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  color: var(--text-primary);
  text-wrap: pretty;
}
.split__row { display: flex; max-width: var(--max-width-md); }
.split__row--stats { gap: 64px; }
.split__row--features { gap: 48px; }
.split__actions { display: flex; gap: 24px; align-items: center; }
.split__media {
  display: block;
  flex: 1;
  min-width: 0;
  align-self: stretch;
  width: 100%;
  min-height: 640px;
  border-radius: var(--radius-lg);
  object-fit: cover;
}
.split__media--trust { min-height: 520px; }

.stat { flex: 1; display: flex; flex-direction: column; gap: 8px; }
.stat__value {
  font-family: var(--font-heading);
  font-size: var(--text-h3);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}
.stat__label { font-family: var(--font-body); font-size: 16px; line-height: 1.5; color: var(--text-primary); text-wrap: pretty; }
.stat-source { display: block; font-family: var(--font-body); font-size: 11px; line-height: 1.5; color: var(--text-secondary); text-wrap: pretty; }

.feature { flex: 1; display: flex; flex-direction: column; gap: 16px; }
.feature__icon { height: 40px; display: flex; align-items: center; color: var(--text-primary); }
.feature__icon .icon { width: 36px; height: 36px; }
.feature h3 {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 24px;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  overflow-wrap: break-word;
}
.feature p { margin: -6px 0 0; font-family: var(--font-body); font-size: 16px; line-height: 1.5; color: var(--text-primary); text-wrap: pretty; }

/* ---- How it works: step cards ------------------------------------ */
.steps-grid { width: 100%; max-width: var(--container-lg); display: grid; grid-template-columns: repeat(4, 1fr); gap: 32px; }
.step-card {
  position: relative;
  min-height: 440px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: linear-gradient(rgba(35, 49, 64, 0.72), rgba(35, 49, 64, 0.88)), var(--bg) center / cover no-repeat;
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: 24px;
  color: var(--text-on-dark);
  transition: transform var(--duration-base) var(--ease-standard), box-shadow var(--duration-base) var(--ease-standard), opacity var(--duration-base) var(--ease-standard);
}
.step-card:hover { transform: translateY(-4px); box-shadow: 0 18px 40px rgba(12, 8, 1, 0.25); }
.step-card__icon { height: 48px; display: flex; align-items: center; }
.step-card__icon .icon { width: 40px; height: 40px; }
.step-card__body { display: flex; flex-direction: column; gap: 12px; flex: 1; }
.step-card__body h3 {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 28px;
  line-height: 1.3;
  letter-spacing: -0.01em;
  overflow-wrap: break-word;
}
.step-card__body p { margin: 0; font-family: var(--font-body); font-size: 16px; line-height: 1.5; text-wrap: pretty; }

/* ---- The vault: tool cards ----------------------------------------- */
.vault-grid { width: 100%; max-width: var(--container-lg); display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px; }
.vault-card {
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--card-bg);
  transition: transform var(--duration-base) var(--ease-standard), box-shadow var(--duration-base) var(--ease-standard), opacity var(--duration-base) var(--ease-standard);
}
.vault-card:hover { transform: translateY(-4px); box-shadow: 0 18px 40px rgba(12, 8, 1, 0.1); }
.vault-card__media { display: block; width: 100%; height: 220px; object-fit: cover; }
.vault-card__body { display: flex; flex-direction: column; gap: 20px; padding: 24px; flex: 1; }
.vault-card__meta { display: flex; flex-direction: column; gap: 8px; }
.vault-card__tags { display: flex; align-items: center; gap: 8px; }
.vault-card__sector { font-family: var(--font-ui); font-size: 14px; font-weight: 600; line-height: 1.5; color: var(--color-pickled-bluewood); }
.vault-card__status { font-family: var(--font-ui); font-size: 14px; line-height: 1.5; color: var(--text-secondary); }
.vault-card__body h3 {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 24px;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  overflow-wrap: break-word;
}
.vault-card__body p { margin: 0; font-family: var(--font-body); font-size: 16px; line-height: 1.5; color: var(--text-primary); text-wrap: pretty; }
.vault-card__action { margin-top: auto; }

/* ---- Features grid --------------------------------------------- */
.features-grid { width: 100%; max-width: var(--container-lg); display: grid; grid-template-columns: repeat(4, 1fr); gap: 32px; }
.feature-card {
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--card-bg);
  transition: transform var(--duration-base) var(--ease-standard), box-shadow var(--duration-base) var(--ease-standard), opacity var(--duration-base) var(--ease-standard);
}
.feature-card:hover { transform: translateY(-4px); box-shadow: 0 18px 40px rgba(12, 8, 1, 0.1); }
.feature-card__media { display: block; width: 100%; height: 200px; object-fit: cover; }
.feature-card__body { display: flex; flex-direction: column; gap: 16px; padding: 24px; flex: 1; }
.feature-card__tags { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.feature-card__eyebrow { font-family: var(--font-ui); font-size: 14px; font-weight: 600; line-height: 1.5; color: var(--color-pickled-bluewood); }
/* A quiet neutral chip - the default, expected state for a feature
   that's already active. Its one-time --addon modifier (a louder navy
   chip for the nurture-emails card, back when that was a purchasable
   add-on rather than an unreleased one) is gone now that card uses the
   shared .tag-soon "Coming soon" label instead (components.css) - no
   other card here has needed anything louder than --included since. */
.feature-card__badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: 999px;
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
}
.feature-card__badge--included { background: var(--opacity-neutral-darkest-5); color: var(--text-secondary); }
.feature-card__body h3 {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 24px;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  text-wrap: pretty;
  overflow-wrap: break-word;
}
.feature-card__body p { margin: 0; font-family: var(--font-body); font-size: 16px; line-height: 1.5; color: var(--text-primary); text-wrap: pretty; }
.feature-card__action { margin-top: auto; padding-top: 16px; }

/* ---- Pricing --------------------------------------------------------
   Home's #pricing section reuses pricing.html's own .billing-toggle +
   .plan-panel (components.css) directly now - both pages show the
   exact same pricing display, not two different ones. */
/* #pricing's own gap is a flat inline 64px (style="gap:64px", see the
   CLAUDE.md note on why that can't be overridden by a class-based rule)
   - unconditional here rather than scoped to a breakpoint, since that
   64px doesn't change with viewport width the way .section's own
   responsive gap does, so one value covers every width. Pulls both the
   header-to-toggle gap and the toggle-to-plan-panel gap down to the
   same 32px convention used elsewhere. Same fix as pricing.css's own
   #pricing .billing-toggle rule, just tuned against Home's own 64px
   gap instead of that page's 48px one. */
#pricing .billing-toggle { margin-top: -32px; margin-bottom: -32px; }
/* Same idea, opposite side: the gap below .plan-panel, above the
   trailing "Marketing freelancer..." note, was the same unreduced 64px
   - too much air between the table and a single trailing line right
   under it. Pulled down to ~20px. Adjacent-sibling selector (not just
   .tools-note) so this only ever targets the note that actually
   follows the pricing table, not .tools-note's unrelated use on the
   Vault page. */
#pricing .plan-panel + .tools-note { margin-top: -44px; }
.section-link { font-family: var(--font-ui); font-size: 16px; line-height: 1.5; }
/* Header-to-content spacing convention (same 32px target already used
   at ≤960px - see that media query below): .section's own gap
   (--space-20, 80px) is too much between a section's header and the
   very first card in the grid right under it. Shared classes, so this
   also covers Vault's tools/soon grids (same .vault-grid class) as
   well as Home's #how/#library/#features. -48px rather than ≤960's
   -32px because the base gap being pulled from is bigger up here
   (80px, not 64px) - same 32px result either way. */
.steps-grid, .vault-grid, .features-grid { margin-top: -48px; }
/* Pulls just this one link up closer to the step-card grid above it:
   leaves too much dead air between a 440px-tall card row and a single
   trailing line of text otherwise. */
#how .section-link { margin-top: -56px; }
/* Same distance as #how's CTA above (24px) - same reasoning: too much
   air below a full card row otherwise. */
#library > .bv-btn--secondary { margin-top: -56px; }

/* ---- Final CTA ------------------------------------------------------ */
.cta { background: var(--color-scheme-1-background); padding: var(--section-padding-lg) var(--page-padding); display: flex; justify-content: center; }
.cta__inner { width: 100%; max-width: var(--container-lg); display: grid; grid-template-columns: 1.1fr 0.9fr; border-radius: var(--radius-lg); overflow: hidden; }
.cta__card { padding: 64px; display: flex; flex-direction: column; gap: 32px; background: var(--color-pickled-bluewood); }
.cta__tagline { font-family: var(--font-body); font-size: 16px; font-weight: 600; line-height: 1.5; color: rgba(255, 255, 255, 0.7); margin-bottom: -8px; }
.cta__card h2 {
  margin: 0;
  margin-top: -16px;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 44px;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--text-on-dark);
  text-wrap: pretty;
}
/* margin, not the parent's flex gap (32px, fine for tagline->h2):
   pulls just the space directly above/below this paragraph down to
   ~20px without touching the other gaps in the card. */
.cta__card p { margin: -12px 0; font-family: var(--font-body); font-size: 16px; line-height: 1.5; color: rgba(255, 255, 255, 0.8); text-wrap: pretty; }
.cta__actions { display: flex; gap: 16px; align-items: center; margin-top: 8px; }
/* Its only live use (Features' closing CTA) is inside .cta--tight,
   which overrides .cta__card's gap from 32px down to 16px - margin-top
   here is tuned against that 16px, not the base .cta__card gap. */
.cta__vat-note { margin-top: 4px; font-family: var(--font-ui); font-size: 9pt; font-style: italic; color: rgba(255, 255, 255, 0.7); }
/* .cta__media's <picture> wrapper is a direct grid child of .cta__inner
   - unlike every other picture-wrapped photo on the site, which sits
   one level deeper inside a flex/block child (.hero__media, .split,
   .vault-card, .feature-card) and never has to act as a grid item
   itself. picture's global display:contents (tokens/base.css) is meant
   to make the <img> behave as if it were that direct child, but that
   pass-through isn't reliable for CSS Grid item placement the way it
   is for flex: instead of the <img> taking picture's place in the
   1.1fr/0.9fr column track, it gets auto-placed as if picture weren't
   there at all, collapsing both columns into a stacked single-column
   layout. Grid needs picture to stay a real, sized grid item here:
   this gives it the same box .cta__media used to own solo so the two
   columns sit side by side again; .cta__media below still owns the
   actual image sizing/crop inside that box. */
/* position:relative/absolute, not height:100% or a fixed height - the
   brief is "always the same height as .cta__card, like a background
   image," and grid alone can't reliably give the picture that: against
   an auto-sized row, height:100% is only definite once the row's own
   size is already resolved, and a photo's own intrinsic aspect ratio
   (at the column's width) counts toward that resolution on the pass
   following image load, growing the row - and a hardcoded height
   fixes that but re-breaks the moment .cta__card's own natural height
   ever differs from that number (longer copy, a different viewport -
   see the git history right above this comment for the hardcoded
   version and why it still wasn't right). Taking .cta__media out of
   flow removes it from the row's sizing calculation entirely: picture
   (the real grid item - see the note above) becomes an empty box that
   only ever takes the height the grid's default align-items: stretch
   gives it, which is driven purely by .cta__card's own content since
   nothing else contributes to it now, and .cta__media then fills
   whatever that turns out to be via inset:0, the same way a CSS
   background-image fills its element regardless of the element's own
   content. */
.cta__inner picture { display: block; position: relative; width: 100%; min-height: 420px; }
.cta__media { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* ---- Footer --------------------------------------------------------- */
.site-footer { background: var(--surface-inverse); color: var(--text-on-dark); padding: 80px var(--page-padding) 32px; }
.site-footer__inner { max-width: var(--container-lg); margin: 0 auto; display: flex; flex-direction: column; gap: 64px; }
.footer-top { display: flex; gap: var(--space-20); align-items: flex-start; }
/* Product/Company/Legal share this wrapper so they can be laid out as
   one unit separate from .footer-brand at narrower widths (see the
   960px/640px overrides below) - at this width it's invisible: same
   flex/gap as .footer-top itself reproduces the exact same spacing as
   when the three .footer-col were plain siblings of .footer-brand. */
.footer-cols { display: flex; gap: var(--space-20); }
.footer-brand { flex: 1; display: flex; flex-direction: column; gap: 32px; max-width: 400px; }
.footer-brand__logo-link { position: relative; display: block; flex-shrink: 0; width: 248px; height: 50px; margin-left: -15.5px; text-decoration: none; }
.footer-brand__logo { height: 50px; }
.footer-brand__strapline {
  position: absolute;
  top: calc(100% + 5px);
  left: 15.5px;
  width: 232px;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 10.3px;
  line-height: 1.2;
  white-space: nowrap;
  color: var(--text-on-dark-muted);
}
/* .footer-brand's own 32px flex gap (above) spaces every child evenly,
   logo block to "by" line to tagline paragraph alike - too much air
   specifically between the logo block and "by", which reads as its own
   small credit line directly under the wordmark, not a separate section.
   Pulls it back up against that 32px gap rather than adding to it. */
.footer-brand__by { margin-top: 1px; display: flex; align-items: flex-end; gap: 6px; }
.footer-brand__by span { font-family: var(--font-ui); font-size: 14px; line-height: 1; margin-bottom: -3px; color: var(--text-on-dark-muted); }
.footer-brand__by img { display: block; height: 26px; width: auto; flex-shrink: 0; opacity: 0.8; }
.footer-brand p { margin: 0; font-family: var(--font-body); font-size: 14px; line-height: 1.5; color: var(--text-on-dark-muted); text-wrap: pretty; }
/* Only used where build.js's footer tagline="..." override swaps the
   marketing strapline for the legal/company-registration line (see
   FOOTER_TAGLINES there) - centred per that request, unlike the
   left-aligned marketing copy every other page keeps. Just the
   alignment here: the width fix this longer line also needs (so it
   breaks after "Company No. 15747529" instead of wrapping a third
   time) lives in the page-specific CSS of whichever page actually uses
   this, since the right fix depends on that page's own footer layout
   (see css/mortgage-broker-demo.css's .minimal-chrome rules for the
   only page that does today). */
.footer-brand__tagline--centre { text-align: center; }
.footer-col { display: flex; flex-direction: column; gap: 6px; width: 180px; }
.footer-col__title { font-family: var(--font-ui); font-size: 14px; font-weight: 600; line-height: 1.5; color: var(--text-on-dark); }
.footer-col a {
  position: relative;
  display: inline-block;
  width: fit-content;
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-on-dark);
  text-decoration: none;
}
/* Same fix as the nav's a:hover override: the generic a:hover rule in
   tokens/base.css tints links navy on hover, which against the footer's
   own dark navy background was reading as barely-there. Stays plain
   white; the keyline below is the only hover feedback, same expanding-
   underline pattern as the main nav. */
.footer-col a:hover { color: var(--text-on-dark); }
.footer-col a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: 1px;
  background: var(--text-on-dark);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 500ms var(--ease-standard);
}
.footer-col a:hover::after,
.footer-col a:focus-visible::after {
  transform: scaleX(1);
}
.footer-bottom { display: flex; gap: 24px; align-items: center; padding-top: 32px; border-top: var(--divider-width) solid var(--border-on-dark); }
.footer-bottom span { font-family: var(--font-ui); font-size: 14px; line-height: 1.5; color: var(--text-on-dark-muted); }

/* ============================================================
   Responsive. The prototype was authored at a fixed desktop
   width; these breakpoints carry it down using the same mobile
   scale the design system already documents in
   tokens/fig-tokens.css (:root[data-mode="mobile"]).
   ============================================================ */
@media (max-width: 1080px) {
  .steps-grid, .features-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Nav collapses to the burger + slide-out panel here - a wider threshold
   than the other 960px mobile/tablet treatment below on purpose, matching
   the design: four nav links + two buttons need more like 1060px of
   breathing room before they start feeling cramped, well before the rest
   of a page's layout needs to adapt. */
@media (max-width: 1060px) {
  .nav-links, .nav-actions { display: none; }
  .nav-toggle { display: flex; }
}

@media (max-width: 960px) {
  :root { --page-padding: 32px; }
  .section { padding: var(--section-padding-md) var(--page-padding); gap: var(--space-16); }
  /* Both the 80vh (Home) and 60vh (every other page, .hero--sub in
     vault.css) forced minimum heights are a deliberate desktop decision
     (see CLAUDE.md) - but nothing scoped that intent to desktop only, so
     it was also forcing a tablet-height viewport's worth of mostly-empty
     space above/around hero content shorter than that, the same dead-
     space bug already fixed for phones below. Relaxes here too. */
  .hero { min-height: auto; padding: var(--space-16) var(--page-padding); }
  /* Same header-overlap compensation as the desktop rule above, just
     against this width's own --space-16 base instead of --space-20:
     the header's own unscrolled height doesn't change at this width
     (see .navbar's -84px margin-bottom), only the padding shorthand
     it's being re-added on top of does. */
  .hero { padding-top: calc(var(--space-16) + 84px); }
  /* .hero--sub itself is relaxed in vault.css, not here - that file's
     <link> loads after this one (it's a separate tag, not part of
     styles.css's @import chain), so its unconditional 60vh rule would
     otherwise keep winning over an override placed in this file
     regardless of media query, the same footgun the 640px fix hit. */
  /* Filled/outline buttons are a touch target below here, not just a
     hover target - 36px (.bv-btn's desktop 6px vertical padding) is
     under the ~44px minimum comfortable tap size. .bv-btn--link is left
     alone: it's an inline text link with its own icon, not a tap-target
     sized control, and padding around plain text would look odd. */
  .bv-btn:not(.bv-btn--link) { padding-top: 10px; padding-bottom: 10px; }
  .bv-btn--small:not(.bv-btn--link) { padding-top: 8px; padding-bottom: 8px; }
  .hero__copy .hero__title { font-size: 44px; }
  /* Home's own hero collage gets its tablet/mobile treatment from the
     dedicated .hero--collage breakpoints at the end of this file (ported
     from the Home Responsive Preview design export), not from here. */
  .section-header h2, .split__heading { font-size: var(--text-h3); }
  /* align-items: stretch, not the base rule's center - .split's base
     "center" is for row mode (vertical-centers the two side-by-side
     halves); once stacked into a column here, "center" instead sizes
     each child to its own content width and centers it, which let
     unwrapped content (the embed snippet's <pre>) size the whole column
     to its content width and overflow the viewport. Stretch makes every
     child fill the full stacked width, same as .split__media's explicit
     width:100% below was already doing by hand. */
  .split, .split--reverse { flex-direction: column; align-items: stretch; }
  .split__media, .split__media--trust { width: 100%; min-height: 320px; }
  .vault-grid { grid-template-columns: repeat(2, 1fr); }
  /* A 2-column grid with an odd number of cards leaves the last one
     stranded alone in its own row - :nth-child(odd) only matches the
     last child when the total count is odd, so this only ever fires
     when there'd actually be an orphan, and self-adjusts if a card is
     added/removed later. :not(:first-child) excludes the one card that
     shouldn't stretch: a grid with only a single card is trivially both
     first and last, and that single card should just sit at its normal
     width (like any other one-card row), not stretch to fill the row:
     Vault's own tools-grid hits exactly this today (1 live tool). Shared
     class, so this covers every .vault-grid on the site: Home's
     #library (3 cards) and Vault's own tools/soon grids. */
  .vault-grid > :last-child:nth-child(odd):not(:first-child) { grid-column: 1 / -1; }

  /* ---- Tablet+mobile spacing convention: 32px between a section's
     intro copy and the content block right below it, wherever that gap
     was otherwise just inheriting the desktop 80px value with nothing
     scoped down for narrower widths. One shared number so every section
     - home and every other page reusing these same classes/patterns:
     reads the same way at both this width and the 640px tier below (that
     tier doesn't repeat these - same 64px .section gap applies at both,
     so the fix is identical and lives here once for both). The sibling
     rules below apply the equivalent fix to the handful of page-specific
     headers that don't go through .section-header/.split at all. */
  /* .split's own gap (--space-20, 80px, unconditional at every width) is
     the content<->image gap once stacked into a column here - too much
     air once it's a column, not a row. Shared class, so this also covers
     every other page's .split sections (How It Works, the Mortgage
     Readiness Report tool page, Vault's bespoke-tools split, About), not
     just Home's - and both #problem's gap above its image and #trust's
     gap below its CTA are this same property, just described from
     opposite ends. */
  .split { gap: 32px; }
  /* .section's own gap is already down to 64px at this width - fine for
     the row-to-CTA gap tuned separately below, too much between a
     section's header and the very first card in the row right under it.
     Shared classes, so this also covers Vault's tools/soon grids (same
     .vault-grid class). */
  .steps-grid, .vault-grid, .features-grid { margin-top: -32px; }

  /* #how/#library pull their trailing CTA up from .section's own gap
     (see the desktop -56px rules above) to sit a tidy 24px under the
     card grid on desktop. At this width .section's own gap is already
     down to --space-16 (64px), so that same -56px leaves only ~8px:
     too cramped, the cards read as crowding the CTA underneath them.
     Eased back up to 40px instead. */
  #how .section-link,
  #library > .bv-btn--secondary {
    margin-top: -24px;
  }
  /* Opposite direction from #how/#library above: #pricing's gap is a
     flat inline 64px (style="gap:64px" on the section itself, see the
     CLAUDE.md note on why that can't just be overridden by a class-
     based rule) at every width - pulled in the same way - margin on
     the child rather than fighting the section's own gap - down to a
     tidier 24px underneath the pricing card. (The header-to-toggle gap
     just above it is the same 64px base but doesn't need a width-
     scoped version here - see the unconditional rule near
     .pricing-body's own definition, since 64px doesn't change with
     viewport the way .section's own responsive gap does.) */
  #pricing > .section-link { margin-top: -40px; }

  .cta__inner { grid-template-columns: 1fr; }
  /* order moves to picture, not .cta__media: picture is the actual
     grid item at this breakpoint now (see the desktop rule above), so
     it's the one that needs reordering above .cta__card. */
  /* Below the breakpoint where .cta__inner stacks to one column, there's
     no .cta__card row-mate left for the photo to match the height of
     (see the desktop rule above) - explicitly not trying to replicate
     that here. Instead the photo drops out of the absolute/cover setup
     entirely and flows normally at its own natural aspect ratio, full
     width, nothing cropped off: order: -1 keeps it visually above
     .cta__card same as before. */
  .cta__inner picture { position: static; width: 100%; min-height: 0; order: -1; }
  .cta__media { position: static; width: 100%; height: auto; object-fit: initial; }
  .cta__card h2 { font-size: 36px; }
  /* Was flex-wrap: wrap on four plain siblings (brand + 3 columns):
     at tablet widths that wrapped unpredictably (brand + two columns
     fitting one row, the third column left stranded alone on a second
     row, indented under nothing in particular). Explicit instead:
     .footer-brand full-width on its own row, the three columns as one
     tidy row underneath via .footer-cols (which stays nowrap - plenty
     of room for three 180px columns at this width, see the comment on
     .footer-cols above). */
  .footer-top { flex-direction: column; gap: 40px; }
  .footer-cols { gap: 40px; }
}

@media (max-width: 640px) {
  :root { --page-padding: 20px; }
  .steps-grid, .features-grid, .vault-grid { grid-template-columns: 1fr; }
  /* Same fix as .split's stretch override below (960px block) - the base
     rule's align-items: center is for the row layout, and left in place
     here it sizes .hero__media to its own content instead of the column
     width, which for a background-image div with no intrinsic size
     collapses it to 0 width (280px tall, invisible, just dead space in
     the stack, image never showing). */
  .hero__inner { flex-direction: column; align-items: stretch; }
  /* Base .hero__inner gap (--space-20, 80px) is fine holding the copy
     and image apart side-by-side, but reads as a big dead gap once
     they're stacked in a column instead. */
  .hero__inner { gap: 40px; }
  /* Base .hero__content gap (32px) sat above .hero__copy's own internal
     gap (24px, h1->p), so the gap above the buttons (copy->actions) was
     a noticeably bigger jump than the gap above the description (h1->p)
     just above it. Matching it to that same 24px makes both gaps equal
     and trims a little off the space above the buttons at the same time. */
  .hero__content { gap: 24px; }
  .hero { min-height: auto; }
  .hero__copy .hero__title { font-size: 36px; }
  .hero__media-frame { width: 100%; height: 280px; min-height: 0; }
  .split__row--stats, .split__row--features { flex-direction: column; gap: 24px; }
  .cta__card { padding: 40px 24px; }
  .cta__card h2 { font-size: 32px; }

  /* ---- Footer ------------------------------------------------------ */
  /* 80px of top padding (unconditional, matches the section rhythm
     above it) is more than a stacked mobile footer needs once there's
     no side-by-side row to visually separate from the section above. */
  .site-footer { padding-top: 40px; }
  /* This -15.5px pull-left is a deliberate desktop-only correction for
     the wordmark SVG's own left-side whitespace, tuned against the
     desktop page edge - stacked on top of --page-padding already having
     dropped to 20px above, it was pulling the logo to within ~4px of
     the screen edge. Dropped here rather than reduced, matching every
     other piece of mobile content that just sits flush against
     --page-padding with no extra pull. */
  .footer-brand__logo-link { margin-left: 0; }
  /* Un-shifting the logo link above (0 instead of -15.5px) also undid
     the reason "by Tinybox Creative" and the wordmark's B lined up on
     desktop: that -15.5px was pulling the whole logo box (B included)
     15.5px left so B would land flush with .footer-brand's own edge,
     the same edge .footer-brand__by already starts from unshifted. With
     the logo box no longer pulled left, B now sits 15.5px in from that
     edge (same offset .footer-brand__strapline is already tuned to at
     this logo height, left: 15.5px) while .footer-brand__by is still at
     0 - nudged over here to the same 15.5px so it starts under B again. */
  .footer-brand__by { margin-left: 15.5px; }
  /* Same reasoning as .hero__inner above - the base --space-20 (80px,
     desktop) already steps down to 40px once the row wraps at 960px;
     tightened further here for a phone-width stack of four blocks
     (brand + Product/Company/Legal) instead of a wrapped row or two. */
  .footer-top { gap: 24px; }
  /* .footer-cols keeps its 960px row layout (three side-by-side columns)
     until it genuinely can't - down here the columns go the rest of the
     way to a single stack too, same gap as .footer-top above it. */
  .footer-cols { flex-direction: column; gap: 24px; }
  /* Gap above the bottom keyline (.footer-bottom's own border-top):
     unconditional 64px at every width, fine holding a single wide row
     of links away from the copyright line but too much air once that
     row is a tall phone-width stack of its own already-tightened
     24px gaps just above it. */
  .site-footer__inner { gap: 32px; }
}

/* ---- Mobile: animation off (bar headings), thin sticky header --------
   Two asks, same breakpoint. (1) Every decorative hover/lift/scroll-
   reveal-stagger/counter animation goes off below 768px - the only
   motion left is the heading fade-in-up. --duration-base drives nearly
   every decorative transition site-wide (button/card hover, pricing
   toggle, accordion chevron, the sticky header's own frosted-in fade),
   so zeroing it here is most of the fix in one place; the two spots that
   use a hardcoded ms value instead of the token (the nav/footer keyline
   hover-draw, the vault tool cards' image-zoom hover in vault.css) get
   their own explicit override alongside it. Card-row stagger-reveal and
   the stat count-up are switched off in JS (initScrollReveal/
   initStatCounters in main.js) rather than here - CSS can silence a
   transition's duration but can't stop the IntersectionObserver logic
   that decides what gets a .reveal class in the first place.
   (2) Sticky header goes thin/low-profile once scrolled - reduced
   padding, logo shrinks, strapline disappears (reusing the existing
   .navbar.is-scrolled rules below, already gated behind
   --duration-base so this is instant here rather than fading). At
   rest, the mobile logo is bigger than that scrolled size and the
   strapline stays visible under it - full desktop scale, not shrunk.
   Still the site's own navy background throughout, not the white bar a
   HubSpot mobile reference used for the same compactness: every logo
   asset in assets/ is a light/white variant, so a white header would
   leave it illegible. */
@media (max-width: 767px) {
  :root { --duration-base: 0.01ms; }
  .nav-links a::after,
  .footer-col a::after {
    transition-duration: 0.01ms;
  }

  /* margin-bottom shrinks alongside the reduced padding (68px real
     height here vs 84px at rest) so the two keep summing to 0 - see
     the base .navbar rule above for why this matters (a real,
     confirmed layout-glitch bug otherwise). */
  .navbar.is-scrolled { padding: 12px var(--page-padding); margin-bottom: -68px; }
  /* Shrinks only once scrolled - same 0.72 scale as before this change,
     just no longer applied at rest too. Re-declared here (rather than
     relying on the shared, non-mobile .navbar.is-scrolled rule) because
     mobile's rest-state logo is now bigger than desktop's own scrolled
     size, so the scroll-triggered shrink needs its own target instead
     of whatever scale desktop uses. */
  .navbar.is-scrolled .navbar__logo-link {
    transform: scale(0.72);
  }
  /* .navbar__inner centers the toggle against the logo *link*'s own
     44px box, but the strapline hanging below it (position: absolute,
     so it adds no height to that box) makes the true visual lockup
     taller - flexbox was centering the toggle to the wordmark alone,
     landing it visibly above the middle of wordmark+strapline together.
     Nudged down by half the strapline's own height + the gap above it
     (measured, not guessed) to land on the full lockup's centre instead.
     Only at rest: once scrolled the strapline's gone and the shrunk logo
     is back to being the whole lockup, so the toggle's default centering
     is already correct there. */
  .nav-toggle { transform: translateY(7px); }
  .navbar.is-scrolled .nav-toggle { transform: none; }
}

/* ---- Hero: card collage (Home) - tablet & mobile ---------------------
   Ported from the Home Responsive Preview design export (which iframes
   BowerVault Home.dc.html at 834px and 390px) - its own two breakpoints
   (1024px, 760px), not the 960px/640px tiers used elsewhere in this
   file, which are tuned for the rest of the site's general layout, not
   this specific card stack. Placed at the very end of the file (rather
   than folded into the 960px/640px blocks above) so these - later in
   source order, same specificity on anything not already scoped more
   tightly - reliably win over the generic .hero rules in those earlier
   blocks. Scoped to .hero--collage (Home only) wherever the touched
   selector is a class shared with every other page's plain-photo hero
   (.hero__inner, .hero__content, .hero__copy .hero__title) - .hero-demo
   and .hero__stats* are Home-only markup already, so those rules apply
   unprefixed, same as the desktop rules above. The industry switcher's
   own auto-advance/pause JS (initHeroIndustrySwitcher in main.js) is
   pure behaviour, untouched by any of this - it keeps running at every
   width these rules cover. */
@media (max-width: 1024px) {
  /* Lets .hero__content and .hero-demo wrap onto their own full-width
     rows instead of being squeezed side by side once there's no longer
     room for both at a comfortable reading width. */
  .hero--collage .hero__inner { flex-wrap: wrap; gap: 20px; }
  .hero--collage .hero__content { flex: 1 1 100%; }
  .hero-demo { min-width: 320px; }
  .hero--collage .hero__copy .hero__title { font-size: clamp(36px, 5.4vw, 54px); }
  /* The staggered desktop offsets (see .hero-demo__brand/__card above)
     are a wide-column flourish - dead space either side once the
     column's this narrow, so all three collapse to 0 together here
     rather than easing down in steps. */
  .hero-demo__brand,
  .hero-demo__card,
  .hero-demo__card--score {
    margin-left: 0;
    margin-right: 0;
  }
  /* Same four industries, now a proper grid instead of a wrapping row,
     so every pill reads as an equal-width choice instead of whatever
     happens to fit per line. */
  .hero-demo__chips { display: grid; grid-template-columns: repeat(4, 1fr); }
  .hero-demo__chip { padding-left: 8px; padding-right: 8px; text-align: center; }
}

@media (max-width: 760px) {
  .hero--collage { padding-top: 124px; min-height: 0; }
  .hero--collage .hero__inner { gap: 18px; }
  /* Two-up instead of four-up - four equal columns at this width leaves
     each pill's label wrapping or clipping. */
  .hero-demo__chips { grid-template-columns: repeat(2, 1fr); }
  /* Three separate stat blocks read as cramped rather than scannable
     this narrow - one condensed line carries the same three facts
     (see .hero__stats-line's own rule above). */
  .hero__stats { display: none; }
  .hero__stats-line { display: block; }
  .hero--collage .hero__copy .hero__title { font-size: 34px; line-height: 1.12; }
}
