  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  :root {
    --cream:   #F8F4EF;
    --warm:    #EDE5D8;
    --sand:    #D4C4AF;
    --muted:   #9A8C7E;
    --charcoal:#2C2825;
    --ink:     #1A1714;
    --rose:    #C4786A;
    --gold:    #B8965A;
    --dark-gray:#2A2622;
    --serif:   'Cormorant Garamond', Georgia, serif;
    --sans:    'Jost', system-ui, sans-serif;
    --nav-h:   72px;
    --max:     1560px;
    --ease:    cubic-bezier(0.22, 1, 0.36, 1);
  }

  html { scroll-behavior: smooth; }
  body { background: var(--cream); color: var(--charcoal); font-family: var(--sans); font-weight: 300; overflow-x: hidden; -webkit-font-smoothing: antialiased; }
  img { display: block; max-width: 100%; }

  /* ============ NAV ============ */
  nav {
    position: fixed; top: 0; left: 0; right: 0; z-index: 100;
    height: var(--nav-h);
    display: flex; align-items: center; justify-content: space-between;
    padding: 0 48px;
    background: rgba(248,244,239,0.8);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-bottom: 1px solid rgba(212,196,175,0.35);
    transition: background 0.35s var(--ease), border-color 0.35s var(--ease);
  }
  .nav-logo { position: relative; display: inline-block; height: 40px; line-height: 0; text-decoration: none; }
  .nav-logo img { height: 40px; width: auto; transition: opacity 0.35s var(--ease); }
  .nav-logo .logo-light { position: absolute; inset: 0; opacity: 0; }
  nav.hero-nav .nav-logo .logo-dark { opacity: 0; }
  nav.hero-nav .nav-logo .logo-light { opacity: 1; }

  .nav-links { display: flex; gap: 40px; list-style: none; }
  .nav-links a {
    position: relative;
    font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase;
    font-weight: 400; color: var(--muted); text-decoration: none;
    padding: 6px 0;
    transition: color 0.25s var(--ease);
  }
  .nav-links a::after {
    content: '';
    position: absolute; left: 0; bottom: 0;
    width: 100%; height: 1px;
    background: currentColor;
    transform: scaleX(0); transform-origin: right;
    transition: transform 0.45s var(--ease);
  }
  .nav-links a:hover { color: var(--ink); }
  .nav-links a:hover::after { transform: scaleX(1); transform-origin: left; }

  .nav-cta {
    position: relative; overflow: hidden;
    font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 400;
    padding: 12px 29px;
    background: #fff;
    border: 0;
    color: var(--ink);
    text-decoration: none;
    opacity: 0.72;
    transition: color 0.4s var(--ease), opacity 0.4s var(--ease);
    z-index: 0;
  }
  .nav-cta::before {
    content: ''; position: absolute; inset: 0;
    background: var(--rose);
    transform: translateX(-101%);
    transition: transform 0.5s var(--ease);
    z-index: -1;
  }
  .nav-cta:hover { color: #fff; opacity: 1; }
  .nav-cta:hover::before { transform: translateY(0); }

  nav.hero-nav { background: transparent; border-bottom-color: transparent; }
  nav.hero-nav .nav-links a { color: rgba(255,255,255,0.8); }
  nav.hero-nav .nav-links a:hover { color: #fff; }

  /* hamburger */
  .hamburger {
    display: none; background: none; border: 0; padding: 8px;
    cursor: pointer; z-index: 301;
    position: relative;
  }
  .hamburger span {
    display: block; width: 26px; height: 1px;
    background: var(--ink); margin: 7px 0;
    transition: transform 0.4s var(--ease), opacity 0.3s, background 0.3s;
  }
  nav.hero-nav .hamburger span { background: #fff; }
  .hamburger.open span:nth-child(1) { transform: translateY(8px) rotate(45deg); background: var(--ink); }
  .hamburger.open span:nth-child(2) { opacity: 0; }
  .hamburger.open span:nth-child(3) { transform: translateY(-8px) rotate(-45deg); background: var(--ink); }

  /* Drawer uses 100dvh so the bottom CTA stays inside the visible
     viewport on iOS Safari — 100vh measures the large viewport (URL
     bar hidden) and would push "Get in Touch" behind Safari's toolbar.
     Bottom padding also respects the safe-area inset for gesture bars. */
  .mobile-drawer {
    position: fixed; inset: 0;
    width: 100vw; height: 100dvh;
    background: var(--cream);
    padding: 100px 40px calc(32px + env(safe-area-inset-bottom, 0px));
    transform: translateX(100%);
    transition: transform 0.55s var(--ease);
    z-index: 300;
    display: flex; flex-direction: column; gap: 4px;
    visibility: hidden;
  }
  /* Ghost close button — pinned to the exact slot the hamburger
     occupies when the menu is shut, so clicking ≡ / X feels like a
     single toggle in place. The hamburger lives inside the nav at
     24px right (nav padding on mobile), vertically centered in the
     72px nav height; it's 42×48 (26px span + 8px padding all sides).
     We mirror those dimensions here and size the internal SVG to
     26px so the X glyph matches the hamburger span width exactly. */
  .drawer-close {
    position: absolute;
    top: calc((var(--nav-h) - 48px) / 2);
    right: 24px;
    width: 42px; height: 48px;
    padding: 8px;
    background: transparent; border: 0;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; color: var(--ink);
    transition: color 0.3s var(--ease), transform 0.3s var(--ease);
    z-index: 310;
  }
  .drawer-close svg { width: 26px; height: 26px; }
  .drawer-close:hover {
    background: transparent;
    color: var(--rose);
    transform: rotate(90deg);
  }
  .mobile-drawer.open { transform: translateX(0); visibility: visible; }
  .mobile-drawer a {
    display: block;
    font-family: var(--serif); font-size: 36px; font-weight: 300;
    color: var(--ink); text-decoration: none;
    padding: 22px 0;
    border-bottom: 1px solid var(--sand);
    opacity: 0;
    transform: translateY(20px);
    transition-property: opacity, transform, color, padding-left;
    transition-duration: 0.5s, 0.5s, 0.25s, 0.4s;
    transition-timing-function: var(--ease);
    transition-delay: 0s, 0s, 0s, 0s;
  }
  .mobile-drawer.open a {
    opacity: 1; transform: translateY(0);
  }
  .mobile-drawer.open a:nth-child(1) { transition-delay: 0.15s, 0.15s, 0s, 0s; }
  .mobile-drawer.open a:nth-child(2) { transition-delay: 0.22s, 0.22s, 0s, 0s; }
  .mobile-drawer.open a:nth-child(3) { transition-delay: 0.29s, 0.29s, 0s, 0s; }
  .mobile-drawer.open a:nth-child(4) { transition-delay: 0.36s, 0.36s, 0s, 0s; }
  .mobile-drawer a:hover { color: var(--rose); padding-left: 10px; }
  .mobile-drawer .drawer-cta {
    margin-top: auto;
    font-family: var(--sans); font-size: 13px; letter-spacing: 0.18em; text-transform: uppercase;
    padding: 20px 28px; background: var(--ink); color: var(--cream);
    text-align: center; border: 0;
    font-weight: 400;
    transition-property: opacity, transform, background, color;
    transition-duration: 0.5s, 0.5s, 0.35s, 0.35s;
    transition-timing-function: var(--ease);
    transition-delay: 0s, 0s, 0s, 0s;
  }
  .mobile-drawer.open .drawer-cta { transition-delay: 0.36s, 0.36s, 0s, 0s; }
  .mobile-drawer .drawer-cta:hover { background: var(--rose); color: var(--cream); padding-left: 28px; }

  /* ============ HERO (unchanged) ============ */
  .hero { position: relative; width: 100%; height: 100vh; overflow: hidden; }
  .hero-slides { position: absolute; inset: 0; }
  .hero-slide { position: absolute; inset: 0; background-size: cover; background-position: center; opacity: 0; transform: scale(1.05); transition: opacity 1.4s ease, transform 6s ease;}
  .hero-slide.active { opacity: 1; transform: scale(1); }
  .hero-slide::after { content: ''; position: absolute; inset: 0; background: linear-gradient(rgba(20,16,12,0) 0%, rgba(20,16,12,0.2) 50%, rgba(20,16,12,0.4) 100%); }
  /* Soft atmospheric halo behind the hero copy — two wide, blurry stops
     that darken the photo immediately behind the letters without any
     visible hard edge. No tight blur; the effect reads as gentle
     ambient darkening rather than a drop-shadow. All hero text
     descendants inherit this via .hero-content. */
  .hero-content { position: absolute; inset: 0; z-index: 10; display: flex; flex-direction: column; justify-content: flex-end; padding: 0 80px 80px; text-shadow: 0 2px 24px rgba(0,0,0,0.42), 0 4px 56px rgba(0,0,0,0.32); }
  .hero-eyebrow { font-size: 13px; letter-spacing: 0.28em; text-transform: uppercase; color: #fff; margin-bottom: 48px; }
  /* Editorial two-line eyebrow layout:
       Line 1 — "San Francisco Wedding Photographer"   (white, full weight)
       — 28px hairline rule, left-aligned —
       Line 2 — "Est. 2009"                            (white, 0.72 opacity)
     The short hairline does all the hierarchy work — no color accent, no
     underline — so the two lines read as a quietly refined typographic
     group rather than a labeled badge. The rule is an ::after on
     .eyebrow-main (left-aligned, 1px tall, dimmed white) so it shares a
     baseline with the start of the text above it. Dim-white on Est. 2009
     keeps it subordinate to the main line without introducing weight or
     color contrast that would compete with the rule. */
  .hero-eyebrow .eyebrow-main {
    display: block;
    position: relative;
    padding-bottom: 14px;
    margin-bottom: 14px;
  }
  .hero-eyebrow .eyebrow-main::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: 0;
    width: 28px;
    height: 1px;
    /* Matches .hero-stats border-top so the two horizontal rules in the
       hero composition share one color/opacity — a single hairline
       treatment repeating at top and bottom of the copy group. */
    background: rgba(255, 255, 255, 0.28);
  }
  .hero-eyebrow .eyebrow-year {
    display: inline-block;
    color: #fff;
    font-weight: 500;
  }
  .hero-title { font-family: var(--serif); font-size: clamp(64px, 8vw, 120px); line-height: 0.95; font-weight: 300; color: #fff; margin-bottom: 36px; }
  .hero-title em { font-style: italic; color: var(--rose); }
  /* Subtitle color bumped from 0.7 → 0.92 for readability against the
     hero slideshow — the 0.7 was too washed out on both desktop and
     mobile, especially on lighter slides. Layered text-shadow inherited
     from .hero-content keeps contrast clean on bright frames. */
  .hero-subtitle { font-size: 16px; line-height: 1.8; color: rgba(255,255,255,0.92); max-width: 500px; margin-bottom: 48px; font-weight: 300; }
  .hero-actions { display: flex; align-items: center; gap: 32px; margin-bottom: 64px; }

  /* Hero primary — filled pill matching the site's primary CTA family
     (17/34 padding, 425 weight, 0.22em tracking). Palette stays cream
     fill / ink label since it sits on the ink hero. Hover uses the same
     left-to-right rose sweep as .btn-view-all for a unified gesture. */
  .btn-primary {
    position: relative; overflow: hidden;
    padding: 17px 34px; background: var(--cream); color: var(--ink);
    font-family: var(--sans); font-size: 12px; font-weight: 425;
    letter-spacing: 0.22em; text-transform: uppercase;
    text-decoration: none;
    display: inline-flex; align-items: center; gap: 14px;
    transition: color 0.4s var(--ease);
    border: 0; cursor: pointer;
  }
  .btn-primary::before {
    content: ''; position: absolute; inset: 0;
    background: var(--rose);
    transform: translateX(-101%);
    transition: transform 0.5s var(--ease);
  }
  .btn-primary span, .btn-primary .arrow { position: relative; z-index: 1; }
  .btn-primary .arrow {
    display: inline-block;
    font-size: 12.5px; line-height: 1;
    transition: transform 0.4s var(--ease);
  }
  .btn-primary:hover { color: var(--cream); }
  .btn-primary:hover::before { transform: translateX(0); }
  .btn-primary:hover .arrow { transform: translateX(6px); }

  /* Hero ghost — no fill, no border, just label + arrow on the dark
     hero. Shares the family's 12px / 0.22em / 425 weight / 12.5px arrow
     metrics so the pair reads as tonal variants of one component. */
  .btn-ghost {
    position: relative;
    font-family: var(--sans); font-size: 12px; font-weight: 425;
    letter-spacing: 0.22em; text-transform: uppercase;
    color: rgba(255,255,255,0.7); text-decoration: none;
    display: inline-flex; align-items: center; gap: 14px;
    transition: color 0.25s var(--ease);
  }
  .btn-ghost .arrow {
    display: inline-block;
    font-size: 12.5px; line-height: 1;
    transition: transform 0.4s var(--ease);
  }
  .btn-ghost:hover { color: #fff; }
  .btn-ghost:hover .arrow { transform: translateX(6px); }

  .hero-stats { display: flex; gap: 48px; padding-top: 32px; border-top: 1px solid rgba(255,255,255,0.28); }
  /* No per-element text-shadow — inherits the layered shadow from
     .hero-content. Label opacity stays at 0.88 (bumped from 0.5) since
     that's a visibility fix, not a shadow one. */
  .stat-num { font-family: var(--serif); font-size: 38px; font-weight: 300; color: #fff; line-height: 1; }
  .stat-label { font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: rgba(255,255,255,0.88); margin-top: 8px; }
  .hero-dots { position: absolute; bottom: 32px; right: 80px; z-index: 20; display: flex; gap: 10px; align-items: center; }
  .hero-dot { width: 6px; height: 6px; border-radius: 50%; background: rgba(255,255,255,0.35); border: none; padding: 0; cursor: pointer; transition: background 0.3s, transform 0.3s; }
  .hero-dot.active { background: #fff; transform: scale(1.3); }

  /* ============ SECTIONS COMMON ============ */
  section { padding: 110px 48px; }
  .container { max-width: var(--max); margin: 0 auto; }
  .section-label {
    font-size: 12px; letter-spacing: 0.24em; text-transform: uppercase;
    color: var(--gold); margin-bottom: 22px;
    display: inline-flex; align-items: center; gap: 14px;
  }
  .section-label::before { content: ''; width: 28px; height: 1px; background: var(--gold); }
  .section-title {
    font-family: var(--serif); font-size: clamp(40px, 4.4vw, 64px);
    font-weight: 300; color: var(--ink); line-height: 1.06;
    letter-spacing: -0.008em;
  }
  .section-title em { font-style: italic; color: var(--rose); }

  /* ============ FILMSTRIP — cinematic-a style, deep warm charcoal ============ */
  .filmstrip {
    padding: 22px 0;
    overflow: hidden;
    /* Darker than --dark-gray (#2A2622) but not pure #000 — a deep
       warm charcoal that keeps the subtle red/amber hint of the site
       palette, so the filmstrip sits against the About section
       without the clinical chill of true black. */
    background: #15120F;
    border-top: 1px solid rgba(0,0,0,0.5);
    border-bottom: 1px solid rgba(0,0,0,0.5);
  }
  .fs-viewport {
    overflow: hidden;
    position: relative;
    /* Grab affordance — the filmstrip is draggable. */
    cursor: grab;
    /* Let the browser handle vertical page scroll, but route horizontal
       gestures to our pointer handlers so swipes can move the strip. */
    touch-action: pan-y;
  }
  .fs-viewport.is-dragging {
    cursor: grabbing;
    /* Keep text/images from selecting or ghost-dragging mid-swipe. */
    user-select: none;
    -webkit-user-select: none;
  }
  .fs-viewport.is-dragging .fs-inner {
    /* Disable the 0.8s zoom transition during drag so the track feels
       1:1 with the pointer instead of easing behind it. */
    transition: none;
  }
  .fs-viewport img,
  .fs-viewport .fs-inner {
    -webkit-user-drag: none;
    user-select: none;
  }
  .fs-track {
    display: flex;
    gap: 0;
    width: max-content;
    will-change: transform;
  }
  .fs-cell {
    position: relative;
    width: 460px; height: 315px;
    flex: 0 0 auto;
    /* Matches the section background so image gaps disappear cleanly
       against the deep-charcoal bed, keeping the filmstrip reading as
       a single ribbon of frames rather than discrete tiles. */
    background: #15120F;
    border-right: 1px solid rgba(0,0,0,0.55);
    overflow: hidden;
  }
  .fs-inner {
    position: absolute; inset: 0;
    background-size: cover;
    background-position: center;
    transition: transform 0.8s var(--ease), filter 0.6s var(--ease);
  }
  .fs-cell:hover .fs-inner {
    transform: scale(1.04);
  }

  /* ============ ABOUT ============ */
  .about { background: var(--warm); padding: 88px 88px; }
  .about-grid {
    max-width: var(--max); margin: 0 auto;
    display: grid;
    grid-template-columns: 460px 1fr;
    gap: 96px;
    align-items: start;
  }
  .about-img-wrap {
    position: relative;
    width: 100%;
    max-width: 460px;
  }
  /* Frame clips the photo so the hover scale happens inside a fixed box
     while the rose name tag — anchored to the wrap, not the frame — stays
     parked in place, letting the photo swell underneath it. */
  .about-img-frame {
    width: 100%;
    aspect-ratio: 1 / 1;
    overflow: hidden;
  }
  .about-img {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: cover;
    transition: transform 1s var(--ease);
    will-change: transform;
  }
  .about-img-wrap:hover .about-img { transform: scale(1.05); }
  .about-tag {
    position: absolute; left: -20px; bottom: -20px;
    background: var(--rose);
    padding: 16px 22px;
    z-index: 2;
  }
  .about-tag-name {
    font-family: var(--serif); font-size: 20px;
    color: var(--cream); line-height: 1.15;
    font-weight: 400; letter-spacing: 0.005em;
  }
  .about-tag-role {
    font-family: var(--sans); font-size: 10px;
    letter-spacing: 0.24em; text-transform: uppercase;
    color: rgba(248,244,239,0.88); margin-top: 6px;
    font-weight: 400;
  }

  .about-body { padding-top: 8px; }
  .about-body .section-title { margin: 6px 0 32px; }
  .about-lead {
    font-family: var(--serif); font-size: 22px; line-height: 1.55;
    color: var(--ink); font-weight: 300;
    margin-bottom: 24px;
  }
  .about-lead em { font-style: italic; color: var(--rose); }
  .about-text {
    font-size: 17px; line-height: 1.85;
    color: var(--charcoal); margin-bottom: 22px;
    font-weight: 300;
    max-width: 640px;
  }
  /* "Get To Know Me →" — mirrors the .ws-card-view micro-interaction
     (underline sweeps in from the left, arrow glides 5px right) but is
     triggered by the link's own :hover since this is a standalone link,
     not an overlay on a parent card. */
  .about-know-link {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    margin-top: 6px;
    font-family: var(--sans);
    font-size: 11px;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    font-weight: 400;
    color: var(--ink);
    text-decoration: none;
  }
  .about-know-link-label {
    position: relative;
    padding-bottom: 4px;
  }
  .about-know-link-label::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 1px;
    background: var(--ink);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 0.55s var(--ease);
  }
  .about-know-link .arrow {
    display: inline-block;
    transition: transform 0.4s var(--ease);
  }
  .about-know-link:hover .about-know-link-label::after {
    transform: scaleX(1);
  }
  .about-know-link:hover .arrow {
    transform: translateX(5px);
  }
  .about-pillars {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 32px;
    margin: 48px 0 36px;
    padding-top: 36px;
    border-top: 1px solid var(--sand);
  }
  .pillar { display: flex; flex-direction: column; gap: 14px; }
  .pillar-num {
    font-family: var(--serif); font-style: italic;
    font-size: 30px; font-weight: 300;
    color: var(--rose); line-height: 1;
  }
  .pillar-text {
    font-size: 15.5px; line-height: 1.7;
    color: var(--charcoal);
    font-weight: 300;
  }
  .pillar-text strong {
    font-weight: 500; display: block;
    font-family: var(--sans); font-size: 11px;
    letter-spacing: 0.18em; text-transform: uppercase;
    color: var(--ink); margin-bottom: 10px;
  }

  .press-row {
    margin-top: 8px;
    padding-top: 28px; border-top: 1px solid var(--sand);
  }
  .press-label {
    display: block;
    font-size: 10px; letter-spacing: 0.24em; text-transform: uppercase;
    color: var(--muted);
    margin-bottom: 24px;
  }
  .press-image {
    display: block;
    width: 100%;
    max-width: 640px;
    height: auto;
  }

  /* ============ WEDDINGS / PORTRAITS — 2x2 landscape ============
     Padding matches the homepage .investment section (88px on all four
     sides) and the inner .container inherits the site's default
     max-width (var(--max) = 1560px, centered). That keeps the Wedding
     Stories / Portrait Sessions grids flush with "Your Story, Artfully
     Preserved." so the homepage rhythm stays coherent top-to-bottom. */
  .gallery-section { padding: 88px 88px; }
  .gallery-head {
    display: flex; justify-content: space-between; align-items: flex-end;
    margin-bottom: 56px;
    gap: 40px;
  }
  .gallery-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 4px;
  }
  .gallery-footer {
    display: flex; justify-content: center;
    margin-top: 64px;
  }
  .gallery-card {
    position: relative;
    display: block;
    overflow: hidden;
    cursor: pointer;
    text-decoration: none;
    color: inherit;
  }
  .gallery-card-img {
    width: 100%;
    aspect-ratio: 3 / 2;
    overflow: hidden;
    position: relative;
    background: #000;
  }
  .gallery-card-img .img-inner {
    position: absolute; inset: -5% 0;
    background-size: cover;
    background-position: center;
    transform: translate3d(0, var(--py, 0px), 0) scale(var(--hscale, 1));
    transition: transform 1.1s var(--ease), filter 0.7s var(--ease);
    will-change: transform;
  }
  .gallery-card:hover .img-inner {
    --hscale: 1.06;
    filter: brightness(0.92);
  }
  .gallery-card::after {
    content: '';
    position: absolute; inset: 0;
    background: linear-gradient(180deg, rgba(20,16,12,0) 45%, rgba(20,16,12,0.8) 100%);
    opacity: 0.85;
    transition: opacity 0.5s var(--ease);
    pointer-events: none;
  }
  .gallery-card:hover::after { opacity: 1; }
  .gallery-overlay {
    position: absolute; left: 0; right: 0; bottom: 0;
    z-index: 2;
    padding: 40px 36px 32px;
    transform: translateY(0);
    transition: transform 0.5s var(--ease);
  }
  .gallery-names {
    font-family: var(--serif); font-size: 28px; font-weight: 300;
    color: #fff; letter-spacing: 0.005em;
    line-height: 1.1;
  }
  .gallery-names em { font-style: italic; color: var(--rose); }
  .gallery-venue {
    font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
    color: rgba(255,255,255,0.75); margin-top: 10px;
  }
  .gallery-view {
    display: inline-flex; align-items: center; gap: 10px;
    margin-top: 18px;
    font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase;
    color: rgba(255,255,255,0);
    transform: translateY(10px);
    transition: color 0.4s var(--ease), transform 0.5s var(--ease);
  }
  .gallery-view .arrow { transition: transform 0.4s var(--ease); }
  .gallery-card:hover .gallery-view {
    color: #fff;
    transform: translateY(0);
  }
  .gallery-card:hover .gallery-view .arrow { transform: translateX(5px); }

  /* Primary CTA — the one arrow-pill component used everywhere:
     Send Inquiry, View All Galleries, View All Sessions, Plan Your
     Story, See My Film / Commercial Work, Inquire About Your Date,
     Check Your Date. Only the color palette changes per background;
     everything typographic (weight, arrow size, padding, letter-
     spacing, ::before sweep) is shared so the family reads as one.
     Font-weight 450 + 13px arrow match the 500/14 "heavier" impression
     of the original About buttons, but at half a step so cream-on-ink
     variants don't read as too bold. Requires the variable Jost axis
     (Jost:wght@300..500) loaded in each page <head>. */
  .btn-view-all {
    position: relative; overflow: hidden;
    display: inline-flex; align-items: center; gap: 14px;
    padding: 17px 34px;
    background: var(--ink); color: var(--cream);
    font-family: var(--sans); font-size: 12px; font-weight: 425;
    letter-spacing: 0.22em; text-transform: uppercase;
    border: 0; text-decoration: none;
    flex-shrink: 0; cursor: pointer;
    transition: color 0.4s var(--ease);
  }
  .btn-view-all::before {
    content: ''; position: absolute; inset: 0;
    background: var(--rose);
    transform: translateX(-101%);
    transition: transform 0.5s var(--ease);
  }
  .btn-view-all span, .btn-view-all .arrow { position: relative; z-index: 1; }
  .btn-view-all .arrow {
    font-size: 12.5px; line-height: 1;
    transition: transform 0.4s var(--ease);
  }
  .btn-view-all:hover::before { transform: translateX(0); }
  .btn-view-all:hover .arrow { transform: translateX(6px); }

  .portraits-section { background: var(--warm); }

  /* ============ EXPERIENCE ============ */
  .experience { background: var(--ink); padding: 88px 88px; position: relative; }
  .experience .section-label { color: var(--gold); }
  .experience .section-label::before { background: var(--gold); }
  .experience .section-title { color: var(--cream); }
  .exp-head { text-align: center; margin-bottom: 72px; }
  .exp-head .section-label { justify-content: center; }

  .exp-rail { position: relative; }
  .exp-img-row,
  .exp-body-row {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 36px;
  }
  .exp-img {
    position: relative;
    width: 100%;
    aspect-ratio: 4 / 3;
    background-size: cover;
    background-position: center;
    filter: saturate(0.94) brightness(0.95);
    cursor: pointer;
    opacity: 0;
    transform: translateY(64px);
    overflow: hidden;
    will-change: transform;
  }
  .exp-img::before {
    content: '';
    position: absolute; inset: 0;
    background-image: inherit;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    transform: scale(1);
    transition: transform 1s var(--ease);
    will-change: transform;
  }
  .exp-img:hover::before {
    transform: scale(1.12);
  }
  .exp-timeline {
    position: relative;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    column-gap: 36px;
    margin: 28px 0 32px;
    height: 18px;
    align-items: center;
  }
  .exp-timeline-line {
    position: absolute;
    left: 11.5%; right: 11.5%;
    top: 50%;
    height: 1px;
    background: linear-gradient(90deg, rgba(212,174,110,0.2) 0%, rgba(212,174,110,0.55) 50%, rgba(212,174,110,0.2) 100%);
    transform: translateY(-50%);
  }
  .exp-timeline-dot {
    width: 12px; height: 12px;
    border-radius: 50%;
    background: var(--gold);
    justify-self: center;
    position: relative;
    z-index: 2;
    box-shadow: 0 0 0 4px var(--ink);
    transition: transform 0.5s var(--ease), box-shadow 0.5s var(--ease), background 0.45s var(--ease);
  }
  .exp-timeline-dot.is-active {
    transform: scale(1.4);
    background: var(--rose);
    box-shadow: 0 0 0 4px var(--ink), 0 0 0 9px rgba(196,120,106,0.2);
  }

  .exp-body {
    display: flex; flex-direction: column; text-align: left;
    opacity: 0;
    transform: translateY(64px);
  }
  .exp-img {
    transition: opacity 1.4s var(--ease), transform 1.1s var(--ease);
  }
  .exp-body {
    transition: opacity 1.4s var(--ease), transform 1.4s var(--ease);
  }
  .exp-rail.visible .exp-img,
  .exp-rail.visible .exp-body { opacity: 1; transform: translateY(0); }
  .exp-rail.visible .exp-img:nth-child(1),
  .exp-rail.visible .exp-body:nth-child(1) { transition-delay: 0s; }
  .exp-rail.visible .exp-img:nth-child(2),
  .exp-rail.visible .exp-body:nth-child(2) { transition-delay: 0.16s; }
  .exp-rail.visible .exp-img:nth-child(3),
  .exp-rail.visible .exp-body:nth-child(3) { transition-delay: 0.32s; }
  .exp-rail.visible .exp-img:nth-child(4),
  .exp-rail.visible .exp-body:nth-child(4) { transition-delay: 0.48s; }

  .exp-num {
    font-family: var(--serif); font-style: italic;
    font-size: 24px; color: var(--gold);
    margin-bottom: 16px; line-height: 1;
  }
  .exp-title {
    font-size: 12px; letter-spacing: 0.22em; text-transform: uppercase;
    color: var(--cream); font-weight: 400;
    margin-bottom: 18px;
  }
  .exp-desc {
    font-size: 15.5px; line-height: 1.8;
    color: rgba(248,244,239,0.68); font-weight: 300;
  }

  .exp-pull {
    margin: 88px auto 0;
    max-width: 820px;
    text-align: center;
    border-top: 1px solid rgba(255,255,255,0.1);
    padding-top: 52px;
  }
  .exp-quote {
    font-family: var(--serif); font-size: clamp(22px, 2.4vw, 30px);
    line-height: 1.5; font-style: italic; color: var(--cream);
    margin-bottom: 24px; font-weight: 300;
  }
  /* Per-line clip masks. Each mask is a block the width of the quote;
     its inner span rises from translateY(110%) to 0 with a staggered
     delay set per line in main.js. padding-bottom + negative margin
     preserves italic descender space below the clip line. */
  .exp-quote .line-mask {
    display: block;
    overflow: hidden;
    padding-bottom: 0.2em;
    margin-bottom: -0.2em;
    line-height: 1.5;
  }
  .exp-quote .line-inner {
    display: inline-block;
    transform: translateY(115%);
    transition: transform 1.4s cubic-bezier(0.22, 1, 0.36, 1);
    will-change: transform;
  }
  .exp-pull.words-in .exp-quote .line-inner {
    transform: translateY(0);
  }
  .exp-pull .exp-quote-attr {
    opacity: 0;
    transform: translateY(10px);
    transition: opacity 0.7s ease-out, transform 0.7s ease-out;
    transition-delay: var(--attr-delay, 0.8s);
  }
  .exp-pull.words-in .exp-quote-attr {
    opacity: 1;
    transform: translateY(0);
  }
  /* Hash-load: snap to final state with no transition churn. */
  .exp-pull.words-instant .exp-quote .line-inner,
  .exp-pull.words-instant .exp-quote-attr {
    transition: none !important;
    transition-delay: 0s !important;
  }
  .exp-quote-attr {
    font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase;
    color: var(--gold);
  }

  /* ============ TESTIMONIAL CAROUSEL ============ */
  .testimonials { background: var(--cream); padding: 88px 88px; }
  .testi-header { text-align: center; margin-bottom: 56px; }
  .testi-header .section-label { justify-content: center; }
  .testi-footer {
    display: flex; justify-content: center; align-items: center;
    gap: 28px;
    margin-top: 18px;
  }
  .testi-btn {
    position: relative;
    width: 48px; height: 48px;
    border: 0;
    background: transparent;
    cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0;
    color: var(--sand);
    transition: color 0.3s var(--ease), opacity 0.3s var(--ease);
  }
  .testi-btn svg {
    width: 32px; height: 32px;
    stroke-width: 1.4;
    transition: transform 0.35s var(--ease);
  }
  .testi-btn:hover { color: var(--rose); }
  .testi-btn:hover.testi-btn-prev svg { transform: translateX(-3px); }
  .testi-btn:hover.testi-btn-next svg { transform: translateX(3px); }
  .testi-btn:disabled { opacity: 0.35; cursor: not-allowed; color: var(--sand); }
  .testi-btn:disabled:hover svg { transform: none; }

  .testi-viewport {
    width: 100vw;
    position: relative;
    left: 50%;
    margin-left: -50vw;
    padding: 20px 0 28px 0;
    margin-top: 12px;
    overflow: hidden;
    cursor: grab;
  }
  .testi-viewport.is-dragging { cursor: grabbing; }
  .testi-track {
    display: flex;
    gap: 28px;
    padding: 0 28px;
    transition: transform 0.75s var(--ease);
    will-change: transform;
  }
  .testi-card {
    flex: 0 0 400px;
    background: var(--warm);
    padding: 0;
    position: relative;
    display: flex; flex-direction: column;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    border-radius: 8px;
    overflow: hidden;
    transition: background 0.45s var(--ease), transform 0.45s var(--ease), box-shadow 0.45s var(--ease);
  }
  .testi-card:hover {
    background: #F0E7D6;
    transform: translateY(-4px);
    box-shadow: 0 12px 24px -14px rgba(26,23,20,0.32);
  }
  .testi-card-body {
    padding: 0 32px 34px;
    display: flex; flex-direction: column;
    flex-grow: 1;
    position: relative;
  }
  .testi-img {
    width: 100%;
    aspect-ratio: 3 / 2;
    background-size: cover; background-position: center;
    margin-bottom: 0;
    position: relative;
    overflow: hidden;
  }
  .testi-img::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image: inherit;
    background-size: cover;
    background-position: center;
    transform: scale(1);
    transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
    will-change: transform;
  }
  .testi-card:hover .testi-img::before { transform: scale(1.06); }
  /* Placeholder state — shown until final testimonial photos are dropped in.
     Warm two-tone gradient keyed to the cream/taupe palette, with a quiet
     italic serif label so the slot reads intentionally unfinished rather
     than broken. The ::before zoom layer is suppressed because there's no
     image to zoom. Swap the .is-placeholder class out and add
     style="background-image:url(...)" to activate the normal image state. */
  .testi-img.is-placeholder {
    background: linear-gradient(135deg, #EFE5D6 0%, #CDB89B 100%);
  }
  .testi-img.is-placeholder::before { background-image: none; }
  .testi-card:hover .testi-img.is-placeholder::before { transform: none; }
  .testi-img.is-placeholder::after {
    content: "Photograph to follow";
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    font-family: 'Cormorant Garamond', Georgia, serif;
    font-style: italic;
    font-size: 15px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: rgba(42, 38, 34, 0.36);
    pointer-events: none;
    white-space: nowrap;
  }
  .testi-mark {
    font-family: 'Playfair Display', Georgia, serif;
    font-style: italic; font-weight: 400;
    font-size: 84px; line-height: 0.4;
    color: var(--rose);
    opacity: 0.85;
    letter-spacing: -0.02em;
    margin: -20px 0 48px 0;
    padding-left: 6px;
    display: block;
  }
  .testi-text {
    font-size: 16.5px; line-height: 1.75; color: var(--charcoal);
    margin-top: 0;
    font-style: italic; font-family: var(--serif); font-weight: 400;
    flex-grow: 1;
  }
  .testi-couple {
    margin-top: 28px; padding-top: 22px;
    border-top: 1px solid var(--sand);
    display: flex; justify-content: space-between; align-items: flex-end;
    gap: 18px;
  }
  .testi-couple-meta { flex: 1 1 auto; min-width: 0; }
  .testi-names {
    font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase;
    color: var(--rose); font-weight: 500;
  }
  .testi-venue {
    font-size: 14px; color: var(--muted); margin-top: 6px;
    font-style: italic; font-family: var(--serif);
  }
  .testi-view {
    position: absolute;
    right: 18px; bottom: 16px;
    display: inline-flex; align-items: center; gap: 8px;
    font-family: var(--sans); font-size: 10px;
    letter-spacing: 0.2em; text-transform: uppercase;
    color: #FBF6EC; font-weight: 500;
    text-shadow: 0 1px 14px rgba(0,0,0,0.45);
    opacity: 0;
    transform: translateY(6px);
    transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
    z-index: 2;
    pointer-events: none;
  }
  .testi-view .arrow { transition: transform 0.35s var(--ease); }
  /* Underline sweeps left-to-right under "View Gallery" when the card
     is hovered. A background gradient sized 0 → 100% gives us a smoothly
     animatable underline that hugs only the text (not the arrow). */
  .testi-view-text {
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-position: 0 100%;
    background-size: 0% 1px;
    padding-bottom: 3px;
    transition: background-size 0.55s var(--ease) 0.12s;
  }
  .testi-card:hover .testi-view { opacity: 1; transform: translateY(0); }
  .testi-card:hover .testi-view .arrow { transform: translateX(5px); }
  .testi-card:hover .testi-view-text { background-size: 100% 1px; }

  .testi-dots {
    display: flex; justify-content: center; align-items: center; gap: 8px;
  }
  .testi-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--sand);
    border: 0; padding: 0; cursor: pointer;
    transition: background 0.3s, transform 0.3s;
  }
  .testi-dot.active { background: var(--rose); transform: scale(1.4); }

  /* ============ INVESTMENT ============ */
  .investment { background: var(--warm); padding: 88px 88px; }
  .investment-inner {
    max-width: var(--max); margin: 0 auto;
    display: grid; grid-template-columns: 7fr 5fr;
    gap: 80px; align-items: start;
  }
  .investment-img-wrap {
    overflow: hidden;
    position: relative;
  }
  .investment-copy .section-title { margin: 6px 0 28px; }
  .investment-lead {
    font-size: 17.5px; line-height: 1.8; color: var(--charcoal);
    margin-bottom: 36px; font-weight: 300;
    max-width: 600px;
  }
  .investment-img {
    width: 100%;
    aspect-ratio: 3 / 2;
    overflow: hidden;
    position: relative;
  }
  .investment-img .img-inner {
    position: absolute; inset: -5% 0;
    background-size: cover;
    background-position: center;
    transform: translate3d(0, var(--py, 0px), 0) scale(var(--hscale, 1));
    transition: transform 1.1s var(--ease);
    will-change: transform;
  }
  .investment-copy:hover .investment-img .img-inner { --hscale: 1.02; }
  .investment-note {
    font-family: var(--serif); font-style: italic; font-size: 17px;
    color: var(--muted); margin-top: 28px;
    padding-top: 24px; border-top: 1px solid var(--sand);
    line-height: 1.65;
  }
  /* Investment Check Your Date — outline variant of .btn-view-all.
     Starts transparent with an ink hairline + ink label. The ::before
     sweep is repainted ink (instead of rose), so hovering slides a dark
     fill across the button and flips the label to cream. Same 17/34
     bounding box as the filled siblings — the 1px border is absorbed
     into the padding so the overall button size matches. */
  .investment-cta {
    margin-top: 28px;
    background: transparent;
    color: var(--ink);
    border: 1px solid var(--ink);
    padding: 16px 33px;
  }
  .investment-cta::before { background: var(--ink); }
  .investment-cta:hover { color: var(--cream); }

  .packages {
    display: flex; flex-direction: column;
    border-top: 1px solid var(--sand);
  }
  .pkg {
    display: grid;
    grid-template-columns: 48px 1fr auto;
    align-items: center;
    padding: 28px 0;
    border-bottom: 1px solid var(--sand);
    gap: 24px;
    transition: padding-left 0.4s var(--ease);
  }
  .pkg:hover { padding-left: 12px; }
  .pkg-idx {
    font-family: var(--serif); font-style: italic; font-size: 22px;
    color: var(--rose); line-height: 1;
  }
  .pkg-name {
    font-size: 13px; letter-spacing: 0.16em; text-transform: uppercase;
    color: var(--ink); font-weight: 400;
  }
  .pkg-desc {
    font-size: 16px; color: var(--muted); margin-top: 8px;
    font-family: var(--serif); font-style: italic;
    line-height: 1.5;
  }
  .pkg-price {
    font-family: var(--serif); font-size: 30px; font-weight: 300;
    color: var(--ink); text-align: right;
  }

  .includes-box {
    background: var(--cream);
    padding: 36px;
    margin-top: 32px;
    border-radius: 8px;
  }
  .includes-title {
    font-size: 10px; letter-spacing: 0.26em; text-transform: uppercase;
    color: var(--gold); margin-bottom: 22px;
  }
  .include-item {
    display: flex; gap: 18px;
    padding: 16px 0;
    border-bottom: 1px solid var(--warm);
    font-size: 16px; line-height: 1.6; color: var(--charcoal);
  }
  .include-item:last-child { border-bottom: none; }
  .include-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--rose); flex-shrink: 0; margin-top: 8px;
  }
  .invest-cta { margin-top: 36px; }

  .btn-invest {
    position: relative; overflow: hidden;
    display: inline-block;
    padding: 18px 40px;
    background: var(--ink); color: var(--cream);
    font-size: 12px; letter-spacing: 0.2em; text-transform: uppercase;
    text-decoration: none;
    transition: color 0.5s var(--ease);
  }
  .btn-invest::before {
    content: ''; position: absolute; inset: 0;
    background: var(--rose);
    transform: translateX(-101%);
    transition: transform 0.5s var(--ease);
  }
  .btn-invest span { position: relative; z-index: 1; }
  .btn-invest:hover { color: var(--cream); }
  .btn-invest:hover::before { transform: translateX(0); }

  /* ============ LET'S CONNECT — dark inquire form (cinematic A adapted) ============ */
  .connect {
    background: var(--ink);
    padding: 104px 88px;
    position: relative;
    overflow: hidden;
    color: var(--cream);
    /* Anchor offset — when #contact is jumped to via any
       href="#contact" link (nav CTA, mobile drawer CTA, Check Your
       Date button), reserve --nav-h at the top so the section's
       upper edge lines up exactly with the bottom of the fixed nav
       bar instead of disappearing behind it. scroll-margin-top only
       affects anchor navigation, not layout, so the section still
       paints flush against the preceding block. */
    scroll-margin-top: var(--nav-h);
  }
  .connect::before {
    content: '';
    position: absolute; top: 0; right: -10%;
    width: 55%; height: 100%;
    background-size: cover; background-position: center;
    background-image: url('../images/photos/home/contact/contact-bg.jpg');
    filter: contrast(1.04);
    opacity: 0.85;
    -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 45%);
    mask-image: linear-gradient(90deg, transparent 0%, #000 45%);
    pointer-events: none;
    z-index: 0;
  }
  .connect-inner {
    max-width: var(--max); margin: 0 auto;
    position: relative; z-index: 2;
  }
  .connect-head { margin-bottom: 56px; max-width: 720px; }
  .connect .section-label { color: var(--rose); }
  .connect .section-label::before { background: var(--rose); }
  .connect-title {
    font-family: var(--serif); font-size: clamp(44px, 5.2vw, 72px);
    font-weight: 300; color: var(--cream);
    line-height: 1.08; letter-spacing: -0.01em;
    margin: 4px 0 24px;
  }
  .connect-title em { font-style: italic; color: var(--rose); }
  .connect-body {
    font-family: var(--serif); font-size: 21px;
    line-height: 1.6; color: rgba(248,244,239,0.7);
    font-weight: 300;
    max-width: 640px;
  }

  .connect-form {
    display: grid; grid-template-columns: 1fr 1fr;
    gap: 1px;
    background: rgba(255,255,255,0.08);
    border: 1px solid rgba(255,255,255,0.1);
    max-width: 920px;
  }
  /* Global [hidden] display:none is normally applied by the UA
     stylesheet, but .connect-form's display:grid class rule wins on
     specificity and keeps the form visible after submission. Force
     hidden here so JS .hidden = true actually removes it from view. */
  .connect-form[hidden] { display: none; }
  .field {
    background: var(--dark-gray);
    padding: 24px 28px;
    display: flex; flex-direction: column; gap: 10px;
  }
  .field.full { grid-column: 1 / -1; }
  .field label {
    font-family: var(--sans); font-size: 10px;
    letter-spacing: 0.24em; text-transform: uppercase;
    color: rgba(248,244,239,0.5); font-weight: 400;
    display: inline-flex; align-items: center; gap: 9px;
  }
  /* A tiny rose dot next to the label marks fields that must be filled in.
     Uses :has() so required-ness is driven entirely by the input's attribute
     — no separate HTML markup needed on the label. */
  .field:has(input[required]) label::after,
  .field:has(textarea[required]) label::after,
  .field:has(select[required]) label::after {
    content: '';
    width: 4px; height: 4px;
    border-radius: 50%;
    background: var(--rose);
    flex-shrink: 0;
  }
  .field input,
  .field textarea,
  .field select {
    background: transparent; border: 0; outline: 0;
    color: var(--cream); font-family: var(--serif);
    font-size: 20px; font-weight: 300;
    padding: 4px 0;
    border-bottom: 1px solid rgba(255,255,255,0.14);
    transition: border-color 0.3s var(--ease);
    width: 100%;
  }
  .field input::placeholder,
  .field textarea::placeholder { color: rgba(248,244,239,0.35); font-style: italic; }
  .field select { color: var(--cream); }
  .field select option { background: var(--ink); color: var(--cream); }
  .field input:focus,
  .field textarea:focus,
  .field select:focus { border-bottom-color: var(--rose); }
  .field textarea {
    resize: vertical; min-height: 100px;
    font-size: 18px; line-height: 1.55;
  }

  /* Date input: subtle red underline on invalid value (format check) */
  .field input:invalid:not(:placeholder-shown):not(:focus) {
    border-bottom-color: rgba(229, 115, 115, 0.6);
  }
  .submit-row {
    grid-column: 1 / -1;
    background: var(--dark-gray);
    padding: 24px 28px;
    display: flex; justify-content: space-between; align-items: center;
    gap: 20px; flex-wrap: wrap;
  }
  .submit-row small {
    font-family: var(--sans); font-size: 12px;
    letter-spacing: 0.16em; text-transform: uppercase;
    color: rgba(248,244,239,0.55);
  }
  /* Send Inquiry — same primary arrow-pill as .btn-view-all, colocated
     with the contact form block. Kept as its own class (not merged into
     .btn-view-all) so the contact section's form styling stays
     self-contained, but the visual contract is identical: 17/34
     padding, 450 weight, 13px arrow, rose ::before sweep. */
  .btn-submit {
    position: relative; overflow: hidden;
    display: inline-flex; align-items: center; gap: 14px;
    padding: 17px 34px;
    background: var(--ink); color: var(--cream);
    font-family: var(--sans); font-size: 12px; font-weight: 425;
    letter-spacing: 0.22em; text-transform: uppercase;
    border: 0; cursor: pointer;
    transition: color 0.4s var(--ease);
  }
  .btn-submit::before {
    content: ''; position: absolute; inset: 0;
    background: var(--rose);
    transform: translateX(-101%);
    transition: transform 0.5s var(--ease);
  }
  .btn-submit span, .btn-submit .arrow { position: relative; z-index: 1; }
  .btn-submit .arrow {
    font-size: 12.5px; line-height: 1;
    transition: transform 0.4s var(--ease);
  }
  .btn-submit:hover::before { transform: translateX(0); }
  .btn-submit:hover .arrow { transform: translateX(6px); }
  /* Disabled/loading state — dimmed, no cursor pointer, sweep blocked. */
  .btn-submit:disabled {
    cursor: default;
    opacity: 0.55;
  }
  .btn-submit:disabled:hover::before { transform: translateX(-101%); }
  .btn-submit:disabled:hover .arrow { transform: none; }

  /* Honeypot — visually and interactively absent for real users, but
     still present in the DOM so bots (which fill every input) will
     check it. Web3Forms discards any submission where botcheck is
     non-empty. Using off-screen positioning instead of display:none
     because some bots skip display:none fields. */
  .connect-botcheck {
    position: absolute !important;
    left: -10000px !important;
    width: 1px !important;
    height: 1px !important;
    opacity: 0 !important;
    pointer-events: none !important;
  }

  /* Error message below the submit button — rose hairline underneath
     so it reads as part of the form chrome, not a system alert. */
  .connect-error {
    margin: 0;
    font-family: var(--sans);
    font-size: 13px;
    letter-spacing: 0.04em;
    color: var(--rose);
    line-height: 1.55;
    max-width: 520px;
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
    pointer-events: none;
  }
  .connect-error.is-visible {
    opacity: 1;
    transform: translateY(0);
  }

  /* Success panel — same max-width as the form so the section doesn't
     visually reflow, same dark-gray tile feel, but sits alone. Fades
     in when the form is hidden. */
  .connect-success {
    max-width: 920px;
    background: var(--dark-gray);
    border: 1px solid rgba(255,255,255,0.1);
    padding: clamp(48px, 7vw, 72px) clamp(28px, 5vw, 64px);
    text-align: left;
    animation: connectSuccessIn 0.8s var(--ease) both;
  }
  .connect-success .section-label {
    color: var(--gold);
    margin-bottom: 18px;
  }
  .connect-success-title {
    font-family: var(--serif);
    font-size: clamp(36px, 4.2vw, 56px);
    font-weight: 300;
    font-style: italic;
    color: var(--cream);
    margin: 0 0 20px;
    line-height: 1.1;
    letter-spacing: -0.01em;
  }
  .connect-success-body {
    font-family: var(--serif);
    font-size: 20px;
    font-weight: 300;
    line-height: 1.55;
    color: rgba(248,244,239,0.82);
    margin: 0;
    max-width: 560px;
  }
  @keyframes connectSuccessIn {
    from { opacity: 0; transform: translateY(18px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  /* ============ FOOTER — bg image with locations ============ */
  footer {
    position: relative;
    overflow: hidden;
    min-height: 560px;
    padding: 0;
    color: var(--cream);
    background: var(--ink);
  }
  .footer-bg {
    position: absolute; inset: -18% 0;
    background-image: url('../images/photos/home/footer/footer-bg.jpg');
    background-size: cover;
    background-position: center;
    z-index: 0;
    transform: translate3d(0, var(--py, 0px), 0);
    will-change: transform;
  }
  .footer-bg::after {
    content: '';
    position: absolute; inset: 0;
    background: linear-gradient(180deg, rgba(20,16,12,0) 0%, rgba(20,16,12,0.18) 55%, rgba(20,16,12,0.62) 100%);
  }
  .footer-inner {
    position: relative; z-index: 1;
    display: flex; flex-direction: column;
    justify-content: flex-end;
    min-height: 560px;
    padding: 100px 80px 40px;
  }
  .footer-hero {
    width: 100%;
    display: flex; justify-content: space-between; align-items: flex-end;
    gap: 48px; flex-wrap: wrap;
  }
  .footer-hero-left { flex: 1 1 auto; min-width: 0; }
  .footer-eyebrow {
    font-size: 12px; letter-spacing: 0.28em; text-transform: uppercase;
    color: rgba(255,255,255,0.75);
    margin-bottom: 26px;
    display: inline-flex; align-items: center; gap: 14px;
  }
  .footer-eyebrow::before {
    content: ''; width: 28px; height: 1px; background: var(--rose);
  }
  .footer-locations {
    font-family: var(--serif); font-weight: 300;
    font-size: clamp(44px, 6vw, 88px);
    line-height: 1.02; color: #fff;
    letter-spacing: -0.01em;
    margin-bottom: 16px;
  }
  .footer-locations em { font-style: italic; color: var(--rose); }
  .footer-tagline {
    text-align: right;
    font-family: var(--serif); font-weight: 300; font-style: italic;
    font-size: clamp(32px, 3.2vw, 45px);
    line-height: 1.3; color: rgba(255,255,255,0.82);
    padding-bottom: 12px;
    flex-shrink: 0;
  }
  .footer-tagline em { font-style: italic; color: var(--rose); }
  .footer-meta {
    width: 100%;
    padding-top: 32px; margin-top: 40px;
    border-top: 1px solid rgba(255,255,255,0.16);
    display: flex; justify-content: space-between; align-items: center;
    gap: 32px; flex-wrap: wrap;
  }
  .footer-nav {
    display: flex; gap: 32px; flex-wrap: wrap;
  }
  .footer-nav a {
    font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
    color: rgba(255,255,255,0.7); text-decoration: none;
    transition: color 0.25s var(--ease);
  }
  .footer-nav a:hover { color: #fff; }
  .footer-social {
    display: flex; gap: 22px; flex-wrap: wrap;
  }
  .footer-social a {
    font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
    color: #fff; text-decoration: none;
    transition: color 0.25s var(--ease);
  }
  .footer-social a:hover { color: var(--rose); }
  .footer-copy {
    font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
    color: rgba(255,255,255,0.55);
  }

  /* ============ FADE UP ============ */
  .fade-up {
    opacity: 0;
    transform: translateY(96px);
    transition:
      opacity 1.3s cubic-bezier(0.33, 0, 0.2, 1),
      transform 2.1s cubic-bezier(0.2, 0.65, 0.2, 1);
  }
  .fade-up.visible { opacity: 1; transform: translateY(0); }
  /* Gallery page prefers a softer, more subtle fade-up — photo tiles are
     dense and the big homepage-style rise felt overbearing in that grid.
     Timing stays the same so the motion feels unhurried, just shorter. */
  body.page-gallery .fade-up { transform: translateY(64px); }
  body.page-gallery .fade-up.visible { transform: translateY(0); }

  /* ============ GALLERY PAGE (story-*) ============
     Namespaced with .story- to avoid colliding with the homepage's
     .gallery-* classes (which drive the Weddings/Portraits cards).
     Nav/footer/fade-up patterns are reused as-is from the homepage. */

  /* Gallery page gets the solid-nav treatment from the top (no hero). */
  body.page-gallery,
  body.page-weddings-index,
  body.page-portraits-index,
  body.page-investment { padding-top: var(--nav-h); }

  /* --- Header: centered text block on muted backdrop.
     Shallow, symmetric padding — the grid section below supplies its own
     top padding, so the header only needs enough breathing room to feel
     like a defined opening, not a standalone hero. Top and bottom match
     so the nav-to-eyebrow distance equals the subtitle-to-section-bottom
     distance regardless of how many lines the subtitle wraps to. */
  .story-header {
    position: relative;
    min-height: 24vh;
    padding: clamp(32px, 3.5vw, 56px) 40px;
    text-align: center;
    overflow: hidden;
    isolation: isolate;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .story-header-backdrop {
    position: absolute; inset: 0;
    background:
      radial-gradient(ellipse at 50% 0%, rgba(196,120,106,0.06) 0%, transparent 55%),
      radial-gradient(ellipse at 20% 100%, rgba(184,150,90,0.05) 0%, transparent 50%),
      linear-gradient(180deg, var(--warm) 0%, var(--cream) 100%);
    z-index: -1;
  }
  .story-header-backdrop::after {
    /* subtle paper grain */
    content: '';
    position: absolute; inset: 0;
    background-image:
      radial-gradient(rgba(26,23,20,0.04) 1px, transparent 1px);
    background-size: 3px 3px;
    opacity: 0.5;
    mix-blend-mode: multiply;
    pointer-events: none;
  }
  .story-header-inner {
    max-width: 820px;
    margin: 0 auto;
    padding: 0 16px;
  }
  .story-eyebrow {
    font-family: var(--sans);
    font-size: 12px;
    letter-spacing: 0.42em;
    text-transform: uppercase;
    color: var(--rose);
    font-weight: 400;
    margin-bottom: 16px;
  }
  .story-title {
    font-family: var(--serif);
    font-weight: 300;
    font-style: normal;
    color: var(--ink);
    font-size: clamp(42px, 5.4vw, 76px);
    line-height: 0.98;
    letter-spacing: -0.01em;
    margin-bottom: 18px;
  }
  .story-title em {
    font-family: 'Playfair Display', var(--serif);
    font-style: italic;
    font-weight: 400;
    color: var(--rose);
    padding: 0 0.06em;
  }
  .story-hairline {
    width: 44px;
    height: 1px;
    background: var(--rose);
    margin: 0 auto 16px;
    opacity: 0.6;
  }
  .story-venue {
    font-family: var(--serif);
    font-style: italic;
    font-weight: 300;
    font-size: clamp(17px, 1.6vw, 22px);
    color: var(--charcoal);
  }
  /* --- Grid: 2 columns desktop, 1 column mobile.
     Left/right padding and inter-image gap match the homepage's
     .gallery-section / .gallery-grid so the two pages feel like one system. */
  .story-grid-section {
    padding: 88px 80px;
    background: var(--cream);
  }
  .story-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px;
  }
  .story-photo {
    margin: 0;
    cursor: zoom-in;
  }
  .story-photo-frame {
    position: relative;
    width: 100%;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    background: var(--warm);
  }
  .story-photo-frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform 1.4s var(--ease), filter 0.8s var(--ease);
    will-change: transform;
  }
  /* Hover: gentle zoom only — no drop shadow, so the grid stays flat and
     editorial and neighboring tiles don't get darkened at the gutters. */
  .story-photo:hover .story-photo-frame img { transform: scale(1.045); }

  /* Desktop 2-col layout: row partners fade together (no per-tile stagger),
     so tiles that share a row don't arrive at different speeds. Row-level
     staggering happens naturally because each row crosses the viewport at
     its own scroll time. Mobile re-enables the odd/even stagger inside the
     @media block below, where each tile IS its own row. */

  /* --- Story footer: quiet signoff beneath the grid --- */
  .story-footer {
    margin: clamp(48px, 6vw, 80px) 0 0;
    padding-top: clamp(32px, 4vw, 56px);
    border-top: 1px solid rgba(26,23,20,0.08);
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 32px;
    flex-wrap: wrap;
  }
  /* Signature composition — small caps eyebrow, italic signature, hairline,
     brand mark. Echoes the story-header stack so the page opens and closes
     on the same visual note. */
  .story-signature {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }
  .story-signoff {
    font-family: var(--sans);
    font-size: 11px;
    letter-spacing: 0.42em;
    text-transform: uppercase;
    color: var(--rose);
    font-weight: 400;
    margin: 0 0 14px;
  }
  .story-author {
    font-family: 'Playfair Display', var(--serif);
    font-style: italic;
    font-weight: 400;
    font-size: clamp(30px, 3.4vw, 44px);
    line-height: 1;
    letter-spacing: -0.005em;
    color: var(--ink);
    /* Playfair italic caps (A, J, K, M, N, W…) have wide left-side swashes
       that overhang the character box. Safari sometimes clips that overhang
       to the element bounds during the .fade-up transform animation, so
       reserve room on both sides — same trick .story-title em uses. */
    padding: 0 0.12em;
    margin: 0 0 14px -0.12em;
  }
  .story-signature-rule {
    display: block;
    width: 36px;
    height: 1px;
    background: var(--rose);
    opacity: 0.55;
    margin: 0 0 14px;
  }
  .story-brand {
    font-family: var(--sans);
    font-size: 10.5px;
    letter-spacing: 0.38em;
    text-transform: uppercase;
    color: var(--muted);
    font-weight: 400;
    margin: 0;
  }
  /* ============ LIGHTBOX ============ */
  .lightbox {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(14, 12, 10, 0.92);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.45s var(--ease);
  }
  .lightbox.open {
    opacity: 1;
    pointer-events: auto;
  }
  .lightbox-stage {
    position: relative;
    width: 92vw;
    height: 88vh;
    max-width: 1600px;
    display: grid;
    place-items: center;
    margin: 0;
    transform: scale(0.985);
    transition: transform 0.55s var(--ease);
  }
  .lightbox.open .lightbox-stage { transform: scale(1); }
  /* Each frame shrink-wraps its image at the image's natural contain-fit
     size. overflow:hidden clips the start-of-cycle overscale so the frame
     reads as a fixed crop. Both frames share the same grid cell so they
     can crossfade in place. */
  .lightbox-frame {
    grid-area: 1 / 1;
    display: block;
    overflow: hidden;
    line-height: 0;
    box-shadow: 0 40px 80px rgba(0,0,0,0.4);
    opacity: 0;
    transition: opacity 1.4s ease;
  }
  .lightbox-frame.active { opacity: 1; }
  /* The zoom-out happens on the image itself, inside its fixed frame —
     matches .hero-slide's opacity 1.4s / transform 6s pairing. */
  .lightbox-img {
    display: block;
    max-width: min(92vw, 1600px);
    max-height: 88vh;
    user-select: none;
    -webkit-user-drag: none;
    transform: scale(1.05);
    transition: transform 6s ease;
  }
  .lightbox-frame.active .lightbox-img { transform: scale(1); }
  .lightbox.is-dragging { cursor: grabbing; }
  .lightbox.is-dragging .lightbox-img,
  .lightbox.is-dragging .lightbox-frame { transition: none; }

  .lightbox-close {
    position: absolute;
    top: 24px; right: 24px;
    width: 48px; height: 48px;
    border: 1px solid rgba(255,255,255,0.25);
    background: rgba(0,0,0,0.2);
    color: rgba(255,255,255,0.88);
    border-radius: 50%;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background 0.35s var(--ease), border-color 0.35s var(--ease), transform 0.35s var(--ease);
  }
  .lightbox-close svg { width: 18px; height: 18px; }
  .lightbox-close:hover {
    background: rgba(255,255,255,0.14);
    border-color: rgba(255,255,255,0.5);
    transform: rotate(90deg);
  }

  .lightbox-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 56px; height: 56px;
    border: 1px solid rgba(255,255,255,0.2);
    background: rgba(0,0,0,0.18);
    color: rgba(255,255,255,0.85);
    border-radius: 50%;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background 0.35s var(--ease), border-color 0.35s var(--ease), color 0.35s var(--ease), opacity 0.35s var(--ease);
  }
  .lightbox-nav svg { width: 20px; height: 20px; }
  .lightbox-nav:hover {
    background: rgba(255,255,255,0.12);
    border-color: rgba(255,255,255,0.5);
    color: #fff;
  }
  .lightbox-nav:disabled {
    opacity: 0.25;
    cursor: default;
  }
  .lightbox-prev { left: 32px; }
  .lightbox-next { right: 32px; }

  /* Lock scroll on body when lightbox is open */
  body.lightbox-open { overflow: hidden; }

  /* ============ WEDDING STORIES INDEX (ws-*) ============
     Listing page: /weddings/index.html. Header reuses the .story-header
     block (same warm-cream backdrop, eyebrow → serif title → hairline →
     italic subtitle stack) so the opening beat matches the individual
     gallery pages a couple will click through to. Below the header, a
     responsive grid of <a> cards — each one a landscape photograph with
     couple names, venue, and a "View Gallery →" cue in an always-visible
     text block below the image. The entire anchor is the click target.
     Grid auto-flows any number of entries; collapses to one column on
     mobile (see responsive blocks further down).
     ================================================================= */
  .ws-grid-section {
    /* Padding mirrors the homepage .investment section (88px on all four
       sides) — the Wedding Stories archive sits inside the same content
       frame as "Your Story, Artfully Preserved." so the listing page and
       the homepage feel like one system. */
    padding: 88px 88px;
    background: var(--cream);
  }
  .ws-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    /* Row gap is tight — the card's own text block (names/venue/view
       gallery) already supplies vertical breathing room between the
       bottom of one row and the top of the next, so the grid's explicit
       row-gap can stay modest. Column gap stays tight so the two
       photographs dominate the row. */
    gap: clamp(36px, 3.2vw, 52px) clamp(20px, 2.4vw, 40px);
    max-width: var(--max);
    margin: 0 auto;
  }
  /* Card anchor — the whole card is clickable. text-decoration: none +
     color: inherit let the inner <h2>/<p>/<span> pick up their own colors
     from the rules below without the default link styling bleeding in. */
  .ws-card {
    display: block;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
  }
  .ws-card-img {
    position: relative;
    width: 100%;
    aspect-ratio: 3 / 2;  /* shared landscape crop — DSLR-native 3:2
                             keeps each photograph at the same footprint
                             so rows align cleanly. */
    overflow: hidden;
    background: var(--warm);
  }
  /* Inner background-image div drives both hover zoom and scroll
     parallax. The 5% vertical overhang (inset: -5% 0) gives the JS
     parallax room to translate the image without exposing the cream
     background. Matches .gallery-card-img .img-inner on the homepage
     so the parallax() loop in main.js can include both via one query. */
  .ws-card-img .img-inner {
    position: absolute; inset: -5% 0;
    background-size: cover;
    background-position: center;
    transform: translate3d(0, var(--py, 0px), 0) scale(var(--hscale, 1));
    transition: transform 1.1s var(--ease), filter 0.7s var(--ease);
    will-change: transform;
  }
  /* Hover: quiet image zoom. No overlay or gradient — the text below
     is already doing the work, so the photograph stays clean. */
  .ws-card:hover .ws-card-img .img-inner {
    --hscale: 1.045;
  }
  /* Text block: always visible beneath the photo. Centered so names,
     venue, and the view-gallery cue stack as a single composed unit. */
  .ws-card-body {
    text-align: center;
    padding: clamp(20px, 2vw, 28px) 8px 0;
  }
  .ws-card-names {
    font-family: var(--serif);
    font-weight: 300;
    font-size: clamp(26px, 2.4vw, 36px);
    line-height: 1.1;
    letter-spacing: -0.005em;
    color: var(--ink);
    margin: 0 0 14px;
  }
  /* Second name is italicized + rose — same treatment the homepage
     featured-wedding cards use for the overlay names, so the visual
     relationship between the two pages reads instantly. */
  .ws-card-names em {
    font-family: 'Playfair Display', var(--serif);
    font-style: italic;
    font-weight: 400;
    color: var(--rose);
    padding: 0 0.04em;
  }
  .ws-card-venue {
    font-family: var(--sans);
    font-size: 11px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--muted);
    margin: 0 0 20px;
  }
  /* "View Gallery →" — a single inline row. On card hover, an underline
     wipes in beneath the label from the left while the arrow nudges
     right; both share the site's ease curve so it feels like one
     gesture. Only the label gets the underline (not the arrow), so the
     arrow can slide freely past the line's right edge. */
  .ws-card-view {
    display: inline-flex;
    /* Baseline alignment keeps the arrow glyph sitting on the same
       typographic baseline as "VIEW GALLERY". align-items:center would
       vertically-center the arrow against the label's box (which is
       taller by 4px of padding-bottom for the hover underline) and drift
       the arrow upward. */
    align-items: baseline;
    gap: 6px;
    font-family: var(--sans);
    font-size: 11px;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    font-weight: 400;
    color: var(--ink);
  }
  .ws-card-view-label {
    position: relative;
    padding-bottom: 4px;
  }
  .ws-card-view-label::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 1px;
    background: var(--ink);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 0.55s var(--ease);
  }
  .ws-card:hover .ws-card-view-label::after {
    transform: scaleX(1);
  }
  .ws-card-view .arrow {
    display: inline-block;
    transition: transform 0.4s var(--ease);
  }
  .ws-card:hover .ws-card-view .arrow {
    transform: translateX(5px);
  }

  /* ============ ABOUT PAGE (ab-*) ============
     Namespaced under .ab- + scoped to body.page-about where useful.
     Design intent: editorial + cinematic — compact text header (no full
     hero), tilted intro polaroid, philosophy pillars that mirror the
     homepage's .about-pillars exactly, asymmetric film-director grid,
     full-bleed "behind the lens" strip, parallax California, masonry
     travel gallery with native aspect ratios, letter-close CTA. Uses
     the established cream/warm/rose/gold palette. */

  /* Photo column bleeds behind the fixed nav, so no top padding here —
     the hero's own padding handles clearance. Nav starts transparent
     (.hero-nav) and fades to solid once .ab-hero scrolls out of view,
     wired up by the heroEl scroll listener in main.js. */

  /* Page-specific fade-up: tighten travel distance so the intro polaroid
     + copy arrive together without the big homepage hero-style lift. */
  body.page-about .fade-up { transform: translateY(56px); }
  body.page-about .fade-up.visible { transform: translateY(0); }

  /* --- STICKY STACK — "page-turn" cover scroll ---
     Each .ab-stack-item is position:sticky at top:0 with height:100vh and
     overflow:hidden. As the user scrolls, the next section (later in DOM)
     slides up from below and paints over the previous one (which remains
     stuck at top:0). The parent .ab-stack is a plain block that inherits
     each child's scroll range. Works on every breakpoint — sections are
     tuned to fit a single mobile viewport on the small end. */
  body.page-about .ab-stack {
    position: relative;
  }
  body.page-about .ab-stack-item {
    position: sticky;
    top: 0;
    height: 100vh;
    overflow: hidden;
  }
  /* On mobile, override any section's own `display` so its inner content
     block centers vertically inside the 100vh sticky frame. Without this,
     sections that anchor content to the top (hero, travel) leave a big
     empty bottom while sections with more content crop below the fold. */
  @media (max-width: 899px) {
    body.page-about .ab-stack-item {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: stretch;
    }
  }

  /* --- HERO — full-bleed B&W backdrop under the title + intro ---
     One 100vh sticky-stack page. A grayscale photograph fills the
     entire section (absolute bg + dark wash), the page title sits at
     the top beneath the fixed nav, and the polaroid + reading copy
     (light-on-dark) live in the remaining space below. */
  .ab-hero {
    position: relative;
    width: 100%;
    min-height: 100vh;
    /* Override the global `section { padding: 110px 48px }` so the
       photo truly bleeds edge-to-edge and all the way behind the nav. */
    padding: 0;
    display: flex;
    flex-direction: column;
    background: var(--ink);
    color: #fff;
    overflow: hidden;
    isolation: isolate;
  }
  .ab-hero-bg {
    position: absolute;
    /* Negative Y inset gives the bg ~140px of vertical slack so the JS
       parallax (--parallax-y up to ±110px) can shift the photo without
       ever revealing an edge outside the cover crop. */
    inset: -140px 0;
    background-size: cover;
    /* Parallax Y offset is driven by JS (--parallax-y on scroll). Using
       background-position keeps parallax on the paint layer and avoids
       conflicting with the Ken Burns transform below. */
    background-position: 50% calc(50% + var(--parallax-y, 0px));
    /* Full grayscale so the photo reads as a moody editorial backdrop. */
    filter: grayscale(1) brightness(0.72) contrast(1.08);
    transform: scale(1.04);
    animation: abHeaderKen 22s var(--ease) forwards;
    z-index: 0;
  }
  @keyframes abHeaderKen {
    from { transform: scale(1.10); }
    to   { transform: scale(1.00); }
  }
  .ab-hero-wash {
    position: absolute; inset: 0;
    /* Gradient tilted to the bottom — the copy column on the right
       sits in the darker zone so the white body text stays legible. */
    background:
      linear-gradient(
        180deg,
        rgba(20,16,12,0.42) 0%,
        rgba(20,16,12,0.48) 40%,
        rgba(20,16,12,0.62) 78%,
        rgba(20,16,12,0.78) 100%
      );
    z-index: 1;
    pointer-events: none;
  }

  /* --- TOP: page title block, sits under the fixed nav --- */
  /* Header floats above the intro so the polaroid + copy block can center
     against the full viewport, not just the space remaining below the
     title. The intro below carries enough top padding to keep this from
     colliding with the copy on shorter screens. */
  .ab-hero-header {
    position: absolute;
    top: 0; left: 0; right: 0;
    z-index: 3;
    pointer-events: none;
  }
  .ab-hero-header-inner {
    padding: clamp(120px, 15vh, 176px) clamp(40px, 5vw, 96px) clamp(20px, 3vh, 40px);
    animation: abHeaderRise 1.4s var(--ease) both 0.15s;
    pointer-events: auto;
  }
  @keyframes abHeaderRise {
    from { opacity: 0; transform: translateY(28px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  .ab-header-eyebrow {
    font-family: var(--sans);
    font-size: 12px;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    color: rgba(255,255,255,0.82);
    margin: 0 0 22px;
    font-weight: 400;
    display: inline-flex; align-items: center; gap: 14px;
  }
  .ab-header-eyebrow::before {
    content: ''; width: 28px; height: 1px; background: var(--rose);
    flex-shrink: 0;
  }
  .ab-header-title {
    font-family: var(--serif);
    font-weight: 300;
    font-size: clamp(56px, 6.8vw, 104px);
    line-height: 0.95;
    letter-spacing: -0.012em;
    color: #fff;
    margin: 0 0 28px;
  }
  .ab-header-title em {
    font-style: italic;
    color: var(--rose);
    padding: 0 0.04em;
    margin: 0 -0.02em;
  }
  .ab-header-sub {
    font-family: var(--sans);
    font-size: 15px;
    line-height: 1.8;
    color: rgba(255,255,255,0.72);
    font-weight: 300;
    max-width: 440px;
    margin: 0;
  }

  /* --- Centered polaroid + copy block.
     Fills the full section so the pair visually centers against the
     viewport rather than the space below the title. Top padding
     reserves room for the overlaid .ab-hero-header so the two never
     collide on short windows. */
  .ab-hero-intro {
    position: relative;
    z-index: 2;
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Asymmetric vertical padding — larger bottom than top (beyond what's
       reserved for the header) biases the centered block upward in the
       viewport. Tuned for a balance that sits slightly above true center
       without hugging the title. */
    padding:
      clamp(240px, 30vh, 330px)
      clamp(40px, 5vw, 96px)
      clamp(88px, 11vh, 150px);
  }
  .ab-hero-intro-inner {
    display: grid;
    /* Content-sized columns (capped at polaroid + copy maxima) so the
       grid tracks collapse to the block's natural width. Combined with
       justify-content: center, this makes the left-of-polaroid and
       right-of-copy offsets perfectly symmetric — no slack on the
       copy side like a 1fr column would create. */
    grid-template-columns: minmax(0, 360px) minmax(0, 640px);
    gap: clamp(56px, 5.5vw, 96px);
    /* Top-align so the polaroid's top edge tracks the headline, not the
       centerline of the (taller) copy column. */
    align-items: start;
    justify-content: center;
    max-width: var(--max);
    width: 100%;
  }
  /* Reading copy — all text rendered light so it reads against the
     darkened B&W backdrop. Headline + signoff lift to pure white,
     body copy uses a softer warm-white for comfortable reading. */
  .ab-hero-intro-copy {
    color: rgba(255,255,255,0.88);
  }
  .ab-hero-intro-copy .section-label {
    color: var(--gold);
  }
  .ab-hero-intro-copy .section-label::before {
    background: var(--gold);
  }
  .ab-hero-intro-copy .section-title {
    color: #fff;
    margin: 0 0 36px;
  }
  .ab-hero-intro-copy .section-title em {
    color: var(--rose);
  }
  .ab-hero-intro-copy .ab-body {
    color: rgba(255,255,255,0.82);
  }
  .ab-hero-intro-copy .ab-signoff-rule {
    opacity: 0.75;
  }
  .ab-hero-intro-copy .ab-signoff-name {
    color: #fff;
  }
  .ab-hero-intro-copy .ab-signoff-role {
    color: rgba(255,255,255,0.82);
    opacity: 1;
  }
  /* Larger hero polaroid — a prominent "print on the wall" against the
     dark backdrop. Sits flush to the left edge of its column so that
     left-of-polaroid mirrors right-of-copy on the far side of the
     section. Slight upward lift adds a sense of pinned asymmetry. */
  .ab-polaroid--hero {
    transform: translateY(-8px);
    max-width: 360px;
    width: 100%;
    justify-self: stretch;
  }
  /* Specificity note: body.page-about .fade-up.visible (0,3,1) wins over
     .ab-polaroid--hero:hover (0,2,0), so without this the hover grow is
     silently overridden by the fade-up reveal transform. The rules below
     re-specify the resting and hover transforms with enough weight
     (0,4,1 / 0,5,1) to win once the polaroid is .visible. */
  body.page-about .ab-polaroid--hero.fade-up.visible {
    transform: translateY(-8px);
  }
  /* On hover: grow the whole polaroid (frame + tape + caption) as a
     single unit. Shadow deepens to sell the lift. */
  body.page-about .ab-polaroid--hero.fade-up.visible:hover {
    transform: translateY(-8px) scale(1.04);
    box-shadow:
      0 1px 2px rgba(0,0,0,0.18),
      0 32px 64px rgba(0,0,0,0.48),
      0 60px 100px rgba(0,0,0,0.32);
  }

  /* --- Scroll hint — bottom-center of the hero ---
     Two stacked open chevrons under a single horizontal "highlight"
     band that sweeps top-to-bottom across the whole stack on one
     2.4s clock. The chevrons themselves always render at a soft dim
     alpha (the track layer); a second, brighter chevron layer sits
     on top of them and is revealed ONLY through a gradient band in
     an SVG mask. As the band translates downward, the portions of
     both carets that fall inside the band briefly brighten — so the
     eye reads a ribbon of light descending across the cue, not a
     pulse tracing each V. The entire cluster also drifts ~20px
     downward each cycle (fading in at the top, out at the bottom)
     so the loop never shows a hard reset. */
  .ab-hero-scroll {
    position: absolute;
    bottom: clamp(28px, 4.2vh, 46px);
    left: 50%;
    transform: translateX(-50%);
    z-index: 4;
    display: flex;
    color: #fff;
    text-decoration: none;
    opacity: 1;
    transition: opacity 0.4s var(--ease);
  }
  .ab-hero-scroll:hover { opacity: 1; }
  .ab-hero-scroll:focus-visible {
    outline: 1px solid rgba(255,255,255,0.55);
    outline-offset: 6px;
  }
  /* Single SVG carries both carets (track + pulse) on one 28×24
     viewBox — top caret V at y=3–11, bottom caret V at y=13–21, so
     their apexes sit 10 units apart in a tight chevron-stack
     cadence. 36×31 display keeps aspect ratio (36 / 28 ≈ 31 / 24).
     Overflow-visible lets the sweep rect live slightly outside the
     viewBox without getting clipped. */
  .ab-hero-scroll-svg {
    width: 36px;
    height: 31px;
    overflow: visible;
    animation: abScrollDrift 2.4s cubic-bezier(0.33, 0, 0.2, 1) infinite;
  }
  .ab-hero-scroll-track { opacity: 0.62; }
  /* The sweep rect is the only thing driving the flash. It's a
     32-unit-tall rectangle filled with a vertical gradient — fully
     transparent at the top and bottom, opaque in a narrow band
     around the middle. The rect is authored at y=0 and animated via
     CSS transform: translateY so it travels from -32 (band parked
     above the chevrons) to +32 (parked below), passing through the
     full chevron stack mid-cycle. Transform is used instead of
     animating the `y` attribute because CSS transform has
     universally reliable support across browsers; linear timing
     gives a constant descent velocity. */
  .ab-hero-scroll-sweep {
    transform: translateY(-32px);
    animation: abScrollSweep 2.4s linear infinite;
  }
  /* Shape drift — entire chevron stack travels ~20px downward across
     the cycle, smooth and continuous, then fades out at the bottom
     and restarts invisible at the top. Long, gradual opacity ramps
     on both ends: the shape brightens as it drifts down through the
     top third of the cycle, holds at full visibility briefly through
     the middle (where the highlight band sweeps across), then softens
     away across the back half. Intermediate opacity stops approximate
     an S-curve for a smoother rise and fall than a two-point fade. */
  @keyframes abScrollDrift {
    0%   { transform: translateY(0);      opacity: 0;    }
    10%  { transform: translateY(2px);    opacity: 0.15; }  /* just emerging    */
    22%  { transform: translateY(4.3px);  opacity: 0.5;  }  /* still rising     */
    36%  { transform: translateY(7.2px);  opacity: 0.9;  }
    46%  { transform: translateY(9.2px);  opacity: 1;    }  /* fully visible    */
    56%  { transform: translateY(11.2px); opacity: 1;    }  /* brief plateau    */
    68%  { transform: translateY(13.7px); opacity: 0.85; }  /* softening begins */
    80%  { transform: translateY(16.2px); opacity: 0.5;  }
    92%  { transform: translateY(18.7px); opacity: 0.15; }  /* almost gone      */
    100% { transform: translateY(20px);   opacity: 0;    }  /* faded out        */
  }
  /* Highlight band sweep — rect translates linearly from -32 (band
     fully above the chevrons) through 0 (band centered between them)
     to +32 (band fully below). Cycle runs continuously; drift
     opacity (below) handles the fade-in / fade-out at the cycle
     edges, so no holds are needed here. */
  @keyframes abScrollSweep {
    0%   { transform: translateY(-32px); }
    100% { transform: translateY( 32px); }
  }
  /* Respect reduced-motion: freeze the sweep and fall back to a very
     gentle opacity breath on the whole hint. Parking the band at y=0
     leaves it centered between the two carets (neither glowing), so
     the static state reads as just "two dim chevrons". */
  @media (prefers-reduced-motion: reduce) {
    .ab-hero-scroll {
      animation: abScrollStaticPulse 3s ease-in-out infinite;
    }
    .ab-hero-scroll-svg {
      animation: none;
      transform: none;
    }
    .ab-hero-scroll-sweep {
      animation: none;
      transform: translateY(0);
    }
    @keyframes abScrollStaticPulse {
      0%, 100% { opacity: 0.55; }
      50%      { opacity: 0.9;  }
    }
  }

  /* (Intro copy + polaroid layout now live inside .ab-hero above.) */

  /* Polaroid — shared between Intro and Travel sections.
     <figure> wrapper + <img> child so the photo loads with a proper
     request; CSS-drawn tape at top, handwritten caption at the bottom. */
  .ab-polaroid {
    position: relative;
    background: #fcf9f2;
    padding: 14px 14px 56px;
    margin: 0;
    box-shadow:
      0 1px 2px rgba(0,0,0,0.08),
      0 22px 44px rgba(26,23,20,0.18),
      0 48px 80px rgba(26,23,20,0.12);
    transition: transform 0.8s var(--ease), box-shadow 0.8s var(--ease);
    max-width: 420px;
    justify-self: center;
    will-change: transform;
  }
  /* (.ab-polaroid--intro removed — hero polaroid now uses .ab-polaroid--hero
     above, and the travel variant has its own rules further down.) */
  .ab-polaroid-tape {
    position: absolute;
    top: -18px;
    left: 50%;
    transform: translateX(-50%) rotate(-5deg);
    width: 118px; height: 30px;
    background: rgba(224, 200, 140, 0.52);
    box-shadow: 0 2px 6px rgba(0,0,0,0.08);
    pointer-events: none;
    z-index: 2;
  }
  /* <img> variant — displays inline, fills the polaroid card, crops to
     a 4:5 portrait frame via object-fit. */
  img.ab-polaroid-img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 5;
    object-fit: cover;
    object-position: center;
    filter: sepia(0.04) contrast(1.02);
  }
  /* Legacy background-image variant (still used by the Travel polaroid). */
  div.ab-polaroid-img {
    width: 100%;
    aspect-ratio: 4 / 5;
    background-size: cover;
    background-position: center;
    filter: sepia(0.04) contrast(1.02);
  }
  .ab-polaroid-caption {
    position: absolute;
    left: 14px; right: 14px; bottom: 18px;
    margin: 0;
    text-align: center;
    font-family: 'Playfair Display', var(--serif);
    font-style: italic;
    font-weight: 400;
    font-size: 16px;
    color: var(--ink);
    letter-spacing: 0.005em;
  }
  .ab-polaroid-caption--script {
    font-size: 15px;
    color: rgba(26, 23, 20, 0.6);
  }

  /* Shared body typography (reused by .ab-hero-intro-copy). */
  .ab-body {
    font-size: 17px;
    line-height: 1.85;
    color: var(--charcoal);
    font-weight: 300;
    margin: 0 0 22px;
    max-width: 56ch;
  }

  /* Signoff — italic name stacked above a hairline rule, with the role
     byline tucked under. Vertical column so the three pieces read as
     one editorial block: byline, separator, credit. */
  .ab-signoff {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 14px;
    margin: 36px 0 0;
  }
  .ab-signoff-rule {
    display: block;
    width: 44px; height: 1px;
    background: var(--rose);
    opacity: 0.7;
    margin: 2px 0;
    flex-shrink: 0;
  }
  .ab-signoff-name {
    font-family: 'Playfair Display', var(--serif);
    font-style: italic;
    font-weight: 400;
    font-size: 26px;
    line-height: 1;
    color: var(--ink);
    padding: 0 0.08em;
    margin-left: -0.08em;
  }
  .ab-signoff-role {
    font-family: var(--sans);
    font-size: 11px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--charcoal);
    opacity: 0.78;
    font-weight: 400;
    padding-left: 0.08em; /* visual alignment with name's optical inset */
  }

  /* --- PHILOSOPHY — its own stacked page, warm ground, single big title ---
     No eyebrow on this one (the big italic "My Philosophy." carries the
     whole page). Warm ground gives a distinct colour-shift from the cream
     Intro above, so the sticky-stack cover reveal reads as a real chapter
     break. The 01/02/03 pillars use the homepage .about-pillars markup. */
  .ab-philo {
    background: var(--warm);
    /* Horizontal padding matches homepage .investment so Philosophy and
       the "Your Story, Artfully Preserved" section feel like one system. */
    padding: clamp(64px, 7.5vh, 104px) 88px;
    position: relative;
    display: grid;
    place-items: center;
    overflow: hidden;
  }
  .ab-philo-inner {
    width: 100%;
    max-width: 1440px;
    margin: 0 auto;
    text-align: center;
  }
  .ab-philo-title {
    font-family: var(--serif);
    font-weight: 300;
    font-size: clamp(52px, 6.4vw, 92px);
    line-height: 1.02;
    letter-spacing: -0.012em;
    color: var(--ink);
    margin: 0 0 clamp(32px, 4.2vh, 56px);
  }
  .ab-philo-title em {
    font-style: italic;
    color: var(--rose);
    padding: 0 0.04em;
    margin: 0 -0.02em;
  }

  /* Bracketing hairlines — one long rule across the top + bottom of the
     pillars row, echoing the homepage's about-pillars treatment. */
  body.page-about .ab-philo-pillars {
    margin: 0 auto;
    padding: clamp(28px, 3.8vh, 44px) 0;
    width: 100%;
    max-width: 1360px;
    border-top: 1px solid var(--sand);
    border-bottom: 1px solid var(--sand);
    gap: clamp(24px, 2.6vw, 40px);
  }

  /* Editorial portrait at the top of each pillar — natural aspect ratio
     preserved so the three images retain their original proportions
     above the existing 01/02/03 + caption. Subtle warm tone + soft
     drop shadow so they sit on the cream ground without stealing
     focus. Hover lifts and warms the print. */
  .ab-philo-pillars .pillar {
    gap: 20px;
  }
  .ab-philo-pillars .pillar-img {
    display: block;
    width: 100%;
    height: auto;
    filter: sepia(0.08) contrast(1.02) brightness(0.98);
    box-shadow:
      0 1px 2px rgba(26,23,20,0.08),
      0 20px 44px rgba(26,23,20,0.14),
      0 44px 72px rgba(26,23,20,0.08);
    transition:
      transform 0.9s var(--ease),
      filter 0.9s var(--ease),
      box-shadow 0.9s var(--ease);
    will-change: transform;
  }
  .ab-philo-pillars .pillar:hover .pillar-img {
    filter: sepia(0) contrast(1.04) brightness(1);
    transform: translateY(-4px);
    box-shadow:
      0 2px 4px rgba(26,23,20,0.10),
      0 28px 56px rgba(26,23,20,0.20),
      0 60px 100px rgba(26,23,20,0.10);
  }

  /* --- FILM DIRECTOR — looping video background behind the original
     two-column photo+copy layout. The video fills the full section via
     absolute positioning; a light wash (just enough to lift text off the
     reel) sits on top; the photo frame on the left and copy on the right
     float above it, mirroring the pre-video layout. */
  .ab-film {
    position: relative;
    overflow: hidden;
    background: var(--ink);
    isolation: isolate;
    /* Horizontal padding matches homepage .investment ("Your Story,
       Artfully Preserved") so the About page's editorial band feels
       flush with the homepage. */
    padding: clamp(64px, 7.5vh, 104px) 88px;
    display: grid;
    place-items: center;
  }
  .ab-film-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    z-index: 0;
    pointer-events: none;
    transform: scale(1.02);
    filter: saturate(1.02) contrast(1.02);
  }
  /* Gentle wash — keeps the reel visible but gives the left-column photo
     and right-column white type enough contrast to read. Much lighter
     than the full-bleed overlay version. */
  .ab-film-wash {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background:
      linear-gradient(
        110deg,
        rgba(12,10,8,0.32) 0%,
        rgba(12,10,8,0.18) 50%,
        rgba(12,10,8,0.28) 100%
      );
  }
  .ab-film-inner {
    position: relative;
    z-index: 2;
    max-width: 1360px;
    width: 100%;
    margin: 0 auto;
    display: grid;
    grid-template-columns: 1.05fr 1fr;
    gap: clamp(56px, 7vw, 96px);
    /* Top-align so the print and the "Beyond Weddings" eyebrow start on
       the same horizontal. Center-alignment read as visually mismatched
       because the caption makes the photo column taller. */
    align-items: start;
  }

  /* Left column: the print. Same 4/3 framed photo as before; a touch
     more shadow so it reads against the live footage behind it. */
  .ab-film-media { position: relative; }
  .ab-film-img-frame { max-height: 72vh; }
  .ab-film-img-frame {
    position: relative;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    background: var(--warm);
    box-shadow:
      0 2px 4px rgba(0,0,0,0.20),
      0 30px 70px rgba(0,0,0,0.45);
  }
  .ab-film-img {
    position: absolute; inset: 0;
    background-size: cover;
    background-position: center;
    filter: contrast(1.04);
    transform: scale(1.04);
    transition: transform 2.4s var(--ease);
    will-change: transform;
  }
  .ab-film-media:hover .ab-film-img { transform: scale(1.12); }
  .ab-film-caption {
    margin-top: 18px;
    font-family: var(--sans);
    font-size: 10.5px;
    letter-spacing: 0.3em;
    text-transform: uppercase;
    /* Lighter than the old warm-ground version so it reads over video. */
    color: rgba(255,255,255,0.78);
    display: flex; align-items: center; gap: 12px;
  }
  .ab-film-caption::before {
    content: ''; width: 20px; height: 1px; background: var(--rose);
  }

  /* Right column: copy. Inverted for light-on-dark readability now that
     the ground is moving footage rather than cream. */
  .ab-film-copy { max-width: 520px; color: #fff; }
  .ab-film-copy .section-label {
    margin-bottom: 24px;
    color: rgba(255,255,255,0.8);
  }
  .ab-film-copy .section-label::before {
    background: rgba(255,255,255,0.55);
  }
  .ab-film-copy .section-title {
    color: #fff;
    margin-bottom: 18px;
  }
  .ab-film-copy .section-title em { color: var(--rose); }
  .ab-film-sub {
    font-family: 'Playfair Display', var(--serif);
    font-style: italic;
    font-weight: 400;
    font-size: clamp(22px, 2vw, 28px);
    color: var(--rose);
    margin-bottom: 28px;
    line-height: 1.3;
    padding: 0 0.04em;
    margin-left: -0.04em;
  }
  .ab-film-body {
    font-size: 17px;
    line-height: 1.85;
    color: rgba(255,255,255,0.90);
    font-weight: 300;
    margin-bottom: 36px;
    max-width: 48ch;
  }
  /* CTA inverted for dark ground: white stroke + white text; hover
     fills white and reveals dark ink. */
  /* Film section sits on var(--ink), so the default ink-on-ink button
     would disappear. Flip the palette: cream fill + ink text, and let
     the base's rose ::before still sweep in on hover — rose reads well
     against both cream and ink so the same animation works on either
     background. */
  .ab-film-cta {
    margin-top: 4px;
    background: var(--cream);
    color: var(--ink);
  }

  /* --- TRAVEL — polaroid on the left, header + editorial mosaic on the
     right. Mirrors the Hero's polaroid-left layout so the About page
     has a recognizable visual rhythm. Same horizontal padding as the
     philosophy + film sections so the content column aligns. */
  .ab-travel {
    position: relative;
    background: var(--ink);
    color: var(--cream);
    /* Horizontal padding matches homepage .investment so Travel sits flush
       with the 'Your Story, Artfully Preserved' band. */
    padding: clamp(80px, 10vh, 136px) 88px;
    overflow: hidden;
    isolation: isolate;
  }
  .ab-travel-inner {
    position: relative;
    z-index: 1;
    max-width: 1360px;
    margin: 0 auto;
  }
  .ab-travel-layout {
    display: grid;
    grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
    gap: clamp(56px, 5vw, 96px);
    align-items: start;
  }
  .ab-travel-right {
    min-width: 0; /* allows the grid column to contract past its content */
  }
  .ab-travel-head {
    text-align: left;
    margin: 0 0 clamp(28px, 4vh, 48px);
  }
  .ab-travel-head .section-title {
    color: #fff;
    margin: 0 0 20px;
  }
  .ab-travel-deck {
    font-family: var(--serif);
    font-style: italic;
    font-weight: 300;
    font-size: clamp(19px, 1.7vw, 23px);
    line-height: 1.5;
    color: rgba(248,244,239,0.68);
    max-width: 58ch;
    margin: 0;
  }
  /* Mobile-only forced break inside .ab-travel-deck — hidden on desktop so
     the paragraph reads as one continuous italic line, revealed below the
     900px breakpoint so it splits at "happiness" for a tighter rhythm on
     narrow viewports. */
  .deck-mobile-break { display: none; }
  .ab-polaroid--travel {
    margin: 0;
    background: #faf5eb;
    max-width: 360px;
    width: 100%;
  }
  /* Specificity override (see hero polaroid above) — the fade-up.visible
     rule on body.page-about would otherwise flatten this transform. */
  body.page-about .ab-polaroid--travel.fade-up.visible {
    transform: none;
  }
  /* Same whole-polaroid grow as the hero variant. */
  body.page-about .ab-polaroid--travel.fade-up.visible:hover {
    transform: scale(1.04);
  }
  .ab-polaroid--travel .ab-polaroid-img { filter: sepia(0.06) contrast(1.02); }

  /* Editorial mosaic — 6-column × 4-row grid with varied spans so the
     six prints read as a composed spread rather than a uniform strip.
     Photo 1 anchors the left as a 3×3 hero frame, photo 2 is the big
     top-right landscape, photo 3 a narrow landscape beneath it, and
     photos 4-6 form a bottom strip of three equal prints. object-fit
     lets each photo crop-to-fill without regard to its native ratio. */
  .ab-travel-grid {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    grid-auto-rows: minmax(96px, 13.5vh);
    gap: 10px;
    max-width: none;
    margin: 0;
  }
  /* Each <figure.ab-travel-cell> is a grid child that clips its image so
     the hover zoom crops cleanly inside the cell instead of bleeding
     into adjacent prints. */
  .ab-travel-cell {
    margin: 0;
    overflow: hidden;
    position: relative;
    /* Cells open the shared lightbox on click — surface that affordance
       with a zoom-in cursor, matching the wedding gallery's story-photo. */
    cursor: zoom-in;
  }
  .ab-travel-cell:focus-visible {
    outline: 2px solid var(--accent, currentColor);
    outline-offset: 2px;
  }
  .ab-travel-photo {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    margin: 0;
    filter: brightness(0.92) saturate(0.96);
    transform: scale(1);
    transition:
      transform 0.9s cubic-bezier(0.22, 0.68, 0.24, 1),
      filter 0.6s var(--ease);
    will-change: transform;
  }
  /* Hover the cell (not the image directly) so the cursor stays over
     a stable target while the image scales up beneath the clip mask. */
  .ab-travel-cell:hover .ab-travel-photo {
    transform: scale(1.08);
    filter: brightness(1.02) saturate(1.02);
  }
  .ab-travel-cell:nth-child(1) { grid-column: 1 / 4; grid-row: 1 / 4; }
  .ab-travel-cell:nth-child(2) { grid-column: 4 / 7; grid-row: 1 / 3; }
  .ab-travel-cell:nth-child(3) { grid-column: 4 / 7; grid-row: 3 / 4; }
  .ab-travel-cell:nth-child(4) { grid-column: 1 / 3; grid-row: 4 / 5; }
  .ab-travel-cell:nth-child(5) { grid-column: 3 / 5; grid-row: 4 / 5; }
  .ab-travel-cell:nth-child(6) { grid-column: 5 / 7; grid-row: 4 / 5; }
  /* Grid-level fade + gentle per-item stagger triggered when the
     container enters the viewport (JS toggles .is-visible on the grid).
     Fade lives on the cell (not the image) so the hover-zoom transform
     on .ab-travel-photo is not overridden by the reveal transform. */
  .ab-travel-grid .ab-travel-cell {
    opacity: 0;
    transform: translateY(32px);
  }
  .ab-travel-grid.is-visible .ab-travel-cell {
    opacity: 1;
    transform: translateY(0);
    transition:
      opacity 1s var(--ease),
      transform 1s var(--ease);
  }
  .ab-travel-grid.is-visible .ab-travel-cell:nth-child(1) { transition-delay: 0s; }
  .ab-travel-grid.is-visible .ab-travel-cell:nth-child(2) { transition-delay: 0.08s; }
  .ab-travel-grid.is-visible .ab-travel-cell:nth-child(3) { transition-delay: 0.16s; }
  .ab-travel-grid.is-visible .ab-travel-cell:nth-child(4) { transition-delay: 0.24s; }
  .ab-travel-grid.is-visible .ab-travel-cell:nth-child(5) { transition-delay: 0.32s; }
  .ab-travel-grid.is-visible .ab-travel-cell:nth-child(6) { transition-delay: 0.40s; }

  /* --- CALIFORNIA — full-bleed parallax, quiet centered closing card.
     Redesigned for restraint: one hairline rule, one line of text above,
     one italic hero word, one line of text below. No ornaments, no
     coordinate tag, no cascading diagonal. The photograph carries the
     mood; the type just names the place. */
  .ab-cali {
    position: relative;
    min-height: 90vh;
    display: flex;
    align-items: center;
    overflow: hidden;
    color: var(--cream);
    padding: 0;
  }
  .ab-cali-bg {
    position: absolute;
    /* Extra Y slack for the parallax Y range (~±120px). -14% over a
       100vh section gives plenty of overhang on both ends so the photo
       can drift without ever baring an edge. */
    inset: -14% 0;
    background-size: cover;
    /* JS-driven parallax via --parallax-y (see about parallax in main.js).
       Replaces the previous background-attachment:fixed, which is unreliable
       on iOS and gets disabled on mobile anyway. */
    background-position: 50% calc(50% + var(--parallax-y, 0px));
    filter: contrast(1.03);
    z-index: 0;
    /* Matches the .ab-cta-bg hover-zoom pattern so both photo-backdrop
       sections breathe the same way when the user pauses on them. */
    transform: scale(1);
    transition: transform 3.2s var(--ease);
    will-change: transform;
  }
  .ab-cali:hover .ab-cali-bg { transform: scale(1.05); }
  /* Simpler wash — slightly darker + more even so the centered type has
     a consistent ground to sit on, regardless of scroll position. */
  .ab-cali-wash {
    position: absolute; inset: 0;
    background:
      linear-gradient(180deg, rgba(12,10,8,0.42) 0%, rgba(12,10,8,0.28) 50%, rgba(12,10,8,0.48) 100%);
    z-index: 1;
  }
  .ab-cali-inner {
    position: relative;
    z-index: 2;
    max-width: 820px;          /* narrow column — the copy doesn't need 1560px */
    margin: 0 auto;
    width: 100%;
    /* Horizontal padding matches homepage .investment so the section
       still snaps to the same rhythm as the rest of the About page. */
    padding: clamp(80px, 10vh, 140px) 88px;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  /* California grizzly silhouette PNG — sits directly above the hairline
     rule as a quiet heraldic anchor. Low opacity lets the silhouette
     recede into the photograph as a subtle heraldic mark, no shadow. */
  .ab-cali-bear {
    display: block;
    width: 63px;
    height: auto;
    margin: 0 0 16px;
    opacity: 0.4;
  }
  /* Specificity override — `.fade-up.visible { opacity: 1 }` (0,2,0)
     would otherwise flatten the bear to opaque after reveal. This
     selector (0,4,1) pins the final state at 0.4 so the bear fades
     in from 0 to 0.4 (not 0 to 1), and starts the heartbeat pulse. */
  body.page-about .ab-cali-bear.fade-up.visible {
    opacity: 0.4;
    /* Delay 1.2s lets the reveal fade complete before the pulse takes
       over — otherwise the two opacity animations fight for the first
       frame of the heartbeat. */
    animation: bear-heartbeat 2.4s ease-in-out 1.2s infinite;
  }
  /* Heartbeat — two quick opacity pulses (lub-dub) followed by a long
     rest. 0.4 baseline (quiet resting state), peaks at 0.85 so the
     beats read clearly against the dim base. */
  @keyframes bear-heartbeat {
    0%   { opacity: 0.4; }
    14%  { opacity: 0.6; }
    28%  { opacity: 0.4; }
    42%  { opacity: 0.6; }
    56%  { opacity: 0.4; }
    100% { opacity: 0.4; }
  }
  /* Respect users who prefer reduced motion — pin the bear at its
     static 0.4 opacity instead of pulsing. */
  @media (prefers-reduced-motion: reduce) {
    body.page-about .ab-cali-bear.fade-up.visible {
      animation: none;
    }
  }
  /* Hairline rule — single thin anchor at the top of the composition,
     replacing the old rose ornament. Desaturated white, so it fits the
     non-rose palette. */
  .ab-cali-rule {
    display: block;
    width: 56px;
    height: 1px;
    background: rgba(255,255,255,0.55);
    margin: 0 0 clamp(28px, 3.4vh, 44px);
  }

  /* Above-line narrative — upright Cormorant, quiet. */
  .ab-cali-above {
    margin: 0;
    font-family: var(--serif);
    font-weight: 300;
    font-size: clamp(20px, 2.1vw, 28px);
    line-height: 1.3;
    letter-spacing: 0.002em;
    color: rgba(255,255,255,0.88);
  }

  /* Hero word — Playfair italic in the site's rose accent color, the
     same accent used for "new places" / "cultures" in the Travel section
     above. Keeps the typographic emphasis scheme consistent across the
     whole page. */
  .ab-cali-hero {
    margin: clamp(10px, 1.4vh, 18px) 0 clamp(18px, 2.4vh, 28px);
    font-family: 'Playfair Display', var(--serif);
    font-style: italic;
    font-weight: 400;
    font-size: clamp(64px, 10vw, 140px);
    line-height: 0.96;
    letter-spacing: -0.02em;
    color: var(--rose);
    text-shadow: 0 8px 40px rgba(0,0,0,0.34);
  }
  .ab-cali-hero em {
    font-style: italic;
    color: var(--rose);
  }

  /* Below-line — matches .ab-cali-above in weight and family so the
     section reads as one typographic voice, not three. */
  .ab-cali-below {
    margin: 0;
    font-family: var(--serif);
    font-weight: 300;
    font-size: clamp(17px, 1.55vw, 21px);
    line-height: 1.5;
    letter-spacing: 0.004em;
    color: rgba(255,255,255,0.82);
    max-width: 40ch;
  }

  /* Gentle staggered reveal — bear, rule, above, hero, below. Single
     line each, so the cascade is quick. */
  .ab-cali .ab-cali-bear  { transition-delay: 0s; }
  .ab-cali .ab-cali-rule  { transition-delay: 0.08s; }
  .ab-cali .ab-cali-above { transition-delay: 0.2s; }
  .ab-cali .ab-cali-hero  { transition-delay: 0.36s; }
  .ab-cali .ab-cali-below { transition-delay: 0.6s; }

  /* --- CTA — "Let's Connect" closing section.
     Redesigned April 2026: the pale-pink (#f7cfc4) italic tint and the
     rose divider rule are gone. The italic accent on "Connect." now
     uses var(--rose), the same accent used for "new places" / "cultures"
     (Travel) and "California" (closing card), so one italic+rose system
     runs the whole page. A small uppercase meta label replaces the rose
     divider, and the primary action is a solid cream-filled button so
     clicking is the obvious next step. */
  .ab-cta {
    background: var(--cream);
    padding: clamp(110px, 13vw, 168px) 48px;
    text-align: center;
    position: relative;
    overflow: hidden;
    isolation: isolate;
  }
  .ab-cta-inner {
    max-width: 760px;
    margin: 0 auto;
    position: relative;
    z-index: 2;
  }

  /* Eyebrow meta — small uppercase sans label, same type family as the
     section eyebrows used elsewhere on the site (footer "RedSphere Studios
     · Est. 2009"). Sits where the old rose divider used to. */
  .ab-cta-eyebrow {
    margin: 0 0 clamp(22px, 2.8vh, 30px);
    font-family: var(--sans);
    font-weight: 400;
    font-size: 11.5px;
    letter-spacing: 0.32em;
    text-transform: uppercase;
    color: var(--muted);
  }

  .ab-cta-title {
    font-family: var(--serif);
    font-weight: 300;
    font-size: clamp(54px, 7.5vw, 104px);
    line-height: 1;
    letter-spacing: -0.018em;
    color: var(--ink);
    margin: 0 0 clamp(28px, 3vh, 38px);
  }
  /* "Connect." italic accent — Playfair italic in the site's rose color,
     matching the italic accents in the Travel title ("new places" /
     "cultures") and the California word, so one italic+rose system runs
     the whole page. */
  .ab-cta-title em {
    font-family: 'Playfair Display', var(--serif);
    font-style: italic;
    font-weight: 400;
    color: var(--rose);
    padding: 0 0.04em;
  }

  .ab-cta-body {
    font-family: var(--serif);
    font-weight: 300;
    font-style: normal;          /* was italic; upright reads calmer + less ornamental */
    font-size: clamp(18px, 1.7vw, 22px);
    line-height: 1.6;
    color: var(--charcoal);
    margin: 0 auto clamp(40px, 5vh, 52px);
    max-width: 54ch;
  }

  /* Solid primary button — matches the homepage contact form's
     .btn-submit animation exactly (rose sweep from the left, arrow
     nudge, text-color cross-fade). The only difference is the starting
     color: this button begins with a cream fill + ink label so it
     reads as a lighter hero CTA against the beach photograph, where
     the homepage form button starts on ink. No border, no lift, no
     shadow — just solid color in both states. */
  /* Inquire About Your Date — same primary arrow-pill as .btn-view-all,
     standalone class because the CTA section sets its own color palette
     (cream fill, ink label). Type metrics must match the rest of the
     family: 17/34 padding, 450 weight, 13px arrow. */
  .ab-cta-btn-solid {
    position: relative; overflow: hidden;
    display: inline-flex; align-items: center; gap: 14px;
    padding: 17px 34px;
    background: var(--cream); color: var(--ink);
    font-family: var(--sans); font-size: 12px; font-weight: 425;
    letter-spacing: 0.22em; text-transform: uppercase;
    text-decoration: none;
    border: 0;
    transition: color 0.4s var(--ease);
  }
  .ab-cta-btn-solid::before {
    content: ''; position: absolute; inset: 0;
    background: var(--rose);
    transform: translateX(-101%);
    transition: transform 0.5s var(--ease);
  }
  .ab-cta-btn-solid > span { position: relative; z-index: 1; }
  .ab-cta-btn-solid .ab-cta-btn-arrow {
    display: inline-block;
    font-size: 12.5px;
    line-height: 1;
    transition: transform 0.4s var(--ease);
  }
  .ab-cta-btn-solid:hover { color: var(--cream); }
  .ab-cta-btn-solid:hover::before { transform: translateX(0); }
  .ab-cta-btn-solid:hover .ab-cta-btn-arrow { transform: translateX(6px); }

  /* --- CTA beach variant — full-bleed sunlit photograph behind the
     invitation. Flips type to light-on-dark; the solid cream button
     is inherited from the base and pops cleanly against the dark wash. */
  .ab-cta--beach {
    background: var(--ink);
    min-height: 82vh;
    display: grid;
    place-items: center;
  }
  .ab-cta-bg {
    position: absolute;
    /* Negative Y inset gives the parallax Y range (~±110px) room to
       shift without the photo's edge ever coming into view. */
    inset: -140px 0;
    background-size: cover;
    /* JS-driven parallax via --parallax-y. 38% vertical anchor is kept
       so the beach horizon still sits where the composition wants it;
       the parallax offset modulates that anchor on scroll. */
    background-position: 50% calc(38% + var(--parallax-y, 0px));
    filter: contrast(1.04) saturate(1.02);
    z-index: 0;
    transform: scale(1.04);
    transition: transform 3.2s var(--ease);
    will-change: transform;
  }
  .ab-cta--beach:hover .ab-cta-bg { transform: scale(1.08); }
  .ab-cta-wash {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    /* Neutral wash — no warm-rose stop. Center bumped darker (was 34%)
       so the italic rose "Connect." has a stable ground to sit on
       regardless of which part of the photograph scrolls behind it. */
    background:
      linear-gradient(
        120deg,
        rgba(14, 10, 8, 0.62) 0%,
        rgba(14, 10, 8, 0.46) 55%,
        rgba(14, 10, 8, 0.48) 100%
      ),
      linear-gradient(to bottom, rgba(14,10,8,0) 55%, rgba(14,10,8,0.38) 100%);
  }
  .ab-cta--beach .ab-cta-eyebrow {
    color: rgba(255, 255, 255, 0.82);
  }
  .ab-cta--beach .ab-cta-title {
    color: #fff;
    text-shadow: 0 2px 30px rgba(0,0,0,0.28);
  }
  /* "Connect." on the beach photograph — the base var(--rose) (#C4786A)
     is a warm terracotta that gets muddied by the sand + sunlight in the
     background. Lift the accent to a brighter, more luminous warm-rose
     (#ECA495) for this variant only, so it pops against the wash while
     staying within the same hue family as the rest of the page's italic
     accents. Add a stronger shadow for extra separation from sand tones. */
  .ab-cta--beach .ab-cta-title em {
    color: #ECA495;
    text-shadow:
      0 2px 20px rgba(0, 0, 0, 0.45),
      0 0 28px rgba(10, 8, 6, 0.3);
  }
  .ab-cta--beach .ab-cta-body {
    color: rgba(255, 255, 255, 0.92);
  }
  /* Beach variant: the button inherits the base animation + solid-color
     treatment as-is. No variant overrides needed. */

  @media (prefers-reduced-motion: reduce) {
    .story-photo-frame img,
    .lightbox, .lightbox-stage, .lightbox-frame, .lightbox-img,
    .lightbox-close, .lightbox-nav,
    .ab-hero-bg, .ab-polaroid, .ab-travel-photo, .ab-cta-bg,
    .ab-film-video {
      transition: none !important;
      animation: none !important;
    }
  }

  /* ============ RESPONSIVE ============ */
  @media (max-width: 1100px) {
    .about-grid { gap: 56px; grid-template-columns: 360px 1fr; }
    .about-img-wrap { max-width: 360px; }
    .investment-inner { grid-template-columns: 1fr; gap: 48px; }
    /* About page: tighten hero/philo/film padding + gap; travel masonry
       drops to 2 columns. Film collapses to single column. */
    .ab-hero-header-inner { padding: clamp(104px, 13vh, 144px) 40px clamp(20px, 3vh, 36px); }
    .ab-hero-intro {
      padding:
        clamp(215px, 27vh, 300px)
        40px
        clamp(72px, 9vh, 120px);
    }
    .ab-hero-intro-inner { grid-template-columns: minmax(240px, 320px) minmax(0, 1fr); gap: 48px; }
    .ab-polaroid--hero { max-width: 320px; }
    /* Keep 88px horizontal padding at tablet — homepage .investment
       doesn't override horizontal padding at this breakpoint either. */
    .ab-philo { padding: clamp(64px, 8vh, 96px) 88px; }
    /* Film: collapse the 2-col photo+copy to a stack on tablet. Video still
       fills the section behind both. */
    .ab-film-inner { grid-template-columns: 1fr; gap: 48px; max-width: 720px; }
    .ab-film-media { max-width: 460px; margin: 0 auto; }
    .ab-film-img-frame { max-height: 52vh; }
    /* Travel: collapse the polaroid-left / content-right split to a stack so
       the mosaic has room to breathe on tablet. The 6-col grid stays, but
       we shrink its row height so it doesn't dominate the fold. */
    .ab-travel-layout {
      grid-template-columns: 1fr;
      gap: 48px;
    }
    .ab-polaroid--travel {
      max-width: 320px;
      margin: 0 auto;
    }
    .ab-travel-head { text-align: center; max-width: 640px; margin: 0 auto clamp(24px, 3vh, 40px); }
    .ab-travel-grid { grid-auto-rows: minmax(80px, 11vh); }
    body.page-about .ab-philo-pillars { grid-template-columns: 1fr 1fr 1fr; gap: 28px; }
    /* Experience: drop the timeline and interleave images/bodies in a 2-col grid
       (2 imgs → 2 bodies → 2 imgs → 2 bodies). The 4-dot rail only shows on
       wide screens where all four columns can breathe. */
    .exp-timeline { display: none; }
    .exp-rail {
      display: grid;
      grid-template-columns: 1fr 1fr;
      column-gap: 32px;
      row-gap: 36px;
    }
    .exp-img-row,
    .exp-body-row { display: contents; }
    .exp-img-row > .exp-img:nth-child(1) { grid-column: 1; grid-row: 1; }
    .exp-img-row > .exp-img:nth-child(2) { grid-column: 2; grid-row: 1; }
    .exp-body-row > .exp-body:nth-child(1) { grid-column: 1; grid-row: 2; }
    .exp-body-row > .exp-body:nth-child(2) { grid-column: 2; grid-row: 2; }
    .exp-img-row > .exp-img:nth-child(3) { grid-column: 1; grid-row: 3; }
    .exp-img-row > .exp-img:nth-child(4) { grid-column: 2; grid-row: 3; }
    .exp-body-row > .exp-body:nth-child(3) { grid-column: 1; grid-row: 4; }
    .exp-body-row > .exp-body:nth-child(4) { grid-column: 2; grid-row: 4; }
    .connect-form { grid-template-columns: 1fr; }
  }
  @media (max-width: 900px) {
    nav { padding: 0 24px; }
    .nav-links, .nav-cta { display: none; }
    .hamburger { display: block; }
    section, .gallery-section, .connect, .about, .investment, .experience, .testimonials { padding: 72px 24px; }
    /* Mobile hero layout — Safari's dynamic URL bar eats the bottom
       of 100vh, so we pin content to a safe band: top padding clears
       the fixed nav (--nav-h + breathing room), bottom padding clears
       both the scroll indicator AND the URL bar via safe-area insets.
       Fonts tightened from desktop (eyebrow/title/subtitle/stats) so
       the stack fits within the visible viewport on an iPhone without
       changing the layout itself — same column, same order. */
    /* Switch .hero from 100vh → 100dvh on phone widths. vh on iOS Safari
       uses the large-viewport height (with the URL bar hidden), so
       absolute positioning from the bottom ends up BEHIND Safari's
       toolbar when the bar is visible. dvh tracks the currently-visible
       viewport, so bottom-pinned elements always sit above the toolbar. */
    .hero { height: 100dvh; }
    .hero-content {
      justify-content: flex-end;
      padding:
        calc(var(--nav-h) + 12px) 28px
        128px 28px;
    }
    .hero-eyebrow { font-size: 11px; letter-spacing: 0.24em; margin-bottom: 18px; }
    .hero-eyebrow .eyebrow-main { padding-bottom: 8px; margin-bottom: 8px; }
    /* Title goes much bigger on mobile — first + second passes landed
       too small. Push the clamp hard so "Honest. Heartfelt. Cinematic."
       reads as the dominant element on the screen. line-height pulled
       in to 0.92 so three big lines still fit within the content band. */
    .hero-title { font-size: clamp(68px, 19vw, 104px); margin-bottom: 18px; line-height: 0.92; }
    .hero-subtitle { font-size: 14px; line-height: 1.55; margin-bottom: 20px; max-width: 100%; }
    .hero-stats { gap: 20px; flex-wrap: wrap; padding-top: 14px; }
    .stat-num { font-size: 26px; }
    .stat-label { font-size: 10px; margin-top: 4px; }
    /* Dots + scroll indicator bumped well above Safari's toolbar.
       With hero now using dvh, bottom is measured from the visible
       viewport edge — 72px keeps them clear of the toolbar + a little
       breathing room. */
    .hero-dots { right: 24px; bottom: 72px; }
    .ab-hero-scroll { bottom: 68px; }
    /* Homepage investment packages — on mobile the middle column
       (name + italic description) wraps to multiple lines, so the
       desktop `align-items: center` leaves the roman numeral and
       the price floating in the middle of the row. Switch to
       baseline alignment so the "i."/"ii."/"iii.", the category
       name, and the price all share the same first-line baseline,
       reading as a clean horizontal sweep across every row.
       Price font size also drops from 30px → 20px so "From $5,900"
       doesn't dominate the card. */
    .pkg {
      align-items: baseline;
      grid-template-columns: 40px 1fr auto;
      gap: 16px;
      padding: 22px 0;
    }
    .pkg-idx { font-size: 18px; }
    .pkg-name { font-size: 12px; letter-spacing: 0.18em; }
    .pkg-desc { font-size: 14.5px; margin-top: 6px; line-height: 1.45; }
    .pkg-price {
      font-size: 20px;
      font-variant-numeric: tabular-nums;
      white-space: nowrap;
    }
    /* Desktop uses only .hero-slide::after (0 → 0.2 @ 50% → 0.4 @ 100%).
       On mobile we keep that base intact and ADD a second gradient that
       is fully transparent on the top half and ramps down gradually to
       a darker bottom, purely to lift the subtitle + stats off bright
       slide content. Top of the hero reads identical to desktop; only
       the lower band builds additional darkening.
       z-index 2 sits above the slide + its ::after tint but below
       .ab-hero-scroll (z:4), .hero-content (z:10), and .hero-dots
       (z:20) so the chevron + dots stay crisp above the wash. */
    .hero::before {
      content: '';
      position: absolute;
      inset: 0;
      z-index: 2;
      pointer-events: none;
      background: linear-gradient(
        180deg,
        rgba(20,16,12,0)    0%,
        rgba(20,16,12,0)    50%,
        rgba(20,16,12,0.15) 75%,
        rgba(20,16,12,0.35) 100%
      );
    }
    .about-grid { grid-template-columns: 1fr; gap: 64px; }
    .about-img-wrap { max-width: 350px; margin: 0 auto; }
    .about-tag { left: auto; right: -16px; bottom: -20px; }
    .about-pillars { grid-template-columns: 1fr; gap: 28px; }
    .gallery-grid { grid-template-columns: 1fr; gap: 8px; }
    .gallery-head { flex-direction: column; align-items: flex-start; }
    /* Gallery page: collapse to single column on mobile, matching the
       homepage's .gallery-section padding (72px 24px) + .gallery-grid gap (8px). */
    .story-grid { grid-template-columns: 1fr; gap: 8px; }
    .story-photo-frame { aspect-ratio: 4 / 3; }
    .story-grid-section { padding: 72px 24px; }
    .story-footer { flex-direction: column; align-items: flex-start; gap: 24px; }
    /* Wedding Stories index: single column on mobile, tighter padding,
       and a smaller vertical gap between cards so the stack doesn't
       spread too thin. */
    .ws-grid { grid-template-columns: 1fr; gap: 40px; }
    .ws-grid-section { padding: 40px 24px 72px; }
    .ws-card-body { padding-top: 18px; }
    /* Single-column: bring back the odd/even stagger so the grid reveals
       in a soft cascade instead of all-at-once. Each tile is its own row
       here, so there are no row partners to desync. */
    .story-photo:nth-child(even) { transition-delay: 0.08s; }
    .lightbox-prev { left: 12px; }
    .lightbox-next { right: 12px; }
    .lightbox-nav { width: 46px; height: 46px; }
    .lightbox-close { top: 16px; right: 16px; width: 42px; height: 42px; }
    .lightbox-stage { width: 96vw; height: 82vh; }
    .testi-card { flex: 0 0 calc(100vw - 56px); }
    .testi-card-body { padding: 28px 24px; }
    .testi-couple { flex-direction: column; align-items: flex-start; gap: 14px; }
    /* Experience interleaved layout is already applied at 1100px — just tighten
       the column gap a bit on smaller screens. */
    .exp-rail { column-gap: 20px; row-gap: 32px; }
    .fs-cell { width: 340px; height: 233px; }
    .filmstrip { padding: 16px 0; }
    .connect::before { display: none; }
    .footer-inner { padding: 80px 24px 32px; }
    .footer-meta { flex-direction: column; align-items: flex-start; gap: 20px; }
    .footer-tagline { text-align: left; margin-top: 32px; }
    /* About page mobile: sticky-stack stays on (height:100vh + overflow
       hidden). Single column everywhere + tighter padding. The hero keeps
       its top-anchored layout because the fixed nav lives over its top
       edge and the header copy has baked-in clearance — overriding the
       stack-item's flex-center here prevents the title from sliding up
       behind the nav. Every other section uses the inherited flex
       vertical-center. */
    body.page-about .ab-hero {
      justify-content: flex-start;
    }
    /* On mobile, return the header to normal document flow — single-column
       stack reads top-to-bottom and the absolute overlay would leave a
       gaping void above the intro otherwise. */
    .ab-hero-header {
      position: relative;
    }
    .ab-hero-header-inner {
      padding: 88px 24px 12px;
    }
    .ab-hero-intro {
      /* Drop the top padding so the polaroid pulls closer to the page
         header above it; breathing room below comes from the inner grid's
         enlarged gap. */
      padding: 0 24px 40px;
    }
    .ab-hero-intro-inner {
      grid-template-columns: 1fr;
      gap: 40px;
      text-align: left;
    }
    .ab-polaroid--hero {
      max-width: 240px;
      /* Negative top margin slides the polaroid up toward the header
         title; `margin-bottom: 0` lets the grid gap (40px) fully govern
         the space between polaroid and copy. */
      margin: -12px auto 0;
    }
    .ab-philo, .ab-film, .ab-travel, .ab-cta { padding: 56px 24px; }
    /* Travel polaroid on mobile: matches the About-Alex hero polaroid size
       (240px) for visual consistency between the two polaroids on the
       page. Keeps the default .ab-polaroid padding so the frame looks
       identical — just with the Myanmar caption inside. */
    .ab-polaroid--travel {
      display: block;
      max-width: 240px;
      margin: 0 auto;
    }
    /* Film: photograph should feel like a cinematic print, not a thumbnail.
       Drop the tablet max-width/max-height caps and shift to a 4/5
       portrait aspect so Alex's photo dominates the fold above the copy. */
    /* Mobile film: stack BEYOND WEDDINGS → image → title → sub → body → CTA,
       all centered. We flatten .ab-film-copy with display: contents so its
       children become direct flex children of .ab-film-inner, letting us
       use `order` to pull the section-label above the image. */
    .ab-film-inner {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 20px;
      max-width: none;
      text-align: center;
    }
    .ab-film-copy {
      display: contents;
    }
    /* Pull the eyebrow to the very top of the mobile slide. */
    .ab-film-copy .section-label {
      order: -1;
      justify-content: center;
      margin-bottom: 0;
      width: 100%;
    }
    .ab-film-media {
      order: 0;
      max-width: none;
      width: 100%;
    }
    .ab-film-img-frame {
      /* Inherit the desktop 4:3 aspect so Alex's photo reads at its
         intended proportion. */
      max-height: none;
    }
    .ab-film-caption { margin-top: 12px; justify-content: center; }
    .ab-film-copy .section-title { font-size: clamp(32px, 8vw, 44px); margin-top: 4px; }
    .ab-film-sub { margin-bottom: 12px; }
    .ab-film-body {
      font-size: 14.5px;
      line-height: 1.55;
      /* Cancel the desktop `max-width: 48ch` that was left-anchored —
         auto margins center the constrained body under the title. */
      margin-left: auto;
      margin-right: auto;
    }
    .ab-film-cta { align-self: center; }
    .ab-philo-title { font-size: clamp(36px, 8vw, 56px); margin-bottom: 24px; }
    /* Mobile pillars: swap the desktop 3-column card layout for a compact
       vertical stack where each pillar becomes a horizontal row (small
       image on the left, number + title + body on the right). This fits
       three full pillars inside one 100vh section without cropping the
       images down to slivers. */
    body.page-about .ab-philo-pillars { grid-template-columns: 1fr; gap: 16px; padding: 20px 0; }
    /* The pillar has THREE children (img, num, text) and a 2-col grid,
       so implicit auto-flow was forcing .pillar-text into row 2 / col 1
       — wrapping the body copy inside the 88px image column and making
       it unreadable. Explicit grid-template-areas pins the image to
       span both rows on the left, and stacks num + text in the right
       column. */
    .ab-philo-pillars .pillar {
      display: grid;
      grid-template-columns: 200px 1fr;
      grid-template-rows: auto 1fr;
      grid-template-areas:
        "img num"
        "img text";
      column-gap: 16px;
      row-gap: 4px;
      align-items: start;
      text-align: left;
    }
    .ab-philo-pillars .pillar-img {
      grid-area: img;
      width: 200px;
      /* Natural aspect — drop the forced height + object-fit cover so
         each pillar image retains its original proportions (matches
         the desktop philosophy cards). */
      height: auto;
      align-self: center;
    }
    .ab-philo-pillars .pillar-num {
      grid-area: num;
      font-size: 13px;
      margin: 4px 0 2px;
    }
    .ab-philo-pillars .pillar-text {
      grid-area: text;
      font-size: 13.5px;
      line-height: 1.5;
    }
    .ab-philo-pillars .pillar-text strong {
      font-size: 17px;
      display: block;
      margin-bottom: 4px;
    }
    /* Travel on phone: polaroid at top (centered), head (title + deck) in
       the middle, and the mosaic as a 3-column CSS masonry at bottom.
       Each piece stacks naturally via the .ab-travel-layout grid (already
       collapsed to 1 column at the tablet breakpoint). The layout gap
       controls the breathing room between the polaroid and the title
       below it. */
    .ab-travel-layout { gap: 32px; }
    /* Breathing room between the deck paragraph and the photo masonry
       below — the italic line needs visual separation so it doesn't
       collide with the first row of images. */
    .ab-travel-head { margin: 0 auto 44px; text-align: center; }
    /* Phone: drop the editorial grid in favor of CSS columns masonry.
       Each photo keeps its natural aspect ratio (no forced crop to a
       uniform row height) and flows into three narrow columns so all
       six prints fit inside the 100vh slide. */
    .ab-travel-grid {
      display: block;
      column-count: 3;
      column-gap: 4px;
      grid-template-columns: none;
      grid-auto-rows: auto;
    }
    /* Reset any grid-placement that would inherit from desktop — we're
       no longer a grid here. break-inside keeps each cell whole inside
       its column. */
    .ab-travel-cell:nth-child(1),
    .ab-travel-cell:nth-child(2),
    .ab-travel-cell:nth-child(3),
    .ab-travel-cell:nth-child(4),
    .ab-travel-cell:nth-child(5),
    .ab-travel-cell:nth-child(6) {
      grid-column: auto;
      grid-row: auto;
    }
    .ab-travel-cell {
      break-inside: avoid;
      display: block;
      margin: 0 0 4px;
      width: 100%;
    }
    .ab-travel-photo {
      /* Let each image size to its natural aspect within the column
         width instead of being cropped to a fixed cell. */
      width: 100%;
      height: auto;
      object-fit: initial;
    }
    /* Tighten the travel header so the masonry has enough vertical room
       to fit cleanly within 100vh. */
    .ab-travel-head .section-title { font-size: clamp(28px, 7vw, 40px); margin-bottom: 10px; }
    /* Deck paragraph on mobile: let it span the section's natural content
       width (bounded by the 24px section padding) instead of an extra
       32ch cap that created visible dead space on both sides. Bumped
       font-size to match the film body copy for consistent reading
       weight across sections. */
    .ab-travel-deck {
      font-size: 15px;
      line-height: 1.55;
      max-width: none;
      margin: 0;
      text-align: center;
    }
    /* Enable the forced break after "happiness" on mobile only. */
    .deck-mobile-break { display: inline; }
    /* California mobile — centered composition stays, just tighten the
       padding and shrink the hero word so it always fits the viewport.
       (No background-attachment override needed anymore — JS-driven
       parallax replaces the old fixed-attachment pseudo-parallax.) */
    .ab-cali-inner { padding: 88px 24px; }
    .ab-cali { min-height: 72vh; }
    .ab-cali-bear { width: 49px; margin-bottom: 12px; }
    .ab-cali-rule { width: 40px; margin-bottom: 24px; }
    .ab-cali-above { font-size: clamp(17px, 4.2vw, 22px); }
    .ab-cali-hero { font-size: clamp(56px, 15vw, 96px); }
    .ab-cali-below { font-size: clamp(15px, 3.8vw, 18px); }
    /* Beach CTA: just let the section shrink on mobile. (Fixed attachment
       is gone now — JS-driven parallax handles the depth effect.) */
    .ab-cta--beach { min-height: 68vh; padding: 96px 24px; }
  }
