/* ==========================================================================
   Long Light Weddings - site stylesheet
   No framework, no build step. Fonts are self-hosted in /fonts.
   ========================================================================== */

/* --- Fonts ---------------------------------------------------------------
   Google serves Cormorant Garamond and Jost as variable woff2 files, so one
   file covers the whole weight axis; the ranges below let the browser
   interpolate 300 and 400 from the same download.                          */

@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/cormorant-garamond-normal-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/cormorant-garamond-normal-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/cormorant-garamond-italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/cormorant-garamond-italic-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/jost-normal-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/jost-normal-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- Tokens ------------------------------------------------------------- */

:root {
  --cream:      #fef7f1;
  --cream-alt:  #f6ede4;
  --dark:       #2b2724;
  --body:       #5a5350;
  --muted:      #8f8681;
  --gold:       #be9e76;
  --gold-dark:  #a27f53;
  --white:      #ffffff;
  --hairline:   rgba(90, 83, 80, 0.18);
  --hairline-on-dark: rgba(255, 255, 255, 0.22);

  --serif: 'Cormorant Garamond', 'Times New Roman', Times, serif;
  --sans:  'Jost', 'Helvetica Neue', Helvetica, Arial, sans-serif;

  --wrap: 1200px;
  --gutter: 16px;
  --nav-h: 76px;
  /* width of the scrollbar, measured by js; keeps 100vw bleeds honest */
  --sbw: 0px;

  --section-y: clamp(72px, 9vw, 140px);
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* --- Reset -------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--cream);
  color: var(--body);
  font-family: var(--sans);
  font-weight: 300;
  font-size: 16px;
  line-height: 1.85;
  letter-spacing: 0.012em;
  overflow-x: hidden;
}

img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
figure { margin: 0; }

::selection { background: var(--gold); color: #fff; }

:focus-visible { outline: 1px solid var(--gold-dark); outline-offset: 3px; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--dark); color: #fff; padding: 12px 18px;
  font-family: var(--sans); font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase;
}
.skip-link:focus { left: 0; }

/* Present to a screen reader and to a search engine, absent from the page.
   The gallery's <h1> is drawn this way: the owner took the page head off on
   purpose, so the page has no heading anybody should see - but it still needs
   one, and it must cost the layout nothing (the film stage stays the first
   thing in flow, and the scroll snapping is untouched). */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- Layout ------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--narrow { max-width: 780px; }
.wrap--wide   { max-width: 1440px; }
/* the photographic columns get most of the browser width, with wide, even
   gutters so that nothing ever touches the edge except a deliberate bleed */
.wrap--gallery { max-width: 1800px; padding-inline: clamp(16px, 4.5vw, 80px); }

.section { padding-block: var(--section-y); }
.section--tight { padding-block: clamp(48px, 6vw, 88px); }

.section--alt  { background: var(--cream-alt); }
.section--dark { background: var(--dark); color: rgba(255, 255, 255, 0.78); }
.section--dark h1, .section--dark h2, .section--dark h3, .section--dark h4 { color: #fff; }
.section--dark .lede { color: rgba(255, 255, 255, 0.86); }
.section--dark .eyebrow { color: var(--gold); }
.section--dark .rule { background: var(--hairline-on-dark); }
.section--dark .small { color: rgba(255, 255, 255, 0.55); }

.center { text-align: center; }

/* --- Type --------------------------------------------------------------- */

h1, h2, h3, h4 {
  font-family: var(--serif);
  font-weight: 300;
  color: var(--dark);
  margin: 0;
  line-height: 1.1;
  letter-spacing: 0.005em;
}

h1 { font-size: clamp(2.6rem, 6vw, 4.6rem); }
h2 { font-size: clamp(2.1rem, 4.6vw, 3.6rem); }
h3 { font-size: clamp(1.45rem, 2.4vw, 1.95rem); line-height: 1.22; }
h4 { font-size: clamp(1.15rem, 1.7vw, 1.35rem); line-height: 1.3; }

h1 em, h2 em, h3 em, .italic { font-style: italic; }

.eyebrow {
  display: block;
  font-family: var(--sans);
  font-weight: 400;
  font-size: 11px;
  line-height: 1.6;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold-dark);
  margin: 0 0 22px;
}
@media (min-width: 900px) { .eyebrow { font-size: 12px; } }

p { margin: 0 0 1.15em; }
p:last-child { margin-bottom: 0; }

.lede {
  font-family: var(--serif);
  font-size: clamp(1.25rem, 2.1vw, 1.6rem);
  line-height: 1.55;
  color: var(--body);
}

.small { font-size: 13px; line-height: 1.8; color: var(--muted); }

.rule { display: block; width: 100%; height: 1px; border: 0; margin: 0; background: var(--hairline); }
.rule--short { width: 64px; margin-inline: auto; background: var(--gold); }

/* --- Buttons ------------------------------------------------------------ */

.btn {
  display: inline-block;
  font-family: var(--sans);
  font-weight: 400;
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  line-height: 1;
  padding: 16px 34px;
  border: 1px solid var(--dark);
  background: var(--dark);
  color: #fff;
  transition: background 0.35s var(--ease), color 0.35s var(--ease), border-color 0.35s var(--ease);
  cursor: pointer;
}
.btn:hover, .btn:focus-visible { background: var(--gold-dark); border-color: var(--gold-dark); color: #fff; }

.btn--outline { background: transparent; color: var(--dark); }
.btn--outline:hover, .btn--outline:focus-visible { background: var(--dark); color: #fff; border-color: var(--dark); }

.btn--light { background: transparent; border-color: rgba(255, 255, 255, 0.6); color: #fff; }
.btn--light:hover, .btn--light:focus-visible { background: #fff; color: var(--dark); border-color: #fff; }

/* the solid one of a pair standing over a photograph: the same weight the
   dark .btn has on the cream, turned the other way up so it reads against a
   picture rather than against the page */
.btn--solid-light { background: #fff; border-color: #fff; color: var(--dark); }
.btn--solid-light:hover, .btn--solid-light:focus-visible {
  background: var(--gold-dark); border-color: var(--gold-dark); color: #fff;
}

.link-quiet {
  display: inline-block;
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold-dark);
  border-bottom: 1px solid var(--gold);
  padding-bottom: 3px;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.link-quiet:hover { color: var(--dark); border-color: var(--dark); }

/* --- Nav ---------------------------------------------------------------- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 90;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background 0.4s var(--ease), border-color 0.4s var(--ease);
}
.nav__inner {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.nav__logo { display: block; flex: none; }
.nav__logo img { width: 150px; height: auto; }
.nav__logo .logo--gold { display: none; }

.nav__links { display: flex; align-items: center; gap: clamp(18px, 2.4vw, 34px); }
.nav__links a {
  font-family: var(--sans);
  font-weight: 400;
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #fff;
  padding-block: 6px;
  border-bottom: 1px solid transparent;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.nav__links a:hover { border-bottom-color: currentColor; }

/* solid state, switched on by js once the page is scrolled */
.nav.is-solid { background: var(--cream); border-bottom-color: var(--hairline); }
.nav.is-solid .nav__links a { color: var(--dark); }
.nav.is-solid .nav__links a:hover { color: var(--gold-dark); }
.nav.is-solid .nav__logo .logo--white { display: none; }
.nav.is-solid .nav__logo .logo--gold { display: block; }
.nav.is-solid .nav__toggle span { background: var(--dark); }

/* pages with no hero start solid */
.nav--plain { background: var(--cream); border-bottom-color: var(--hairline); }
.nav--plain .nav__links a { color: var(--dark); }
.nav--plain .nav__links a:hover { color: var(--gold-dark); }
.nav--plain .nav__logo .logo--white { display: none; }
.nav--plain .nav__logo .logo--gold { display: block; }
.nav--plain .nav__toggle span { background: var(--dark); }

.nav__toggle { display: none; width: 34px; height: 22px; position: relative; flex: none; }
.nav__toggle span {
  position: absolute; left: 0; right: 0; height: 1px;
  background: #fff;
  transition: transform 0.35s var(--ease), opacity 0.25s var(--ease), background 0.4s var(--ease);
}
.nav__toggle span:nth-child(1) { top: 4px; }
.nav__toggle span:nth-child(2) { top: 11px; }
.nav__toggle span:nth-child(3) { top: 18px; }

@media (max-width: 899px) {
  .nav__toggle { display: block; z-index: 2; }
  .nav__links {
    position: fixed;
    inset: 0;
    flex-direction: column;
    justify-content: center;
    gap: 30px;
    background: var(--cream);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    transition: opacity 0.4s var(--ease), transform 0.4s var(--ease), visibility 0.4s;
  }
  .nav__links a { color: var(--dark) !important; font-size: 13px; letter-spacing: 0.24em; }
  .nav.is-open .nav__links { opacity: 1; visibility: visible; transform: none; }
  .nav.is-open .nav__toggle span { background: var(--dark); }
  .nav.is-open .nav__toggle span:nth-child(1) { top: 11px; transform: rotate(45deg); }
  .nav.is-open .nav__toggle span:nth-child(2) { opacity: 0; }
  .nav.is-open .nav__toggle span:nth-child(3) { top: 11px; transform: rotate(-45deg); }
  .nav.is-open .nav__logo { position: relative; z-index: 2; }
  .nav.is-open .nav__logo .logo--white { display: none; }
  .nav.is-open .nav__logo .logo--gold { display: block; }
}

body.is-locked { overflow: hidden; }

/* --- Hero --------------------------------------------------------------- */

/* The hero is a full-viewport band and is drawn by exactly the same rule: the
   media layer is the frame at its natural scale for the window's width -
   width times --band-nat, which the bake writes on the section - and never
   shorter than the hero itself, so nothing is ever scaled up to fill a layer
   that is taller than the picture. --yf, the owner's Position as a fraction,
   says which part of a frame taller than the window the hero shows; at 0.5 it
   is centred. The hero's own parallax lags the page rather than swinging
   about a centre, and it hides its own travel inside overflow: hidden, so it
   needs no slack reserved for it. */
.hero {
  --band-nat: 0.5625;                               /* media height / width */
  --hero-w: calc(100vw - var(--sbw, 0px));
  --hero-layer: max(calc(var(--hero-w) * var(--band-nat)), 100%);
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--dark);
}
.hero__media {
  position: absolute;
  left: 0; right: 0;
  height: var(--hero-layer);
  top: calc((100% - var(--hero-layer)) * var(--yf, 0.5));
}
.hero__media picture { display: block; width: 100%; height: 100%; }
.hero__media img { width: 100%; height: 100%; object-fit: cover; }
.hero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(to bottom,
    rgba(24, 21, 19, 0.46) 0%,
    rgba(24, 21, 19, 0.14) 30%,
    rgba(24, 21, 19, 0.26) 62%,
    rgba(24, 21, 19, 0.58) 100%);
}
.hero__inner { position: relative; text-align: center; padding: var(--nav-h) var(--gutter) 0; width: 100%; }
.hero__logo {
  width: min(560px, 76vw);
  margin: 0 auto;
  filter: drop-shadow(0 2px 18px rgba(0, 0, 0, 0.4));
}
.hero__tagline {
  margin: 28px auto 0;
  max-width: 34ch;
  color: #fff;
  font-family: var(--sans);
  font-weight: 300;
  font-size: clamp(11px, 1.1vw, 13px);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  line-height: 2.1;
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.5);
}
/* The tagline is the home page's <h1>, because the page needs one and this is
   what the page actually says first. Nothing about how it looks changes: every
   value the heading rules further up would otherwise impose is restated here,
   as the paragraph's own, at a specificity that beats a bare h1 selector. */
h1.hero__tagline {
  margin: 28px auto 0;
  color: #fff;
  font-family: var(--sans);
  font-weight: 300;
  font-size: clamp(11px, 1.1vw, 13px);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  line-height: 2.1;
}
.hero__cue {
  position: absolute;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%);
  color: rgba(255, 255, 255, 0.85);
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.5);
}
.hero__cue::after {
  content: '';
  width: 1px;
  height: 44px;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0));
  animation: cue 2.6s var(--ease) infinite;
  transform-origin: top;
}
@keyframes cue {
  0%   { transform: scaleY(0.2); opacity: 0.15; }
  45%  { transform: scaleY(1);   opacity: 1; }
  100% { transform: scaleY(1);   opacity: 0; }
}

/* --- Reveal on scroll --------------------------------------------------- */

.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 1.05s var(--ease), transform 1.05s var(--ease);
}
.reveal.is-in { opacity: 1; transform: none; }
.reveal[data-delay="1"] { transition-delay: 0.1s; }
.reveal[data-delay="2"] { transition-delay: 0.2s; }
.reveal[data-delay="3"] { transition-delay: 0.3s; }

/* --- Photo frames ------------------------------------------------------- */

.frame { position: relative; display: block; overflow: hidden; background: var(--cream-alt); }
/* a framed slot holding a clip: the poster <picture> fills the frame exactly
   as the <img> would, and site.js lays the muted video over it */
.frame picture { display: block; width: 100%; height: 100%; }
/* A work tile with a row to itself keeps its media in a layer of its own, so
   that the same element is the slot whether the row planner has made the tile
   a band or left it a plain capped tile. Here it is the plain case and the
   layer simply fills the frame; .band > .band__media further down turns the
   very same element into the band's moving layer, and wins by coming last. */
.frame > .band__media { position: static; display: block; width: 100%; height: 100%; }
.frame img {
  width: 100%; height: 100%; object-fit: cover;
  /* --x and --y are baked onto the frames the owner can reframe (the two
     About portraits); everywhere else they are absent and the crop is
     centred, exactly as it always was. */
  object-position: var(--x, 50%) var(--y, 50%);
  transition: transform 1.2s var(--ease);
}
.frame:hover img, .frame:focus-visible img { transform: scale(1.045); }
/* a clip in a framed slot is cropped the same way the photograph would be,
   so a 16:9 clip in the 2:3 portrait frame is framed by --x and --y */
.frame__video { object-position: var(--x, 50%) var(--y, 50%); }

.frame--16x9 { aspect-ratio: 16 / 9; }
.frame--4x3  { aspect-ratio: 4 / 3; }
.frame--2x3  { aspect-ratio: 2 / 3; }

.section--dark .frame { background: #383330; }

.caption {
  margin-top: 10px;
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

/* --- Intro -------------------------------------------------------------- */

.intro { text-align: center; }
.intro h2 { margin-bottom: 30px; }
.intro p { max-width: 62ch; margin-inline: auto; }

/* --- Films -------------------------------------------------------------- */

.films { display: grid; gap: clamp(34px, 4vw, 52px); grid-template-columns: 1fr; }
@media (min-width: 760px) { .films { grid-template-columns: repeat(3, 1fr); } }
.film h3 { margin: 22px 0 8px; }
.film__meta {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold-dark);
  margin-bottom: 14px;
}
.film p { font-size: 15px; }

/* --- Editorial gallery grid (markup built by tools/build_site.py) --------
   Never more than two photographs across, however wide the screen, and one
   across below 1100px. The rows are spaced far further apart than they are
   internally, so each picture reads as its own moment.

   The pictures are no longer all 16:9, so the shape of a row is worked out by
   the bake and written onto it as --ar. Every frame in the row takes that
   shape, which is what gives two pictures side by side one height; the row is
   planned so that the two nearly agree, and only the taller of them loses a
   sliver to object-fit: cover. A row of one is its own shape and is not
   cropped at all - it is held instead to a height the screen can take, and a
   portrait alone is held to 60% of the column besides, because a portrait
   filling the width reads as a mistake.                                     */

.grid {
  --ar: 1.7778;
  display: grid;
  justify-items: center;
  gap: var(--grid-gap, 16px);
  margin-bottom: var(--row-gap, 32px);
}
.grid:last-child { margin-bottom: 0; }
.grid .frame { width: 100%; aspect-ratio: var(--ar); }
.grid--1 { grid-template-columns: 1fr; }
.grid--2 { grid-template-columns: repeat(2, 1fr); }

/* one picture on its own: never taller than most of the window */
.grid--1 .frame { max-width: calc(var(--ar) * var(--solo-h, 88vh)); }
.grid--1.grid--tall .frame { max-width: min(60%, calc(var(--ar) * var(--solo-h, 88vh))); }

/* a single photograph escaping the wrap, edge to edge. Only 16:9 or wider is
   ever put here: a portrait run to the full width of the window would be
   taller than any screen it landed on. */
.grid--bleed {
  grid-template-columns: 1fr;
  width: calc(100vw - var(--sbw));
  margin-left: calc(50% - (100vw - var(--sbw)) / 2);
  margin-block: var(--bleed-gap, 48px);
}
.grid--bleed:first-child { margin-top: 0; }
.grid--bleed:last-child { margin-bottom: 0; }

/* Phones and narrow tablets: one picture to a row, and a portrait no wider
   than about four fifths of it, so a standing photograph still sits inside
   the page rather than becoming the page. */
@media (max-width: 1099px) {
  .grid--2 { grid-template-columns: 1fr; }
  .grid.grid--tall .frame { max-width: 80%; }
  .grid.grid--1:not(.grid--tall) .frame { max-width: 100%; }
}

.gallery-stack {
  --grid-gap: clamp(14px, 1.6vw, 30px);
  --row-gap: clamp(34px, 4.2vw, 86px);
  --bleed-gap: clamp(44px, 5.4vw, 110px);
}

/* --- Full-bleed bands: motto stills and the moving video tiles ----------
   One structure for both. The picture (or the poster under a video) sits in
   .band__media and the words in .band__text, which travels the other way as
   the nearer layer.

   The media layer is the frame at its natural scale for the band's width -
   the width times --band-nat - and never shorter than the band itself. There
   is no overscan multiplier and therefore no zoom: opening the Height slider
   only uncovers more of the same frame, and at Height 1, where the band is
   the frame's own height, the whole frame is on screen uncropped.

   What is hidden is (layer - band), and that is all the parallax has to play
   with: site.js swings the layer by at most half of it, so an edge can never
   come into view. --yf is the owner's Position as a fraction of that slack -
   0 the top of the frame, 1 the bottom, 0.5 centred - and it is what places
   the layer before any transform, so a page with no script, a touch screen
   and reduced motion all frame it right.

   --band-h is the owner's Height slider and --band-nat the shape of the
   media in it, both baked onto the section. The band is drawn at
   --band-compact - the height it has always had, per kind - and opened from
   there towards the media's own shape as --band-h goes to 1. At 0 the
   interpolation vanishes and the compact rule is exactly what is drawn, so a
   band nobody has touched is untouched.                                    */

.band {
  --band-nat: 0.5625;                               /* media height / width */
  --band-h: 0;                                      /* 0 compact, 1 full shape */
  --band-w: calc(100vw - var(--sbw, 0px));
  --band-compact: clamp(400px, 68vh, 820px);
  /* the media's whole frame, and never shorter than the compact height.
     There is no ceiling: a 4:5 photograph at --band-h 1 is the window's
     width times 1.25, which is taller than the window, and that is the
     point - the slider reaches the picture's full native height. */
  --band-full: max(calc(var(--band-w) * var(--band-nat)), var(--band-compact));
  /* the frame at its natural scale, and never shorter than the band. The
     100% is the band's own used height, so a band site.js has shortened
     above parallax strength 1 is followed without being told. */
  --band-layer: max(calc(var(--band-w) * var(--band-nat)), 100%);
  position: relative;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  background: var(--dark);
  height: calc(var(--band-compact)
               + (var(--band-full) - var(--band-compact)) * var(--band-h));
}
/* The moving tiles run just short of 16:9 - 16:8.2, so about 51% of the
   width - which gives the video room to travel vertically on scroll. The
   ceiling is the window minus the nav bar, so a tile and the bar together
   always fit one screenful: on a 16:9 window the tile is shorter than the
   space under the bar, which is what lets the snap below land the whole
   tile on screen with nothing cut off at either edge. */
.band--video { --band-compact: min(calc(var(--band-w) * 0.5125), calc(100vh - var(--nav-h))); }

/* A phone held upright is far too narrow for 16:8.2, so the tile becomes a
   4:5 portrait of the viewport width and keeps its height. */
@media (max-width: 599px) and (orientation: portrait) {
  .band--video { --band-compact: min(calc(var(--band-w) * 1.25), calc(100vh - var(--nav-h))); }
}

/* svh is the window with the phone's toolbars showing, so the tile stays
   whole while they are on screen instead of hiding under them. */
@supports (height: 100svh) {
  .band--video { --band-compact: min(calc(var(--band-w) * 0.5125), calc(100svh - var(--nav-h))); }
  @media (max-width: 599px) and (orientation: portrait) {
    .band--video { --band-compact: min(calc(var(--band-w) * 1.25), calc(100svh - var(--nav-h))); }
  }
}

/* --- The phone: words under the picture, and the owner's phone crop ----- */
/* Everything here is inside the phone query (max-width 599px, portrait) and
   scoped to html:not(.llw-phone), so the desktop and the editor's phone view
   are exactly as they were.

   A band with words is two boxes on a phone: the picture on top, in a media
   box --m-h tall, and the words under it on the band's own dark background,
   in normal flow - over a picture that narrow they crowd it. The band is the
   two together, so the closing band with its buttons may be taller than a
   screen. --m-h is the height the band itself had before: the compact rule
   for its kind opened by its Height slider (a clip is a 4:5 window), or the
   owner's crop below. The picture never runs under the words: the words'
   box is dark and sits above the layer, and there is no scrim on a phone.
   A gallery tile (band--work) has no words and keeps being one box.

   The owner's phone crop - `has-phone` and the four fractions --px --py --pw
   --ph, baked onto the band, the gallery tile or the hero - shows that
   region of the frame instead of the automatic slice:

   - a band or an edge-to-edge gallery tile takes the crop's own shape at
     the window's width (--crop-h), held to the screen under the nav. The
     layer is the whole frame scaled so the crop is exactly the box's width -
     never more, so the crop the owner drew is never zoomed - and if the
     screen cap makes the box shorter than the crop, the crop is centred and
     loses the same amount top and bottom;
   - the hero keeps the whole screen and its dark background, and shows the
     crop WHOLE in it: the frame is scaled until the crop fits the screen
     (contain), centred on the screen. A crop wider than the screen has dark
     bands above and below it, a taller one at its sides. The crop's box on
     screen is --crop-w x --crop-h; the scrim lights only that box, and the
     logo and tagline are centred in it and sized to it (see below).

   The layer is placed with `translate`, never `transform`: transform is the
   parallax loop's, and site.js holds every band with words, and every
   cropped slot, still on this query. */
@media (max-width: 599px) and (orientation: portrait) {
  /* the media box: the band's own height as it was */
  html:not(.llw-phone) .band {
    --m-h: calc(var(--band-compact)
                + (var(--band-full) - var(--band-compact)) * var(--band-h));
  }
  html:not(.llw-phone) .band.has-phone {
    --crop-h: calc(var(--band-w) * var(--band-nat) * var(--ph) / var(--pw));
    --m-h: min(var(--crop-h), calc(100vh - var(--nav-h)));
  }

  /* a band with words: the media box, then the words in flow under it */
  html:not(.llw-phone) .band:not(.band--work) {
    display: block;
    height: auto;
    padding-top: var(--m-h);
    /* the automatic layer, measured off the media box rather than the band */
    --band-layer: max(calc(var(--band-w) * var(--band-nat)), var(--m-h));
  }
  html:not(.llw-phone) .band:not(.band--work):not(.has-phone) > .band__media {
    top: calc((var(--m-h) - var(--band-layer)) * var(--yf, 0.5));
  }
  html:not(.llw-phone) .band:not(.band--work) > .band__scrim { display: none; }
  html:not(.llw-phone) .band:not(.band--work) > .band__inner {
    z-index: 1;
    background: var(--dark);
    padding: 28px 16px 36px;
  }
  /* on the dark box the over-picture glow would read as grey halos */
  html:not(.llw-phone) .band:not(.band--work) > .band__inner .eyebrow,
  html:not(.llw-phone) .band:not(.band--work) .band__line,
  html:not(.llw-phone) .band:not(.band--work) .band__cta-line { text-shadow: none; }
  /* the closing band's first button, "See the gallery", steps back on a
     phone so the enquiry is the one that leads */
  html:not(.llw-phone) .band__cta-row .btn:first-child { opacity: 0.5; }

  /* an edge-to-edge gallery tile with a crop is the media box alone */
  html:not(.llw-phone) .grid--bleed .frame.band.has-phone { height: var(--m-h); }

  html:not(.llw-phone) .band.has-phone > .band__media,
  html:not(.llw-phone) .hero.has-phone > .hero__media {
    left: 50%;
    right: auto;
    width: auto;
    aspect-ratio: calc(1 / var(--band-nat));
  }
  html:not(.llw-phone) .band.has-phone > .band__media {
    height: calc(var(--band-w) * var(--band-nat) / var(--pw));
    top: min(0px, calc((var(--m-h) - var(--crop-h)) / 2));
    translate: calc((var(--px) + var(--pw) / 2) * -100%)
               calc(var(--py) * -100%);
  }

  /* The hero is exactly one screen here (--hero-h), so the crop's box can be
     worked out in lengths rather than percentages, which the words inside
     could not read: the crop at the screen's width, unless that is taller
     than the screen, when it is the screen's height. */
  html:not(.llw-phone) .hero.has-phone {
    --hero-h: calc(100vh);
    --crop-h: min(calc(var(--hero-w) * var(--band-nat) * var(--ph) / var(--pw)),
                  var(--hero-h));
    --crop-w: calc(var(--crop-h) * var(--pw) / (var(--ph) * var(--band-nat)));
    /* the logo's width: 62% of the crop's width at most, and never taller
       than 30% of its height (the logo is 1595 x 653, so that is 0.7328 x
       the height). The tagline is scaled by the same width - see below. */
    --hero-logo-w: min(560px, 76vw, calc(var(--crop-w) * 0.62),
                       calc(var(--crop-h) * 0.3 * 1595 / 653));
    height: var(--hero-h);
  }
  /* the layer is the whole frame, so it is clipped to the crop: what lies
     outside it is the dark screen, not more of the picture */
  html:not(.llw-phone) .hero.has-phone > .hero__media {
    height: calc(var(--crop-h) / var(--ph));
    clip-path: inset(calc(var(--py) * 100%) calc((1 - var(--px) - var(--pw)) * 100%)
                     calc((1 - var(--py) - var(--ph)) * 100%) calc(var(--px) * 100%));
    top: 50%;
    translate: calc((var(--px) + var(--pw) / 2) * -100%)
               calc((var(--py) + var(--ph) / 2) * -100%);
  }
  html:not(.llw-phone) .hero.has-phone > .hero__scrim {
    inset: auto;
    left: 50%;
    top: 50%;
    width: var(--crop-w);
    height: var(--crop-h);
    translate: -50% -50%;
  }
  /* the logo block centred in the crop - which is centred on the screen -
     rather than pushed down under the nav, and no wider than the crop */
  html:not(.llw-phone) .hero.has-phone > .hero__inner {
    width: var(--crop-w);
    padding: 0 12px;
  }
  html:not(.llw-phone) .hero.has-phone .hero__logo { width: var(--hero-logo-w); }
  /* The tagline keeps the proportion it has to the logo on a 390px phone,
     where the logo is 241.8px and the tagline 11px: a smaller logo takes a
     smaller tagline and the gap under the logo with it, so the words never
     outweigh the logo. Never above today's 11px, never below 7px; the
     letter-spacing is in em and follows. */
  html:not(.llw-phone) .hero.has-phone h1.hero__tagline {
    margin-top: min(28px, calc(28 * var(--hero-logo-w) / 241.8));
    font-size: clamp(7px, calc(11 * var(--hero-logo-w) / 241.8), 11px);
  }
}
/* the same cap with the phone's toolbars showing, as the video band's is */
@supports (height: 100svh) {
  @media (max-width: 599px) and (orientation: portrait) {
    html:not(.llw-phone) .band.has-phone {
      --m-h: min(var(--crop-h), calc(100svh - var(--nav-h)));
    }
    html:not(.llw-phone) .hero.has-phone { --hero-h: calc(100svh); }
  }
}

/* --- Scrolling settles on a band ---------------------------------------- */
/* Scrolling near a full-bleed band comes to rest with the band's top edge
   just under the nav bar, so a tile is never half cut. "proximity" means it
   only pulls when the scroll already ended close to a band: reading a text
   section in between is never hijacked. Gallery bleed rows (band--work) are
   left out, so paging through photographs stays free. */
html {
  scroll-padding-top: var(--nav-h);
  scroll-snap-type: y proximity;
}
.band:not(.band--work) { scroll-snap-align: start; }
/* Edit mode scrolls the same way the public page does, so the owner sees the
   real thing while editing. The editor lifts the snap only for the moment it
   scrolls to a slot of its own accord, and puts it back. */

.band > .band__media {
  position: absolute;
  left: 0; right: 0; width: auto;
  height: var(--band-layer);
  top: calc((100% - var(--band-layer)) * var(--yf, 0.5));
}
.band__media picture { display: block; width: 100%; height: 100%; }
.band__media img { width: 100%; height: 100%; object-fit: cover; }

/* Where the crop sits vertically. --y is baked onto .hero__media and
   .band__media by tools/build_site.py (50% unless the owner moved it) and it
   inherits down to the poster and to the video js lays over it. On a band and
   on the hero the layer is the frame's own shape, so the cover crop has
   nothing left to give and --yf above is what frames it; in a .frame slot -
   the two portraits, the film cards, the grid tiles - this is still the whole
   story. */
.hero__media img,
.band__media img,
.band__video,
.hero__video { object-position: 50% var(--y, 50%); }

/* Every clip site.js lays over a poster, wherever the slot is: a band, the
   hero, a film card, a grid tile or a portrait. One rule for all of them -
   only where the crop sits differs, which is the object-position above. */
.clip-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.85s var(--ease);
  pointer-events: none;
  user-select: none;
}
.clip-video.is-playing { opacity: 1; }

/* Two soft gradients rather than a box: one lifts the whole lower edge, the
   second pools a little more darkness in the corner the words sit in, so the
   line stays readable even over a sunlit photograph. */
/* The scrim is the last viewport-height of the band, not the whole of it.
   Below one viewport that is the whole band and nothing changes; on a band
   the owner has opened past the window it keeps the dark pool the same size
   as the words that sit in it, instead of stretching one gradient over
   1800px and leaving the line over whatever happens to be there. */
.band__scrim {
  position: absolute;
  inset: auto 0 0 0;
  height: min(100%, 100vh);
  background:
    radial-gradient(130% 95% at 6% 100%,
      rgba(14, 12, 11, 0.66) 0%,
      rgba(14, 12, 11, 0.34) 38%,
      rgba(14, 12, 11, 0) 70%),
    linear-gradient(to top,
      rgba(18, 16, 14, 0.62) 0%,
      rgba(18, 16, 14, 0.32) 24%,
      rgba(18, 16, 14, 0.06) 52%,
      rgba(18, 16, 14, 0.24) 100%);
}

.band__inner {
  position: relative;
  width: 100%;
  max-width: 1800px;
  margin-inline: auto;
  padding: 0 clamp(20px, 4.5vw, 80px) clamp(34px, 6vh, 78px);
  color: #fff;
}
.band__inner .eyebrow {
  color: rgba(255, 255, 255, 0.74);
  margin-bottom: 16px;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.55);
}
.band__line {
  margin: 0;
  max-width: 19ch;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(1.9rem, 4vw, 3.4rem);
  line-height: 1.16;
  letter-spacing: 0.005em;
  text-shadow: 0 2px 30px rgba(0, 0, 0, 0.62), 0 1px 5px rgba(0, 0, 0, 0.4);
}

/* --- The closing call to action -----------------------------------------
   The band whose slot carries "cta": true is the last thing on the home page
   before the footer, and it ends on two buttons. They sit inside .band__text
   with the eyebrow and the line, so they drift as the nearer layer with the
   words and read against the same scrim - nothing new has to be darkened.
   On a phone the two stack and fill the column.                           */

.band__cta { margin-top: clamp(24px, 3.4vh, 40px); }
.band__cta-line {
  margin: 0 0 clamp(18px, 2.4vh, 28px);
  max-width: 34ch;
  font-family: var(--sans);
  font-size: clamp(0.95rem, 1.2vw, 1.05rem);
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.86);
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.6);
}
.band__cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 18px;
}
.band__cta-row .btn { text-decoration: none; }

@media (max-width: 599px) {
  .band__cta-row { display: grid; gap: 12px; }
  .band__cta-row .btn { text-align: center; }
}

/* only promoted to its own layer while js is actually moving it - which is
   never, for the media of a slot whose parallax the owner switched off */
.js-parallax .hero:not(.hero--static) .hero__media,
.js-parallax .hero__inner,
.js-parallax .band:not(.band--static) .band__media,
.js-parallax .band__text { will-change: transform; }

/* --- A work tile that has its row to itself: a band without words --------
   The row planner gives a single wide picture or clip a row of its own, and
   such a tile reads as a band, so it is given the band's model: the layer is
   the frame at its natural scale, the tile shows a window on to it, --yf
   frames it and the parallax swings it over what is hidden, never over more.

   Edge to edge it is a band outright, and nothing has to be said here at
   all: it runs the window's width, which is what --band-w already is, so
   .band's own rules give it the compact height of its kind - 16:8.2 for a
   clip, clamp(400px, 68vh, 820px) for a still - and its Height slider opens
   it from there to the media's native height. That is a little shorter than
   the row's own shape would be, which is the price of having something for
   the parallax to move over: a 16:9 clip in a 1440-wide bleed row is drawn
   738px rather than 810px and travels over the 72px it is hiding.

   Inside the column it is not edge to edge, so it keeps the height its row
   was planned at, and its layer has to be measured off the tile rather than
   off the window, because a lone tile is held to a width the window can
   take. The tile makes itself a size container and 100cqw is then its own
   width.

   A lone portrait is never here. It is held to 60% of the column and is a
   plain tile, which is what the editor re-derives after a media swap.      */

.grid--1 .frame.band {
  container-type: size;
  --band-w: 100cqw;
  height: auto;
  aspect-ratio: var(--ar);
  /* Held to a height the window can take. The Height slider lifts that cap
     towards the whole column, where the picture stands at its own native
     height - which is what Height 1 means on a band. At 0 the cap is exactly
     the one the site has always drawn. */
  max-width: calc(var(--ar) * var(--solo-h, 88vh)
                  + (100% - var(--ar) * var(--solo-h, 88vh)) * var(--band-h, 0));
}
/* A browser with no container units cannot work out --band-layer for that
   tile and would leave the layer to shrink to nothing. This is the floor
   that stops it: the tile then simply shows the whole frame and never moves.
   Where the units do work the layer is already at least the tile, so this
   changes nothing. The edge-to-edge tile needs no such floor - its --band-w
   is a plain length. */
.grid--1 .frame.band > .band__media { min-height: 100%; }
/* The slow hover zoom and the parallax pull the same picture two ways, so a
   tile that is moving does not also scale. A tile whose parallax the owner
   took down to 0 keeps the zoom, as every plain tile does. */
.frame.band:not(.band--static):hover img,
.frame.band:not(.band--static):focus-visible img { transform: none; }

/* --- Split / story ------------------------------------------------------ */

.split { display: grid; gap: clamp(30px, 4vw, 64px); align-items: center; grid-template-columns: 1fr; }
@media (min-width: 860px) {
  .split { grid-template-columns: 1fr 1fr; }
  .split--wide-left { grid-template-columns: 1.1fr 1fr; }
}
.split__text h2 { margin-bottom: 26px; }

.duo { display: grid; gap: clamp(16px, 2.4vw, 28px); grid-template-columns: repeat(2, 1fr); }

/* --- Testimonials ------------------------------------------------------- */
/* Screenshots of couples' own messages, as they sent them: three to a row on
   a desktop, two around a tablet, one on a phone. Each keeps its own shape -
   nothing is cropped, the row lines up along the top - and no figure is ever
   wider than its screenshot (--tw, its natural width in px, baked onto it),
   so a 4K window never blows one up. Small corners and a hairline, no shadow. */

.testimonials {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(22px, 2.6vw, 36px);
  align-items: start;
}
@media (min-width: 700px) { .testimonials { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .testimonials { grid-template-columns: repeat(3, 1fr); } }
.testimonial {
  margin: 0;
  width: 100%;
  max-width: calc(var(--tw, 800) * 1px);
  justify-self: center;
}
.testimonial__frame {
  display: block;
  overflow: hidden;
  border-radius: 10px;
  border: 1px solid var(--hairline);
  background: var(--white);
  cursor: zoom-in;
}
.section--dark .testimonial__frame { border-color: var(--hairline-on-dark); }
.testimonial__frame img { display: block; width: 100%; height: auto; }

/* --- Investment --------------------------------------------------------- */

.tiers { display: grid; gap: clamp(24px, 3vw, 36px); grid-template-columns: 1fr; }
@media (min-width: 820px) { .tiers { grid-template-columns: repeat(3, 1fr); } }
.tier {
  border: 1px solid var(--hairline);
  background: var(--cream);
  padding: clamp(28px, 3.4vw, 44px) clamp(22px, 2.6vw, 34px);
  display: flex;
  flex-direction: column;
}
.tier h3 { margin-bottom: 12px; }
.tier__price { font-family: var(--serif); font-size: 1.55rem; color: var(--gold-dark); margin-bottom: 22px; }
.tier ul { list-style: none; margin: 0 0 26px; padding: 0; font-size: 15px; }
.tier li { padding-block: 10px; border-bottom: 1px solid var(--hairline); line-height: 1.6; }
.tier li:last-child { border-bottom: 0; }
.tier__group + .tier__group { margin-top: 22px; }
.tier__group h4 {
  font-family: var(--sans);
  font-weight: 400;
  font-size: 11px;
  line-height: 1.6;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold-dark);
  margin: 0 0 6px;
}
@media (min-width: 900px) { .tier__group h4 { font-size: 12px; } }
.tier .tier__group ul { margin-bottom: 0; }
.tier__group:last-child { margin-bottom: 26px; }
.invest-terms {
  max-width: 640px;
  margin: clamp(32px, 4vw, 48px) auto 0;
  text-align: center;
  font-size: 14px;
  line-height: 1.7;
  color: var(--body);
}

/* --- Form --------------------------------------------------------------- */

.form { display: grid; gap: 28px; grid-template-columns: 1fr; }
@media (min-width: 720px) {
  .form { grid-template-columns: 1fr 1fr; }
  .form .field--full, .form .form__submit { grid-column: 1 / -1; }
}
.field label {
  display: block;
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}
.field input, .field textarea {
  width: 100%;
  font-family: var(--sans);
  font-weight: 300;
  font-size: 15px;
  color: var(--dark);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--hairline);
  padding: 8px 0 12px;
  border-radius: 0;
  transition: border-color 0.3s var(--ease);
}
.field textarea { resize: vertical; min-height: 116px; }
.field input:focus, .field textarea:focus { outline: 0; border-bottom-color: var(--gold-dark); }

/* The honeypot: off-screen rather than display:none, which some spam bots
   skip filling precisely because it is easy to detect. A person tabbing
   through the form never lands on it (tabindex="-1") and a screen reader
   never announces it (aria-hidden on the wrapping .field, up in the markup).
   overflow:hidden on the wrapper only clips what is painted, not the input's
   own box, so the 1x1 sizing is put on the input (and its label) directly too. */
.field--trap,
.field--trap label,
.field--trap input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.form__note {
  min-height: 20px;
  margin-top: 14px;
  font-size: 13px;
  letter-spacing: 0.02em;
  color: var(--muted);
}
.form__note.is-error { color: #a8412f; }

.form__done p {
  font-family: var(--serif);
  font-weight: 300;
  font-size: 1.4rem;
  color: var(--dark);
  margin: 0;
}

.contact-lines { display: grid; gap: 20px; }
.contact-lines > div { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; }
.contact-lines .k {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  min-width: 92px;
}
.contact-lines .v, .contact-lines .v-link { font-size: 15px; color: var(--dark); }
/* The Instagram line is a link. It reads as words first: the size and the
   colour are the line's own, and a 1px hairline in that same colour appears
   under it on hover, like the nav's and the footer's. It is an underline
   rather than the border those two use, because a border on this anchor would
   make its own row of the contact grid a pixel taller than the other three -
   the line is inside a layout, not on its own. An underline costs no layout at
   all, so the four rows stay identical. */
.contact-lines .v-link {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  text-decoration-color: transparent;
  transition: text-decoration-color 0.3s var(--ease);
}
.contact-lines .v-link:hover { text-decoration-color: currentColor; }

/* --- Footer ------------------------------------------------------------- */

.footer { background: var(--cream-alt); padding-block: clamp(52px, 6vw, 84px); }
.footer__inner { display: grid; gap: 32px; justify-items: center; text-align: center; }
.footer__logo img { width: 190px; }
.footer__nav { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px 26px; }
.footer__nav a {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--body);
  transition: color 0.3s var(--ease);
}
.footer__nav a:hover { color: var(--gold-dark); }

/* --- Gallery page ------------------------------------------------------- */

.tier-label {
  display: flex;
  align-items: center;
  gap: 22px;
  margin: clamp(44px, 5vw, 76px) 0 clamp(22px, 2.6vw, 34px);
}
.tier-label::before, .tier-label::after { content: ''; flex: 1; height: 1px; background: var(--hairline); }
.tier-label span {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

/* --- The film -----------------------------------------------------------
   The gallery opens on the whole wedding film, with its sound, in a player
   of its own. It is not a band: nothing about it drifts, nothing is cropped
   and there are no words over it. The stage runs the width of the page and
   is as tall as the window under the nav bar will take; the 16:9 frame is
   centred in it and letterboxed on the stage colour, so on a window wider
   than 16:9 the picture is a little narrower than the page and the whole of
   it is always on screen.

   Everything on it is grey. Pure white and pure black both shout next to a
   film; these are the greys the rest of the site's dark surfaces use.      */

.player {
  --player-bg:     #151412;                    /* the stage, and the letterbox */
  --player-ink:    #cfc9c2;                    /* icons and labels at rest */
  --player-lit:    #ece7e0;                    /* the same, under the pointer */
  --player-track:  rgba(255, 255, 255, 0.18);
  --player-played: #bfb7ad;
  --player-stage-h: min(calc(100vh - var(--nav-h)),
                      calc((100vw - var(--sbw, 0px)) * 9 / 16));
  position: relative;
  background: var(--player-bg);
  /* It is the first thing on the gallery page and the nav is fixed over the
     top of it, so it starts exactly where the nav ends - which is also what
     makes the stage and the bar together one screenful. */
  margin-top: var(--nav-h);
  /* and it settles under the nav exactly as a band does */
  scroll-snap-align: start;
}
@supports (height: 100svh) {
  /* on a phone the toolbars are on screen, so svh is the honest window */
  .player { --player-stage-h: min(calc(100svh - var(--nav-h)),
                              calc((100vw - var(--sbw, 0px)) * 9 / 16)); }
}

.player__stage {
  position: relative;
  height: var(--player-stage-h);
  display: flex;
  align-items: center;
  justify-content: center;
  outline: none;
}
/* the controls and the pointer go together: while the film is playing and
   nothing has moved, there is only the picture */
.player__stage.is-quiet { cursor: none; }

.player__frame {
  position: relative;
  height: 100%;
  aspect-ratio: 16 / 9;
  max-width: 100%;
  background: #000;
  overflow: hidden;
}
.player__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000;
}

/* --- the intro screen --------------------------------------------------- */
/* A plain white card over the frame: one delicate line, one toggle that asks
   for sound, and a countdown. It dissolves when the count reaches nothing. */

.player__intro {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(18px, 2.4vw, 34px);
  padding: var(--gutter);
  background: #fbf9f7;
  color: #3a3531;
  text-align: center;
  transition: opacity 0.9s var(--ease);
}
.player__intro.is-gone { opacity: 0; pointer-events: none; }
.player__intro[hidden] { display: none; }

/* A third again the size it was, at every width - the line is the only thing
   the card really has to say, and at the old size it sat too quietly in all
   that white. Still the light weight, still the wide measure: bigger, not
   louder. */
.player__ask {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(1.5rem, 3.12vw, 2.47rem);
  line-height: 1.35;
  letter-spacing: 0.01em;
  margin: 0;
  max-width: 22ch;
}

/* --- the intro's sound toggle ------------------------------------------- */
/* One press, not a slider: a speaker with a word under it. It is the size of
   a fingertip, because on a phone it is the only thing on the card anybody
   has to hit. */

.player__sound {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  min-width: 64px;
  padding: 10px 14px 8px;
  border: 0;
  border-radius: 10px;
  background: none;
  color: #6c635d;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.25s var(--ease), background-color 0.25s var(--ease);
}
/* the extra .player carries this past the bar's own `.player .i` sizing,
   which is written further down the file */
.player .player__sound .i {
  width: 30px; height: 30px;
  stroke-width: 1.2;
}
.player__soundlabel {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  white-space: nowrap;
}
.player__sound:hover,
.player__sound:focus-visible { color: #3a3531; background: rgba(58, 53, 49, 0.06); }
.player__sound:focus-visible { outline: 1px solid #6c635d; outline-offset: 2px; }
/* the same rule the bar's button follows: each holds both icons and shows
   whichever one is true now */
.player__sound .i--off,
.player.is-muted .player__sound .i--on { display: none; }
.player.is-muted .player__sound .i--off { display: block; }
.player__count {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(2.4rem, 6vw, 4.4rem);
  line-height: 1;
  margin: 0;
  color: #6c635d;
  font-variant-numeric: tabular-nums;
}

/* --- the volume slider -------------------------------------------------- */
/* The bar's own, and the only one there is: a hairline track with a small
   round head, from nothing to everything. The intro asks with a toggle
   instead, so this is where the loudness is actually chosen - which is why
   the speaker walks here and stops on it. */

.player__volume {
  -webkit-appearance: none;
  appearance: none;
  width: 78px;
  height: 18px;
  background: transparent;
  cursor: pointer;
}
.player__volume::-webkit-slider-runnable-track {
  height: 2px;
  background: var(--player-track);
}
.player__volume::-moz-range-track { height: 2px; background: var(--player-track); }
.player__volume::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 11px; height: 11px;
  margin-top: -4.5px;
  border-radius: 50%;
  border: 0;
  background: var(--player-played);
}
.player__volume::-moz-range-thumb {
  width: 11px; height: 11px;
  border-radius: 50%;
  border: 0;
  background: var(--player-played);
}
.player__volume:focus-visible { outline: 1px solid var(--player-lit); outline-offset: 4px; }

/* --- the travelling speaker --------------------------------------------- */
/* However the countdown ends, this crosses the picture from the intro's
   toggle to the bar's own volume control, so the viewer is shown where the
   sound is set from. site.js measures both ends and writes them onto the
   element, because only the browser knows where the bar laid its buttons out.

   It is a gesture and not a control: it takes no pointer, and it fades out
   over the last part of the path so that it is already gone by the time it
   reaches the corner. Nothing is ever left sitting over the bar, so it can
   neither overlap the full-screen button nor swallow a press meant for it -
   from then on the bar is the control, which is what the walk pointed at.

   Without .is-walking there is no transition at all, which is what lets
   site.js paint the start before it applies the destination. */

.player__speaker {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 44px; height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 50%;
  background: rgba(21, 20, 18, 0.45);
  color: var(--player-ink);
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, -50%);
  transition: none;
}
.player__speaker[hidden] { display: none; }
.player__speaker.is-shown { opacity: 1; }
/* 1.6s of walking; the fade starts at 55% of it and is over on arrival */
.player__speaker.is-walking {
  opacity: 0;
  transition: left 1.6s var(--ease), top 1.6s var(--ease),
              opacity 0.72s linear 0.88s;
}
/* it holds both icons and shows the true one, exactly as the bar's does */
.player__speaker .i--off,
.player.is-muted .player__speaker .i--on { display: none; }
.player.is-muted .player__speaker .i--off { display: block; }

/* --- the control bar ---------------------------------------------------- */

.player__bar {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.4vw, 18px);
  padding: 40px clamp(14px, 2vw, 26px) clamp(12px, 1.6vw, 20px);
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.45));
  color: var(--player-ink);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s var(--ease);
}
.player__bar.is-shown { opacity: 1; pointer-events: auto; }

.player__bar button {
  display: grid;
  place-items: center;
  width: 32px; height: 32px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  flex: none;
}
.player__bar button:hover, .player__bar button:focus-visible { color: var(--player-lit); }

.player .i {
  width: 22px; height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.player .i--play { fill: currentColor; stroke: none; }
/* each button holds both of its icons and shows the one that is true now */
.player__play .i--pause,
.player.is-playing .player__play .i--play { display: none; }
.player.is-playing .player__play .i--pause { display: block; }
.player__mute .i--off,
.player.is-muted .player__mute .i--on { display: none; }
.player.is-muted .player__mute .i--off { display: block; }
.player__full .i--shrink,
.player.is-full .player__full .i--expand { display: none; }
.player.is-full .player__full .i--shrink { display: block; }

.player__seek {
  position: relative;
  flex: 1 1 auto;
  height: 22px;
  display: flex;
  align-items: center;
  cursor: pointer;
  touch-action: none;
}
.player__track {
  width: 100%;
  height: 2px;
  background: var(--player-track);
}
.player__played {
  height: 100%;
  width: 0;
  background: var(--player-played);
}
.player__head {
  position: absolute;
  left: 0;
  width: 10px; height: 10px;
  margin-left: -5px;
  border-radius: 50%;
  background: var(--player-played);
}
.player__seek:focus-visible { outline: 1px solid var(--player-lit); outline-offset: 3px; }

.player__time {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.08em;
  font-variant-numeric: tabular-nums;
  margin: 0;
  flex: none;
  white-space: nowrap;
}
.player__vol { display: flex; align-items: center; gap: 6px; flex: none; }

/* A narrow window has no room for the volume slider beside everything else;
   the mute button alone still turns the sound on and off. */
@media (max-width: 599px) {
  .player__volume--bar { display: none; }
  .player__bar { gap: 8px; padding-inline: 10px; }
}

/* In full screen the stage is the whole screen, so the frame takes whatever
   of it 16:9 fits into. Asking for full screen on the stage rather than on
   the video is what keeps the bar and the intro over the picture. */
.player__stage:fullscreen { height: 100vh; width: 100vw; background: var(--player-bg); }
.player__stage:fullscreen .player__frame { height: auto; width: 100%; max-height: 100%; }
.player__stage:-webkit-full-screen { height: 100vh; width: 100vw; background: var(--player-bg); }

/* --- A section that is not ready yet ------------------------------------ */
/* Copy the owner has still to write is not shown to a visitor. The section
   stays in the page so the editor can put the real words in it and publish;
   editor.css brings it back under html.llw-on, and the next bake sees real
   text and takes the class off again. */
.is-draft { display: none; }

/* --- Lightbox ----------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: none;
  background: rgba(20, 18, 16, 0.97);
  opacity: 0;
  transition: opacity 0.3s var(--ease);
}
.lightbox.is-open { display: block; }
.lightbox.is-visible { opacity: 1; }

.lightbox__stage {
  position: absolute;
  inset: 62px 12px 58px;
  display: flex;
  align-items: center;
  justify-content: center;
}
@media (min-width: 900px) { .lightbox__stage { inset: 74px 88px 66px; } }

.lightbox__img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.6s var(--ease);
  user-select: none;
  -webkit-user-drag: none;
}
.lightbox__img.is-shown { opacity: 1; }
/* the outgoing photograph during a cross-fade: same box, laid over the stage */
.lightbox__ghost {
  position: absolute;
  inset: 0;
  margin: auto;
  pointer-events: none;
  z-index: 1;
}

.lightbox__btn {
  position: absolute;
  z-index: 2;
  color: #fff;
  width: 54px;
  height: 54px;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.7;
  transition: opacity 0.25s var(--ease);
}
.lightbox__btn:hover { opacity: 1; }
.lightbox__btn svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.1; }
.lightbox__close { top: 6px; right: 4px; }
.lightbox__prev { left: 0; top: 50%; transform: translateY(-50%); }
.lightbox__next { right: 0; top: 50%; transform: translateY(-50%); }
@media (min-width: 900px) {
  .lightbox__close { top: 16px; right: 20px; }
  .lightbox__prev { left: 18px; }
  .lightbox__next { right: 18px; }
}

.lightbox__meta {
  position: absolute;
  left: 0; right: 0; bottom: 18px;
  text-align: center;
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}
.lightbox__spinner {
  position: absolute;
  left: 50%; top: 50%;
  width: 26px; height: 26px;
  margin: -13px 0 0 -13px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-top-color: rgba(255, 255, 255, 0.8);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
  opacity: 0;
  transition: opacity 0.2s var(--ease);
  pointer-events: none;
}
.lightbox.is-loading .lightbox__spinner { opacity: 1; }
@keyframes spin { to { transform: rotate(360deg); } }

/* --- Tap targets on a phone -------------------------------------------- */
/* A fingertip needs about 40px. The few small controls a phone shows - the
   menu button, the menu and footer links, the quiet text links, the contact
   line's link and the film's bar buttons - get an invisible ::after that
   widens what can be tapped to at least that, centred on what is drawn, so
   nothing else on the page moves or changes size. */
@media (max-width: 599px) {
  .footer__nav a, .link-quiet, .contact-lines .v-link,
  .nav__links a, .player__bar button { position: relative; }
  .nav__toggle::after, .footer__nav a::after, .link-quiet::after,
  .contact-lines .v-link::after, .nav__links a::after,
  .player__bar button::after { content: ''; position: absolute; }
  .nav__toggle::after { inset: -11px -5px; }               /* 34x22 -> 44x44 */
  .footer__nav a::after { inset: -10px -6px; }             /* 20 tall -> 40 */
  /* the footer's links wrap to two rows on a phone; 20px between the rows
     instead of 12 is what lets each row have its 40px without the one below
     covering it */
  .footer__nav { row-gap: 20px; }
  .link-quiet::after { inset: -8px -4px; }                 /* 24 tall -> 40 */
  .contact-lines .v-link::after { inset: -6px -4px; }      /* 28 tall -> 40 */
  .nav__links a::after { inset: -2px 0; }                  /* 37 tall -> 41 */
  .player__bar button::after { inset: -4px; }              /* 32x32 -> 40x40 */
}

/* --- Motion preferences ------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  .frame:hover img, .frame:focus-visible img { transform: none; }
  /* no parallax, and no video: every slot stays on its poster */
  .hero__media, .hero__inner, .band__media, .band__text { transform: none !important; }
  .clip-video { display: none; }
  .hero__cue::after { animation: none; opacity: 0.6; }
}
