/* =============================================================================
   Hopelessly Romantic Films — site stylesheet
   -----------------------------------------------------------------------------
   THE single home for every cross-page element. A page's own stylesheet
   (page-*.css) holds ONLY that page's genuinely unique sections. If a second
   page needs something in here, it reuses these class names verbatim — never a
   re-skinned copy under new names.

   Every value below was measured off the mockups with mockup_probe.py; see
   notes/design-system.md and notes/spec-*.md for the evidence.

   Layout system (confirmed independently by the About, Blog, Portfolio and
   Services specs — one system for the whole site, never a per-page decision):
     outer container   1280px   (x161…x1441 on the 1602 design frame)
     content container 1168px   (x217…x1385) = 1280 − 2×56
     inner padding       56px
     header + marquee are FULL-BLEED with 56px side padding, not the 1280 box
   ============================================================================= */

/* ---------------------------------------------------------------- tokens --- */
:root {
  /* palette — every value sampled from the mockups, not eyeballed */
  --cream:          #F7F4EF;
  --burgundy:       #5B0D1A;
  --brown:          #2E2118;
  --tan:            #D8C2A8;
  --body:           #7A6A5F;   /* body copy on cream, nav links */
  --body-on-dark:   #928A83;   /* = cream @50% over --brown */
  --body-on-burg:   #D8C6C4;   /* = cream @80% over --burgundy */
  --cream-alt:      #F2EFEA;   /* the second, very slightly darker cream (Services bands 2 & 5) */
  --rule-light:     #E4D8D5;   /* marquee hairlines on cream */
  --rule-dark:      #3C3027;   /* footer hairline */
  --social-border:  #4B4037;   /* = cream @14.5% over --brown */
  --legal:          #60564E;

  /* Type — POSITIVE IDENTIFICATIONS, not approximations. See notes/typography.md.
     Young Serif was found by screening all 816 Google serif/display families on the display
     face's topological signature (single-storey `a` + BINOCULAR `g`), then confirmed by stem
     width at 88px: T 14/14, L 14/14, a 11,12/11,12, n 13,12/13,12 — four glyphs exact.
     Great Vibes won a 357-family handwriting screen by shape IoU 0.459 vs 0.198 runner-up.
     Jost is the best of 717 sans families on word-start position vectors (0.147 RMS%).
     Young Serif and Great Vibes ship ONE weight (400) each — never synthesise bold. */
  --font-display: 'Young Serif', Georgia, 'Times New Roman', serif;
  --font-body:    'Jost', 'Futura', 'Century Gothic', system-ui, sans-serif;
  --font-script:  'Great Vibes', 'Snell Roundhand', cursive;

  /* layout */
  /* 1280 -> 1600 -> 1440 (Pete, 15 Aug). 1600 matched the 1602 design frame exactly but ran the
     content right to the edge of a common laptop screen with no margin left; 1440 keeps the
     wider feel while still breathing on smaller displays.
     --content follows it: 1440 - 2x56 padding = 1328.
     The HERO is deliberately excluded — it is a full-bleed split and does not use .container. */
  --container:  1440px;
  --content:    1328px;
  --pad:          56px;
  --frame:      1602px;   /* the design frame width, for reference */

  /* rhythm */
  --sp-1:  8px;
  --sp-2: 16px;
  --sp-3: 24px;
  --sp-4: 32px;
  --sp-5: 48px;
  --sp-6: 64px;
  --sp-7: 96px;
  --sp-8: 128px;
}

/* ------------------------------------------------------------------ base --- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--cream);
  color: var(--body);
  font-family: var(--font-body);
  font-weight: 300;
  /* SITE-WIDE BODY SIZE — 18px (Pete, 15 Aug: "all body fonts should be 18px site wide").
     Also closes Impressive QA item 2, "Larger font — Everywhere", which the client raised on
     27 Jul and which had only ever been applied to the Home page.

     The comps measured Jost 300 at exactly 14.0px (x-height 6.5) and that is what shipped, but
     14px is below the comfortable reading floor for long-form copy and the client asked for it
     up. 31px leading keeps the 1.72 ratio already approved on Home, so the vertical rhythm of
     the whole site stays consistent with the one page that had already been signed off. */
  font-size: 18px;
  line-height: 31px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img, svg, video { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  color: var(--brown);
  font-weight: 400;                /* Young Serif has ONE weight — never synthesise bold */
  letter-spacing: 0;
}

p { margin: 0 0 27px; }
p:last-child { margin-bottom: 0; }

a { color: inherit; }

ul { margin: 0; padding: 0; list-style: none; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------------------------------------------------------------- layout --- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--pad);
}

/* full-bleed band with the content column inside it */
.band { width: 100%; }
.band--cream    { background: var(--cream); }
.band--cream-alt{ background: var(--cream-alt); }
.band--burgundy { background: var(--burgundy); color: var(--body-on-burg); }
.band--brown    { background: var(--brown);    color: var(--body-on-dark); }
.band--burgundy h1, .band--burgundy h2, .band--burgundy h3,
.band--brown    h1, .band--brown    h2, .band--brown    h3 { color: var(--cream); }

/* ------------------------------------------------------------ typography --- */
/* Sizes are the measured desktop values; clamp() gives a mobile floor while
   keeping the desktop rendering EXACTLY as measured at >=1602px. */
/* Line-heights are RATIOS, not fixed px. A fixed 92px leading against a clamp()ed font-size is
   correct at >=1602px and absurd below it — a 44px heading on a 92px line. Each ratio reproduces
   the measured pitch exactly at full size (88x1.0455=92, 68x1.0588=72, 50x1.22=61, 44x1.1818=52)
   and scales sanely down. */
.t-display-xl { font-size: clamp(44px, 5.5vw, 88px); line-height: 1.0455; letter-spacing: -.01em; }
.t-display-l  { font-size: clamp(38px, 4.3vw, 68px); line-height: 1.0588; letter-spacing: -.02em; }
.t-display-m  { font-size: clamp(30px, 3.1vw, 50px); line-height: 1.22; }
.t-display-s  { font-size: clamp(26px, 2.7vw, 44px); line-height: 1.1818; }

.t-script {
  font-family: var(--font-script);
  font-size: clamp(22px, 1.9vw, 30px);
  line-height: 1.3;
  color: var(--burgundy);
  font-weight: 400;
}
/* On the dark bands the script accent is tan, not burgundy (burgundy on burgundy would vanish). */
.band--brown .t-script,
.band--burgundy .t-script { color: var(--tan); }

/* Intermediate display steps, for pages whose measured cap heights land between the main
   four. typography.md finds no true h3 tier in the display face — below .t-display-s the
   next step down is an uppercase Jost micro-label. */
.t-display-ml { font-size: clamp(34px, 3.4vw, 51px); line-height: 1.03; }
.t-display-sm { font-size: clamp(20px, 1.6vw, 23px); line-height: 1.22; }

/* uppercase micro-labels: eyebrows, nav, buttons, marquee, small links */
.t-caps {
  font-size: 11px;
  line-height: 1.2;
  letter-spacing: .18em;
  text-transform: uppercase;
  font-weight: 400;
}
.t-caps-xs { font-size: 8px; letter-spacing: .24em; text-transform: uppercase; }

.t-body-s { font-size: 12px; line-height: 22px; }

.eyebrow {
  font-size: 10px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--body);
  font-weight: 400;
  margin: 0 0 var(--sp-3);
}
.band--burgundy .eyebrow, .band--brown .eyebrow { color: var(--tan); }

/* short rule that sits under a section eyebrow */
.eyebrow-rule {
  width: 32px; height: 1px; border: 0; margin: var(--sp-2) 0 0;
  background: var(--burgundy);
}

/* --------------------------------------------------------------- buttons --- */
/* Measured: 163x36, radius 0, fill #5B0D1A, label 11px cream, ~24px h-padding.
   Radius 0 was verified with `radius` (constant 0 inset over 14 rows) — these
   are hard-cornered rectangles, NOT pills. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 36px;
  padding: 0 24px;
  border: 0;
  border-radius: 0;
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .14em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: color .28s ease, border-color .28s ease;

  /* WIPE-FILL HOVER (Pete, 15 Aug: "hover bg transparent... it fills it up from left to right").
     The fill is a ::before that scales on the X axis from the left edge, NOT an animated
     background-position or width — transform is the only one of the three the compositor can
     run off the main thread, so the wipe stays smooth while the page is scrolling.

     transform-origin flips to the RIGHT on the way out, so the fill retreats back the way it
     came instead of snapping. That one line is the difference between a wipe and a flicker. */
  position: relative;
  overflow: hidden;
  isolation: isolate;            /* keeps ::before behind the label without a z-index on the text */
  background: transparent;
  border: 1px solid currentColor;
}
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  transform: scaleX(1);
  transform-origin: left center;
  transition: transform .38s cubic-bezier(.22, .61, .36, 1);
}
.btn:hover::before,
.btn:focus-visible::before {
  transform: scaleX(0);
  transform-origin: right center;
}
/* Reduced motion: keep the colour swap, drop the travel. */
@media (prefers-reduced-motion: reduce) {
  .btn::before { transition: none; }
}
/* Each variant sets the FILL colour on ::before and the two label colours. At rest the label
   reads against the fill; on hover the fill wipes away and the label becomes the fill colour,
   so the button turns into an outline of what it just was. */
.btn--solid   { color: var(--cream); border-color: var(--burgundy); }
.btn--solid::before { background: var(--burgundy); }
.btn--solid:hover, .btn--solid:focus-visible { color: var(--burgundy); }

.btn--cream   { color: var(--burgundy); border-color: var(--cream); }
.btn--cream::before { background: var(--cream); }
.btn--cream:hover, .btn--cream:focus-visible { color: var(--cream); }

/* Already an outline at rest, so it inverts the other way: transparent -> filled on hover. */
.btn--outline { color: var(--cream); border-color: rgba(247, 244, 239, .7); }
.btn--outline::before { background: var(--cream); transform: scaleX(0); transform-origin: left center; }
.btn--outline:hover::before, .btn--outline:focus-visible::before { transform: scaleX(1); transform-origin: left center; }
.btn--outline:hover, .btn--outline:focus-visible { color: var(--burgundy); border-color: var(--cream); }
/* Hero-scale button — measured 183x43 (Services hero) and 284x44 (About hero). */
.btn--lg { min-height: 44px; padding: 0 30px; letter-spacing: .18em; }

/* inline arrow link — "WATCH THE FILMS ->" / "CHECK YOUR DATE ->" */
.arrowlink {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  line-height: 1.2;            /* explicit: body's 27px strut would make this 33px tall */
  letter-spacing: .15em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--burgundy);
  padding-bottom: 5px;
  border-bottom: 1px solid currentColor;
}
/* the underline is a 25%-opacity burgundy on some pages (measured #D0BABA on cream) */
.arrowlink--muted { border-bottom-color: rgba(91, 13, 26, .25); }
/* dark-brown variant (About) rather than burgundy */
.arrowlink--dark  { color: var(--brown); border-bottom-color: #998D83; }
.arrowlink svg    { width: 11px; height: 11px; }
.arrowlink svg { width: 11px; height: 11px; flex: none; }
.band--burgundy .arrowlink, .band--brown .arrowlink { color: var(--cream); }

/* underlined text link — "READ MORE" */
.textlink {
  display: inline-block;
  font-size: 11px;
  line-height: 1.2;            /* explicit — see .arrowlink */
  letter-spacing: .18em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--burgundy);
  padding-bottom: 4px;
  border-bottom: 1px solid rgba(91,13,26,.35);
}
.textlink:hover { border-bottom-color: var(--burgundy); }
/* body-grey variant, tracked to match .arrowlink so the two read as one system (About) */
.textlink--muted { color: var(--body); border-bottom-color: #DCD5CF; letter-spacing: .15em; }

/* ---------------------------------------------------------------- header --- */
/* Full-bleed with 56px side padding — NOT inside the 1280 container. */
/* HEADER — scrolls away at the top, then drops back in as a fixed bar (Pete, 15 Aug:
   "as soon as it starts scrolling down, it comes back down into view and is sticky",
   then "sticky scroll down too", then "hide at the top").

   Three states:
     (default)   position: relative — the header is part of the hero and scrolls away with it.
                 This is why .idx-hero's -112px pull-up still works untouched.
     .is-pinned  past the hero: switches to fixed and slides DOWN into view. Solid cream so it
                 reads against page content. Stays put in BOTH scroll directions — no
                 hide-on-scroll-down.
     The slide itself is a CSS animation (@keyframes hrf-slide-down) rather than a transition —
     see the note on .site-head.is-pinned for why a transition cannot work here.

   No spacer element is needed: it only goes fixed once it has already scrolled out of view, so
   nothing below it ever jumps. */
.site-head {
  position: relative;
  z-index: 50;
  padding: 0 var(--pad);
}
/* SLIDE-IN USES A KEYFRAME ANIMATION, NOT A TRANSITION.
   Two transition-based attempts both failed for the same underlying reason: a transition only
   runs when the element already had the transition property AND a previous value to interpolate
   from. Put `transition: transform` on the base rule and the jump to -100% animates too, so the
   bar barely moves and you only see the background fade. Put it on the .is-ready class and the
   element gains the transition and the new transform in the same frame, so nothing animates at
   all and the bar snaps into place.

   An animation has no such precondition: it runs on the frame the class lands, every time. That
   also removes the need for the second class and the forced-reflow trick in site.js.

   `both` holds the final frame, so the bar stays put after the animation ends. */
@keyframes hrf-slide-down {
  from { transform: translateY(-100%); }
  to   { transform: translateY(0); }
}

.site-head.is-pinned {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  will-change: transform;
  animation: hrf-slide-down .34s cubic-bezier(.22, .61, .36, 1) both;
  /* Glassy (Pete, 15 Aug): translucent cream over a blurred backdrop rather than a solid fill,
     so the page reads as continuing underneath the bar instead of being cut off by it.
     More blur compensates for the low opacity — at .55 the fill alone would not carry the nav
     over a busy photograph, but 20px flattens whatever is behind it into soft colour.
     Pete rejected a dark scrim outright ("dont make it dark") — the bar stays light. */
  background: rgba(247, 244, 239, .55);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  backdrop-filter: blur(20px) saturate(150%);
  box-shadow: 0 1px 0 rgba(228, 216, 213, .8), 0 6px 24px rgba(46, 33, 24, .05);
}

/* No backdrop-filter (older Firefox, some Android): fall back to a nearly opaque cream so the
   nav never sits unreadable over a photograph. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-head.is-pinned { background: rgba(247, 244, 239, .94); }
}

@media (prefers-reduced-motion: reduce) {
  .site-head.is-pinned { animation: none; transform: none; }
}

.site-head.is-pinned .site-head__inner { min-height: 64px; }
.site-head.is-pinned .logo .logo__img  { height: 30px; }

@media (prefers-reduced-motion: reduce) {
  .site-head.is-pinned { transition: none; transform: translateY(0); }
}

.site-head__inner {
  /* Match the section column (Pete, 15 Aug: "including the nav"). The bar itself stays
     full-bleed so its background and blur span the viewport; only the logo/nav row is
     constrained, so it lines up with the content below it. */
  max-width: var(--container);
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: 112px;
}
.site-nav { display: flex; align-items: center; gap: 40px; }
.site-nav__list { display: flex; align-items: center; gap: 40px; }
.site-nav__list a {
  /* 18px per Pete, 15 Aug. The comp measured 10.5px, but at that size the nav was unreadable
     over the hero video. Letter-spacing drops from .175em to .09em because .175em at 18px makes
     the five items 400px wider than the space between the logo and the CTA. */
  font-size: 18px;
  letter-spacing: .09em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--body);
  transition: color .18s ease;
}
.site-nav__list a:hover { color: var(--brown); }
/* No extra left margin. The 8px I originally added pushed the whole nav list 8px left of the
   mockup on every page (FILMS ink x950 vs measured 959.5; CONTACT->CTA gap 48 vs 40.5). */
.site-head__cta { margin-left: 0; }

/* --- logo variant toggle -----------------------------------------------------
   The two logo files are DIFFERENT artwork (trimmed aspect 8.02 vs 6.89), so a
   CSS filter cannot substitute one for the other — both ship and we toggle.
   NOTE the specificity: the base rule is `.logo .logo__img` (0-2-0), so a bare
   `.logo__img--light { display:none }` (0-1-0) would LOSE and both logos would
   render stacked. Every toggle below is qualified to at least match it. */
.logo { display: block; line-height: 0; }
/* 27px was the measured comp value, but the nav went from 10.5px to 18px on 15 Aug and the
   wordmark was left behind — it read as an afterthought next to the links. 40px restores the
   balance. The two logo files are different artwork with different trimmed aspects, so height
   is the only dimension set; width follows. */
.logo .logo__img        { display: block; height: 40px; width: auto; }
.logo .logo__img--light { display: none; }

body.theme-dark-top .logo .logo__img--dark  { display: none; }
body.theme-dark-top .logo .logo__img--light { display: block; }
body.theme-dark-top .site-nav__list a       { color: rgba(247,244,239,.75); }
body.theme-dark-top .site-nav__list a:hover { color: var(--cream); }

/* --- nav over the hero video (Home) ----------------------------------------
   On Home AND Films the nav links sit on top of the hero imagery, where dark-on-footage is
   unreadable (Pete, 15 Aug: "nav on this page seems to not have the shadow and font color like
   the homepage"). Both heroes put their picture on the RIGHT, which is exactly where the links
   are; the logo stays dark on the left over cream.
   (the gradient in page-index.css does the other half of this job). The LOGO is deliberately
   left dark: it sits over the cream copy column, not over the footage, so switching it to the
   light artwork would make it disappear.

   Once .is-pinned lands the bar has its own cream background and the links go back to normal. */
body.page-index .site-head:not(.is-pinned) .site-nav__list a,
body.page-portfolio .site-head:not(.is-pinned) .site-nav__list a,
body.page-about .site-head:not(.is-pinned) .site-nav__list a,
body.page-services .site-head:not(.is-pinned) .site-nav__list a,
body.page-blog .site-head:not(.is-pinned) .site-nav__list a {
  color: rgba(247, 244, 239, .92);
  text-shadow: 0 1px 3px rgba(0, 0, 0, .35);
}
body.page-index .site-head:not(.is-pinned) .site-nav__list a:hover,
body.page-portfolio .site-head:not(.is-pinned) .site-nav__list a:hover,
body.page-about .site-head:not(.is-pinned) .site-nav__list a:hover,
body.page-services .site-head:not(.is-pinned) .site-nav__list a:hover,
body.page-blog .site-head:not(.is-pinned) .site-nav__list a:hover { color: #fff; }

/* --- mobile drawer toggle (hidden on desktop) ------------------------------ */
.navtoggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 40px; height: 40px;
  padding: 0; border: 0; background: none; cursor: pointer;
}
.navtoggle__bar { display: block; width: 22px; height: 1px; background: var(--brown); }
body.theme-dark-top .navtoggle__bar { background: var(--cream); }

/* --------------------------------------------------------------- marquee --- */
/* Full-bleed strip, hairline top and bottom, items separated by a burgundy ✦. */
.marquee {
  overflow: hidden;
  border-top: 1px solid var(--rule-light);
  border-bottom: 1px solid var(--rule-light);
  padding: 14px 0;
  background: var(--cream);
}
.marquee__track { display: flex; width: max-content; animation: marquee 48s linear infinite; }
.marquee__set   { display: flex; align-items: center; flex: none; }
/* Band totals 46px: 16 (item) + 2x14 (padding) + 2x1 (hairlines). The explicit line-height is
   load-bearing — without it these 10px items inherit body's 27px strut and the band renders 57.
   All three of the Services, Blog and Portfolio measurements independently gave 46. */
.marquee__item {
  font-size: 10px;
  line-height: 16px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--body);
  white-space: nowrap;
}
/* The ✦ bearings are ASYMMETRIC in the mockup — ~21px before, ~41px after — measured
   consistently across all eight occurrences on four pages. */
.marquee__sep {
  font-style: normal;
  color: var(--burgundy);
  font-size: 9px;
  padding: 0 41px 0 21px;
  line-height: 16px;
}
@keyframes marquee { from { transform: translate3d(0,0,0); } to { transform: translate3d(-50%,0,0); } }

/* ---------------------------------------------------------------- footer --- */
.site-foot {
  overflow-x: clip; background: var(--brown); color: var(--body-on-dark); }
/* Footer band measures 358.5 tall — confirmed independently on the Services, Blog, Portfolio and
   About mockups. Every rule below sets line-height EXPLICITLY: these are all 8-12px types, and
   body's 27px strut leaking in is what inflated the band to 475. */
.site-foot__inner {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding: 64px var(--pad) 0;
  display: grid;
  grid-template-columns: 1fr 2fr 1fr 1fr;
  gap: var(--sp-6);
}
/* the wordmark sits at footer-top +121.5 in the mockup, i.e. optically centred against the
   columns rather than aligned to their top */
.site-foot__brand { align-self: center; }
/* The new mark is a STACKED two-line script (aspect ~1.5:1), not the old single-line wordmark
   (~8:1). At the inherited 28px height it rendered 42px wide and was illegible. 92px gives the
   two script lines and the FILMS rule underneath room to actually read. */
.site-foot__logo  { height: 92px; width: auto; }
.site-foot__head {
  font-family: var(--font-body);
  font-size: 8px;
  line-height: 1;
  font-weight: 400;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--tan);
  margin: 0 0 22px;
}
/* Footer body copy at the site body size (Pete, 15 Aug: "footer text to 18px"). The comp
   measured 12/22 here, but the footer blurb and nav are real reading text, so they follow the
   same 18/31 rule as the rest of the site rather than sitting two tiers below it. */
.site-foot__col p { font-size: 18px; line-height: 31px; margin: 0; }
.site-foot__links li { line-height: 31px; }
.site-foot__links li + li { margin-top: 8px; }   /* -> 30px baseline pitch (measured 30.4) */
.site-foot__links a {
  font-size: 18px;
  line-height: 31px;
  text-decoration: none;
  color: var(--body-on-dark);
  transition: color .18s ease;
}
.site-foot__links a:hover { color: var(--cream); }

.social { display: flex; gap: 12px; margin-top: 24px; }
.social__btn {
  display: grid;
  place-items: center;
  width: 32px; height: 32px;
  border: 1px solid var(--social-border);
  border-radius: 0;                       /* measured square, not rounded */
  color: #867E77;
  transition: border-color .18s ease, color .18s ease;
}
.social__btn:hover { border-color: var(--tan); color: var(--cream); }

/* The legal hairline spans the FULL 1280 container (x161…x1441) — deliberately wider than the
   1168 content column, which is why it is a ::before bled out by the inner padding rather than
   a border on the content box. */
.site-foot__legal {
  width: 100%;
  max-width: var(--container);
  /* the ::before bleeds past this box on purpose; nothing here may clip it */
  overflow: visible;
  /* Solved against the Blog mockup's footer, band top y2194: social-button bottom 2455.5
     (=261.5), hairline 2499.5 (=305.5), copyright baseline 2529.5, band bottom 2552.5 (=358.5). */
  margin: 40px auto 0;
  padding: 0 var(--pad) 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* ROW gap must be 0. `gap: 32px` on a wrapping flex row silently added 32px of row-gap
     between the ::before hairline and the legal row — 32 of the footer's 117px overshoot. */
  gap: 0 var(--sp-4);
  flex-wrap: wrap;
}
/* Full-bleed hairline (Pete, 15 Aug: "should go the whole length of the width of the viewport").
   It used to bleed only as far as the container's own padding, so it stopped at the 1600px
   column and left a gap either side on wider screens.

   50vw either side of the element's own centre reaches the viewport edge from wherever the
   column happens to sit, at any width, without needing to know the container size — and it does
   not introduce a horizontal scrollbar the way width:100vw does, because the negative margins
   pull outward rather than widening the box. */
.site-foot__legal::before {
  content: '';
  /* flex-basis: AUTO, then size with width/height. Two bugs live here, both fixed by this:
       1. `flex: 0 0 100%` + `width: 100vw` — flex-basis beats width on a flex item, so the rule
          rendered at the content width and stopped ~430px short of the right edge at 1920.
       2. `flex: 0 0 100vw` — flex-basis applies to the MAIN axis. The footer's legal row turns
          into a COLUMN under 600px, where the main axis is vertical, so the 1px hairline became
          100vw TALL and opened a ~375px hole above the copyright on every phone.
     With basis auto, the box takes width in a row and height in a column: correct in both. */
  flex: 0 0 auto;
  width: 100vw;
  height: 1px;
  background: var(--rule-dark);
  /* Pulled back to the viewport's left edge. NOT the earlier margin:-50vw + padding:50vw trick —
     padding widens the box, which pushed the document 1552px wider than the viewport and produced
     a horizontal scrollbar on every page. */
  margin-left: calc(50% - 50vw);
  margin-bottom: 20px;
}
.site-foot__copy,
.site-foot__legallinks a,
.site-foot__legallinks li {
  font-size: 10px;
  line-height: 1.2;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--legal);
  margin: 0;
  text-decoration: none;
}
.site-foot__legallinks { display: flex; gap: 24px; }

/* ----------------------------------------------------------------- cards --- */
/* Shared card shell. Radius 0 everywhere — verified square on every page. */
.card { background: transparent; border-radius: 0; }
.card__media { position: relative; overflow: hidden; border-radius: 0; }
.card__media img { width: 100%; height: 100%; object-fit: cover; }
.card__title { font-family: var(--font-display); font-weight: 400; color: var(--brown); }
.card__meta  { font-size: 12px; line-height: 1.15; letter-spacing: .12em; text-transform: uppercase; color: var(--body); }

/* ---------------------------------------------------------------- reveal --- */
/* animations.js drives these. Deliberately NOT hidden in CSS — no-JS and QA
   (?nomotion=1) must read the final state. */
.reveal { will-change: opacity, transform; }

/* ============================================================== responsive ===
   The mockups are a single 1602px desktop frame — there are no tablet or mobile
   comps. Everything below this line is our design call, not a reproduction.
   See notes/design-system.md.
   ========================================================================== */
@media (max-width: 1200px) {
  :root { --pad: 40px; }
  .site-nav, .site-nav__list { gap: 28px; }
}

@media (max-width: 900px) {
  :root { --pad: 24px; }

  .navtoggle { display: flex; }

  .site-head__inner { min-height: 80px; }

  /* The logo was STRETCHING. `img { max-width: 100% }` (the global reset) shrinks its width to
     fit the narrow bar while `height: 40px` holds the height fixed — so the artwork compresses
     horizontally. Scaling the height down instead keeps the aspect ratio: at 26px the wordmark
     is ~210px wide and fits beside the toggle on a 375px screen without the reset ever biting. */
  .logo .logo__img { height: 26px; }
  .site-head.is-pinned .logo .logo__img { height: 22px; }

  /* Heroes are pulled up under the header. That offset is the HEADER'S height, which is 80px
     here, not the 112px of the desktop bar — pulling 112 left a strip of page background above
     the hero on every phone. */
  .idx-hero,
  .portfolio-hero--split,
  .services-hero--split,
  .blog-hero--split,
  .about-hero { margin-top: -80px; }

  /* FULL-SCREEN DRAWER (Pete, 15 Aug: "menu not working on mobile, make it full screen and the
     items staggered animate in").

     It was an absolutely-positioned panel hanging off the header, and it was effectively
     INVISIBLE on Home, Films, About, Services and Blog: those pages colour the nav links cream
     so they read over the hero photograph, and the drawer's own background is also cream. White
     on white. The colour is forced back to brown below, with enough specificity to beat the
     page-scoped rules.

     position: fixed with inset:0 rather than absolute — anchored to the header, the panel
     inherits its stacking context and can be clipped by it, and it also scrolls away with the
     page instead of covering it. */
  .site-nav {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: var(--sp-4);
    padding: 96px var(--pad) var(--sp-6);
    background: var(--cream);
    border-top: 0;
    /* Closed state: not display:none — a hidden element cannot transition, and the items must
       animate. visibility keeps it out of the tab order and off the screen reader. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    transition: opacity .3s ease, transform .3s ease, visibility 0s linear .3s;
    overflow-y: auto;
  }
  body.nav-open .site-nav {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: opacity .3s ease, transform .3s ease, visibility 0s;
  }

  .site-nav__list { flex-direction: column; align-items: flex-start; gap: var(--sp-3); width: 100%; }
  /* Beat body.page-* .site-head:not(.is-pinned) .site-nav__list a — that rule makes the links
     cream for the hero, which is unreadable on the cream drawer. */
  body .site-head .site-nav__list a,
  body.page-index .site-head:not(.is-pinned) .site-nav__list a,
  body.page-portfolio .site-head:not(.is-pinned) .site-nav__list a,
  body.page-about .site-head:not(.is-pinned) .site-nav__list a,
  body.page-services .site-head:not(.is-pinned) .site-nav__list a,
  body.page-blog .site-head:not(.is-pinned) .site-nav__list a {
    font-size: 28px;
    letter-spacing: .06em;
    color: var(--brown);
    text-shadow: none;
  }
  .site-head__cta { align-self: flex-start; }

  /* Staggered entrance. Each item starts low and fades up; the delay is per-position so they
     cascade rather than arriving together. Applied only while open, so closing is immediate and
     the items do not animate out one by one. */
  .site-nav__list li,
  .site-nav .site-head__cta {
    opacity: 0;
    transform: translateY(14px);
  }
  body.nav-open .site-nav__list li,
  body.nav-open .site-nav .site-head__cta {
    opacity: 1;
    transform: none;
    transition: opacity .42s ease, transform .42s cubic-bezier(.22, .61, .36, 1);
  }
  body.nav-open .site-nav__list li:nth-child(1) { transition-delay: .10s; }
  body.nav-open .site-nav__list li:nth-child(2) { transition-delay: .17s; }
  body.nav-open .site-nav__list li:nth-child(3) { transition-delay: .24s; }
  body.nav-open .site-nav__list li:nth-child(4) { transition-delay: .31s; }
  body.nav-open .site-nav__list li:nth-child(5) { transition-delay: .38s; }
  body.nav-open .site-nav .site-head__cta        { transition-delay: .45s; }

  /* The toggle must sit ON TOP of the full-screen panel, or it cannot be pressed to close. */
  .navtoggle { position: relative; z-index: 61; }

  /* CRITICAL: a transformed ancestor becomes the containing block for position:fixed children.
     .site-head.is-pinned runs the slide-down keyframe and carries will-change:transform, so once
     the reader had scrolled, the "full screen" drawer was being contained — and clipped — by the
     header's own 80px box. That is why opening the menu after scrolling showed a stub panel with
     the page still visible underneath.
     While the drawer is open the header needs no transform of its own: it is already at the top
     of a locked page. Clearing them restores the viewport as the containing block. */
  body.nav-open .site-head.is-pinned {
    animation: none;
    transform: none;
    will-change: auto;
    /* backdrop-filter ALSO establishes a containing block for position:fixed descendants —
       same rule as transform, and the reason clearing the transform alone did not fix this.
       The glass bar was still trapping the drawer inside its own 80px box. filter and
       perspective behave identically, so all four are cleared while the menu is open. */
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    filter: none;
    perspective: none;
    /* With the blur gone the bar needs a solid ground, or the drawer shows through it. */
    background: var(--cream);
  }

  /* Page behind must not scroll while the overlay is up. */
  body.nav-open { overflow: hidden; }

  @media (prefers-reduced-motion: reduce) {
    .site-nav, .site-nav__list li, .site-nav .site-head__cta { transition: none !important; }
    .site-nav__list li, .site-nav .site-head__cta { transform: none; }
  }

  .site-foot__inner { grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
  .site-foot__brand { grid-column: 1 / -1; }
}

@media (max-width: 600px) {
  body { font-size: 15px; line-height: 25px; }
  .site-foot__inner { grid-template-columns: 1fr; }
  .site-foot__legal { flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
  .marquee__sep { padding: 0 18px; }
}

/* motion off: honour the OS setting AND keep the marquee measurable for QA */
@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* ============================================================ split feature ===
   Image + copy, side by side. The pattern that already appears as .about-split on
   About and .idx-location on Home — promoted to a site-wide component (Pete,
   15 Aug) because it is going to be reused across the site in four combinations:

     .splitsec                      image left, full-bleed   (the original)
     .splitsec--flip                image right, full-bleed
     .splitsec--inset               image left, inset 100px
     .splitsec--inset.splitsec--flip  image right, inset 100px

   --flip uses `direction` rather than `order` so the DOM order never changes:
   the image stays first in the markup, which is what a screen reader and a
   no-CSS render should see. order: would achieve the same visual result but
   silently desynchronise the reading order from the visual one.

   Existing .about-split and .idx-location are untouched. This is additive. */
.splitsec {
  display: grid;
  grid-template-columns: 1fr 1fr;
  background: var(--cream);
  align-items: center;
  /* Same 1600 column as every other section (Pete, 15 Aug: "even our newly created image and
     text section should be max 1600"). It was full-bleed, so on a wide display the photograph
     ran to the screen edge while the bands above and below stopped at 1600 — the page lost its
     left edge at that one section. */
  max-width: var(--container);
  margin-inline: auto;
}
.splitsec--flip { direction: rtl; }
.splitsec--flip > * { direction: ltr; }

.splitsec__media { position: relative; overflow: hidden; }
.splitsec__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Inset variant: the photograph keeps its own square shape instead of stretching to the full
   section height. The 100px of cream that originally surrounded it was removed on 15 Aug
   (Pete) — the image now meets the section edges, and only the aspect-ratio distinguishes this
   from the plain variant. */
.splitsec--inset .splitsec__media { padding: 0; }
.splitsec--inset .splitsec__media img { aspect-ratio: 1 / 1; height: auto; }

.splitsec__copy {
  padding: 96px 56px;
  align-self: center;
}
/* The copy always sits nearer the middle of the page than the outer edge, in both
   directions — so the inner padding follows the flip. */
.splitsec:not(.splitsec--flip) .splitsec__copy { padding-left: 80px; }
.splitsec--flip .splitsec__copy { padding-right: 80px; }
/* Inset already supplies breathing room on the image side; drop the extra inner pad. */
.splitsec--inset .splitsec__copy { padding-left: 56px; padding-right: 56px; }

.splitsec__sub    { margin-top: 13px; }
.splitsec__script { margin: 34px 0 0; font-size: clamp(18px, 1.4vw, 22.5px); line-height: 1; }
.splitsec__body   { max-width: 34em; margin-top: 30px; }

@media (max-width: 900px) {
  /* One column, COPY FIRST then the photograph — in every variant (Pete, 15 Aug: "swap image and
     text on mobile so we dont have 2 in a row").

     The heroes stack copy-then-image. These bands used to stack image-then-copy, so a hero
     followed by a band produced hero-copy, HERO-IMAGE, BAND-IMAGE, band-copy — two photographs
     back to back with no text between them. Matching the hero's order makes the whole page
     alternate text, image, text, image the way it does on desktop.

     `direction: ltr` also has to be restored here or --flip would reverse the stack as well. */
  .splitsec, .splitsec--flip { grid-template-columns: 1fr; direction: ltr; }
  .splitsec__copy  { order: 1; }
  .splitsec__media { order: 2; }
  .splitsec__copy,
  .splitsec:not(.splitsec--flip) .splitsec__copy,
  .splitsec--flip .splitsec__copy,
  .splitsec--inset .splitsec__copy { padding: 48px var(--pad); }
  .splitsec--inset .splitsec__media { padding: 0; }
}

/* ================================================================= carousel ===
   Horizontal card carousel with prev/next controls underneath (Pete, 15 Aug).
   Used by the Home "Featured Wedding Films" band and the Portfolio grid.

   Native scroll-snap does the work, not JS. That means: it still scrolls by
   touch/trackpad if the script never runs, it keeps momentum scrolling on iOS,
   and the buttons are a convenience rather than the only way through. The script
   only moves the scroll position and toggles the disabled state.

   .carousel__track REPLACES whatever grid the list had, so the same markup can be
   a static grid on one page and a carousel on another. */
.carousel { position: relative; }

/* `ul.` deliberately: the lists this is applied to (.idx-filmgrid, .portfolio-grid) each
   declare `grid-template-columns: repeat(3, 1fr)` in a page-*.css that loads AFTER this file.
   An explicit template makes grid-auto-columns inert — the four cards get packed into three
   fixed tracks and wrap instead of overflowing, so the track never becomes scrollable and the
   arrows sit permanently disabled. The type selector takes this to 0-1-1 so it wins on
   specificity rather than order, and the template is reset to none so the auto tracks apply. */
ul.carousel__track,
.carousel__track {
  display: grid;
  grid-template-columns: none;
  grid-auto-flow: column;
  /* three cards visible, matching the 3-up grid this replaces */
  grid-auto-columns: calc((100% - 2 * var(--sp-3)) / 3);
  gap: var(--sp-3);
  overflow-x: auto;
  /* overflow-y MUST be stated. Per spec, when one axis is `auto` the other computes to `auto`
     too, so the track quietly became a vertical scroll container — and a wheel gesture over the
     images was captured by it instead of scrolling the page, which reads as the page seizing up
     (Pete, 15 Aug: "it gets stuck trying to scroll the carousel up"). `hidden` on the cross axis
     hands vertical wheel back to the document.
     overscroll-behavior-x stops a horizontal fling chaining into browser back-navigation. */
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  /* Touch devices: pan horizontally on the track, but let vertical swipes scroll the page. */
  touch-action: pan-x pan-y;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* room for the cards' hover lift + focus ring, clipped otherwise by overflow */
  padding-bottom: 4px;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.carousel__track::-webkit-scrollbar { display: none; }
.carousel__track > * { scroll-snap-align: start; min-width: 0; }

@media (prefers-reduced-motion: reduce) {
  .carousel__track { scroll-behavior: auto; }
}

/* Controls sit OUTSIDE the cards, in the page gutter, vertically centred on the IMAGES
   (Pete, 15 Aug). Two separate requirements, both worth spelling out:

   OUTSIDE — the content column is 1168px inside a 1280px container, so there is a 56px gutter
   either side at desktop and a 48px button drops into it with room to spare. Negative offsets
   put them there rather than over the photographs.

   CENTRED ON THE IMAGES — a card is image + title + description, so centring on the card sits
   the buttons well below the picture. site.js measures the first card's MEDIA box and publishes
   it as --media-h; the fallbacks degrade to the track height and then to 50%, so the buttons are
   never wildly misplaced even if the script never runs. */
.carousel__nav { display: contents; }

.carousel__btn {
  position: absolute;
  top: calc(var(--media-h, var(--track-h, 100%)) / 2);
  transform: translateY(-50%);
  z-index: 3;
}
.carousel__btn[data-carousel="prev"] { left: -64px; }
.carousel__btn[data-carousel="next"] { right: -64px; }

/* The outside position only works when the viewport can hold the 1440px column PLUS a 64px
   button either side — i.e. above 1568px. Keep this in step with --container: when the threshold
   was too low the buttons hung past the content edge and put a horizontal scrollbar on every
   page. Below it they sit inside the track; the cream fill keeps them legible over photographs. */
@media (max-width: 1568px) {
  .carousel__btn[data-carousel="prev"] { left: 10px; }
  .carousel__btn[data-carousel="next"] { right: 10px; }
}

.carousel__btn {
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  border: 1px solid var(--tan);
  border-radius: 0;              /* square, like .social__btn — the site has no rounded controls */
  background: var(--cream);
  color: var(--body);
  cursor: pointer;
  padding: 0;
  transition: border-color .18s ease, color .18s ease, opacity .18s ease;
}
.carousel__btn:hover:not(:disabled) { border-color: var(--burgundy); color: var(--burgundy); }
.carousel__btn:focus-visible { outline: 2px solid var(--burgundy); outline-offset: 2px; }
/* With the infinite loop the ends are never reached, so this only applies to a track
   that does not overflow — and that case hides the controls entirely. */
.carousel__btn:disabled { opacity: .3; cursor: default; }
.carousel__btn svg { width: 16px; height: 16px; display: block; }

/* On the dark bands the tan hairline is the only thing that reads. */
.band--brown .carousel__btn, .band--burgundy .carousel__btn { color: var(--tan); }
.band--brown .carousel__btn:hover:not(:disabled),
.band--burgundy .carousel__btn:hover:not(:disabled) { border-color: var(--cream); color: var(--cream); }

@media (max-width: 900px) { ul.carousel__track, .carousel__track { grid-auto-columns: calc((100% - var(--sp-3)) / 2); } }
@media (max-width: 600px) { ul.carousel__track, .carousel__track { grid-auto-columns: 78%; } }
/* Fewer cards than fit: the track cannot scroll, so the controls would be permanently
   disabled. Hide them instead of showing two dead buttons. */
.carousel--static .carousel__nav { display: none; }

/* ============================================================== line reveal ===
   The "text rises up out of the line it sits on" heading effect (Pete, 15 Aug).

   Each visual LINE of the heading gets an overflow:hidden wrapper; the words inside start
   translated fully below that wrapper and slide up into it, so the type appears to emerge from
   the baseline rather than fading in as a block. Lines are staggered top to bottom.

   Split into lines by site.js at runtime, because the heading wraps responsively — where the
   line breaks fall is only known after layout, and changes on resize.

   .lines-ready is set by the script once it has done the splitting. Until then, and forever if
   the script never runs, the heading is simply itself: no hidden text, nothing to go wrong. */
.lines-ready .lr-line {
  display: block;
  overflow: hidden;
}
.lines-ready .lr-line__inner {
  display: block;
  transform: translateY(110%);
  will-change: transform;
}
.lines-ready.lr-play .lr-line__inner {
  transform: translateY(0);
  transition: transform .85s cubic-bezier(.22, .61, .36, 1);
  transition-delay: var(--lr-delay, 0s);
}

@media (prefers-reduced-motion: reduce) {
  .lines-ready .lr-line__inner { transform: none; transition: none; }
}

/* Anchor landings must clear the pinned header (Pete, 15 Aug — the Home carousel deep-links to
   /portfolio/#film-<slug>). Without this the browser scrolls the section's true top to y=0 and
   the 64px bar covers the couple's name. scroll-margin-top is the modern, layout-free way to do
   it — no spacer elements, no negative-margin anchor tricks. */
:target { scroll-margin-top: 96px; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* .hero-vh REMOVED 15 Aug — superseded by the .hero component below, which owns the same 100vh
   sum plus the whole hero layout. Two rules doing the same job is exactly how the five page
   heroes drifted apart in the first place. */

/* ================================================================== FAQ ===
   Accordion on <details>/<summary>. Single column with a hairline between rows — a two-column
   grid of collapsing panels makes the ones beside each other jump as siblings expand.

   The height animation is driven from site.js (see the FAQ block there): <details> cannot be
   transitioned natively, because it flips between height:0 and auto in one frame. */
.faq { max-width: 62em; margin-inline: auto; border-top: 1px solid var(--rule-light); }
.faq__item { border-bottom: 1px solid var(--rule-light); }

.faq__q {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: 26px 0;
  cursor: pointer;
  list-style: none;                 /* kill the default disclosure triangle */
  font-family: var(--font-display);
  font-size: clamp(18px, 1.5vw, 22px);
  line-height: 1.4;
  color: var(--brown);
  transition: color .2s ease;
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q:hover { color: var(--burgundy); }
.faq__q:focus-visible { outline: 2px solid var(--burgundy); outline-offset: 4px; }

.faq__chev {
  flex: none;
  color: var(--burgundy);
  transform: rotate(0deg);
  transition: transform .32s cubic-bezier(.22, .61, .36, 1);
  align-self: center;
}
.faq__item[open] .faq__chev { transform: rotate(180deg); }

/* The panel is the animated element; the inner .faq__a carries the padding so the wrapper can
   go to height:0 without its own padding holding it open. */
.faq__panel { overflow: hidden; }
.faq__a { padding: 0 0 26px; max-width: 54em; }

/* No-JS / reduced-motion: open and close instantly, never stuck shut. */
@media (prefers-reduced-motion: reduce) {
  .faq__panel { transition: none !important; }
  .faq__chev  { transition: none; }
}

/* ================================================================== hero ===
   THE hero. One component, used by Home, Films, About, Services and Blog.

   Written 15 Aug after Pete asked "why are they all different!!!!!" — and he was right. Each
   page had grown its own hero layout (.idx-hero, .portfolio-hero--split, .services-hero--split,
   .blog-hero--split, .about-split--copyfirst), so every fix had to be made five times and they
   drifted apart between fixes. Mobile stacking order, the header offset, the 100vh sum and the
   copy padding had all diverged. This block is now the single source; the page stylesheets keep
   only their own TYPE and COLOUR, never layout.

   Contract:
     - copy LEFT, photograph RIGHT (logo stays over cream, nav sits over the picture)
     - pulled up under the transparent header
     - hero + the marquee under it = exactly one screen
     - on mobile: one column, COPY FIRST, so a hero never butts an image against the next
       section's image
   ========================================================================= */
.hero {
  --hero-head-h: 112px;      /* desktop bar */
  --hero-marquee-h: 46px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: 0;
  padding: 0;
  margin-top: calc(var(--hero-head-h) * -1);
  background: var(--cream);
  /* dvh with a vh fallback: 100vh excludes the retracted mobile address bar, which would push
     the marquee below the fold — the exact thing this is here to prevent. */
  height: calc(100vh - var(--hero-marquee-h));
  height: calc(100dvh - var(--hero-marquee-h));
}

.hero__copy {
  padding: clamp(148px, 18vh, 208px) 40px clamp(64px, 9vh, 104px) var(--pad);
  box-sizing: border-box;
  align-self: center;
  overflow-y: auto;          /* yields before anything is clipped on a short screen */
}
/* The comps solved their measures against a narrow one-column hero; in the split the copy column
   is much wider, and a capped measure leaves the body stopping short of the heading above it. */
.hero__copy > * { max-width: none; }

.hero__media { position: relative; overflow: hidden; height: 100%; min-height: 0; }
.hero__media img,
.hero__media video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Scrim behind the nav: the links land on whatever the photograph happens to show, and
   dark-on-photo is unreadable. Fades to nothing; never intercepts clicks. */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 180px;
  background: linear-gradient(to bottom,
              rgba(20, 12, 8, .55) 0%,
              rgba(20, 12, 8, .32) 45%,
              rgba(20, 12, 8, 0) 100%);
  pointer-events: none;
  z-index: 2;
}

@media (max-width: 900px) {
  .hero {
    --hero-head-h: 80px;     /* the mobile bar is shorter — using 112 left a strip of page
                                background above the hero on every phone */
    grid-template-columns: 1fr;
    height: auto;            /* a fixed screen height crushes the copy once stacked */
  }
  /* The copy's bottom padding is the gap BETWEEN copy and photograph now that copy stacks
     first — but it was also landing under the image, because the image is the last thing in the
     hero. Measured 40px of cream between the photo and the marquee (Pete, 15 Aug: "needs to be
     touching the bottom of the image"). Padding moves onto the copy only, and the media is
     pinned flush to the section's bottom edge. */
  /* Text sits higher (Pete, 15 Aug: "we need hero text higher on mobile"). The bar is 80px and
     transparent, so the copy only needs to clear it — 32px of extra air pushed the heading a
     long way down a 844px screen before any of it was readable. 14px is enough separation
     without the eyebrow tucking under the logo. */
  .hero__copy  { order: 1; padding: calc(80px + 14px) var(--pad) 28px; overflow: visible; }
  .hero__media { order: 2; height: auto; aspect-ratio: 4 / 5; margin-bottom: 0; }
  /* The page stylesheets load AFTER this one, so a leftover `.portfolio-hero { padding: … }`
     silently beat `.hero { padding-bottom: 0 }` on equal specificity and put 40px of cream
     between the photograph and the marquee. Those rules are now deleted; the type selector here
     is belt-and-braces so a future one cannot reintroduce the gap. */
  section.hero { padding-bottom: 0; }
}

/* Carousel arrows on mobile (Pete, 15 Aug: "much smaller... and all the way left and right").
   At 48px they covered a serious share of a 375px card; 34px sits over the image without
   burying it, and pinning them to the very edges keeps them clear of the card's subject. */
@media (max-width: 600px) {
  .carousel__btn { width: 34px; height: 34px; }
  .carousel__btn svg { width: 12px; height: 12px; }
  .carousel__btn[data-carousel="prev"] { left: 0; }
  .carousel__btn[data-carousel="next"] { right: 0; }
}

/* ============================================================ video modal ===
   "The video should play in a large popup on desktop and full screen with an x on mobile"
   (Pete, 15 Aug). Any link to a video file opens here instead of navigating away.

   <dialog> rather than a hand-rolled div: the browser gives focus trapping, Escape-to-close,
   inertness of the page behind and the ::backdrop pseudo-element for free. Rebuilding those by
   hand is where accessibility bugs live. */
.vmodal {
  width: min(1100px, 92vw);
  max-width: none;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: visible;
}
.vmodal::backdrop { background: rgba(20, 12, 8, .82); }
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .vmodal::backdrop { -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
}

.vmodal__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
  overflow: hidden;
}
.vmodal__frame video,
.vmodal__frame iframe {
  width: 100%;
  height: 100%;
  display: block;
  border: 0;
}

.vmodal__close {
  position: absolute;
  top: -52px;
  right: 0;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border: 1px solid rgba(247, 244, 239, .5);
  background: transparent;
  color: var(--cream);
  cursor: pointer;
  padding: 0;
  transition: border-color .18s ease, background-color .18s ease;
}
.vmodal__close:hover { border-color: var(--cream); background: rgba(247, 244, 239, .12); }
.vmodal__close:focus-visible { outline: 2px solid var(--cream); outline-offset: 3px; }
.vmodal__close svg { width: 16px; height: 16px; }

/* Opening animation — scale up from slightly small, so it reads as arriving rather than blinking. */
.vmodal[open] { animation: hrf-vmodal-in .26s cubic-bezier(.22, .61, .36, 1); }
@keyframes hrf-vmodal-in {
  from { opacity: 0; transform: scale(.97); }
  to   { opacity: 1; transform: none; }
}

@media (max-width: 700px) {
  /* Full screen on mobile: the 16:9 frame is centred in a black field and the X moves INSIDE
     the viewport — at -52px it would sit off the top of the screen and be unreachable. */
  .vmodal {
    width: 100vw;
    height: 100dvh;
    max-height: none;
    margin: 0;
    background: #000;
    display: grid;
    place-items: center;
  }
  .vmodal__frame { width: 100vw; }
  .vmodal__close {
    top: max(12px, env(safe-area-inset-top));
    right: 12px;
    z-index: 2;
    background: rgba(20, 12, 8, .55);
  }
}

@media (prefers-reduced-motion: reduce) { .vmodal[open] { animation: none; } }
