/* ============================================================
   Fonts, self-hosted.
   These were an @import of fonts.googleapis.com on line 1, which made the WHOLE
   stylesheet wait on a third-party round trip: the browser could not apply a single
   rule — not even the hero sky — until Google answered. Measured locally on
   2026-09-03: style.css arrived at 41ms, the Google CSS did not finish until
   14,329ms, and sun.webp (referenced from this file) not until 14,331ms.
   Serving the faces from this origin removes the DNS lookup, the TLS handshake and
   two serialized round trips, and keeps the site's typography working if Google
   Fonts is ever blocked, changed or gone. Regenerate with ../_build/fetch-fonts.mjs.
   ============================================================ */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 300;
  font-display: swap;
  src: url('../fonts/CormorantGaramond-300i-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, 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;
  font-display: swap;
  src: url('../fonts/CormorantGaramond-300i-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: 400;
  font-display: swap;
  src: url('../fonts/CormorantGaramond-400i-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, 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: 400;
  font-display: swap;
  src: url('../fonts/CormorantGaramond-400i-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;
  font-display: swap;
  src: url('../fonts/CormorantGaramond-300-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, 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: normal;
  font-weight: 300;
  font-display: swap;
  src: url('../fonts/CormorantGaramond-300-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: 400;
  font-display: swap;
  src: url('../fonts/CormorantGaramond-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, 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: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/CormorantGaramond-400-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: 500;
  font-display: swap;
  src: url('../fonts/CormorantGaramond-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, 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: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/CormorantGaramond-500-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: 'Karla';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('../fonts/Karla-300-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, 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: 'Karla';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('../fonts/Karla-300-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: 'Karla';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/Karla-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, 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: 'Karla';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/Karla-400-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: 'Karla';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/Karla-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, 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: 'Karla';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/Karla-500-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;
}


/* ============================================================
   Tori & Alec — Memorial Site
   Palette: dusk. Deep warm night, amber horizon, cream light.
   Chosen so 1,158 photographs glow instead of glare, and so a
   sunset photograph can sit behind the hero without fighting it.
   ============================================================ */

:root {
  /* ---- ink & ground ---- */
  --ink-900: #0a0908;
  --ink-800: #100e0d;
  --ink-700: #16130f;
  --ink-600: #1e1a16;
  --ink-500: #2a251f;
  --line:    #2e2823;
  --line-soft: rgba(232, 213, 183, 0.10);

  /* ---- light ---- */
  --cream:   #f7f1e6;
  --cream-dim: #cabfae;
  --muted:   #9b9083;  /* 6.36:1 on --ink-900 */
  --muted-2: #8a8074;  /* 5.14:1 — was #7b7166 at 4.16:1, under the 4.5 floor */

  /* ---- amber, from the original logo script ---- */
  --amber:      #e9a05c;
  --amber-soft: #f3c48d;
  --amber-deep: #c2703a;
  --rose:       #d98a76;
  --violet:     #4a3a5c;

  /* ---- type ---- */
  /* Karla, not Inter: humanist and slightly literary, so it sits with Cormorant
     instead of flattening it into the default AI-interface look */
  --serif: 'Cormorant Garamond', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --sans:  'Karla', 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;

  /* ---- rhythm ---- */
  --s1: 0.5rem;  --s2: 0.75rem; --s3: 1rem;   --s4: 1.5rem;
  --s5: 2rem;    --s6: 3rem;    --s7: 4.5rem; --s8: 7rem;   --s9: 10rem;

  --measure: 34rem;
  --page: 76rem;
  --radius: 2px;

  /* ---- motion: restrained. Nothing here should feel eager. ---- */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
  --t-fast: 180ms;
  --t-mid: 320ms;
  --t-slow: 700ms;
}

/* ============================================================
   reset
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* the header is fixed and 76px tall, so any anchor jump or focus-driven scroll would
     otherwise land its target underneath it (WCAG 2.4.11 Focus Not Obscured). Today the
     generous section padding happens to cushion every existing anchor; this makes that
     true by construction rather than by luck. */
  scroll-padding-top: 6rem;
  /* browser surfaces carry the palette too */
  scrollbar-color: var(--ink-500) var(--ink-900);
  accent-color: var(--amber);
}
input, textarea { caret-color: var(--amber); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  background: var(--ink-900);
  color: var(--cream);
  font-family: var(--sans);
  font-size: clamp(1rem, 0.97rem + 0.15vw, 1.075rem);
  font-weight: 300;
  line-height: 1.75;
  letter-spacing: 0.005em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img, picture, svg, video { display: block; max-width: 100%; }
button, input, textarea { font: inherit; color: inherit; }
a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 3px;
  border-radius: var(--radius);
}

::selection { background: var(--amber-deep); color: var(--cream); }

.wrap { width: min(100% - 2.5rem, var(--page)); margin-inline: auto; }
.narrow { width: min(100% - 2.5rem, 46rem); margin-inline: auto; }
/* galleries are the artifact — give them the screen */
.wrap-wide { width: min(100% - 3rem, 110rem); margin-inline: auto; }

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

.skip-link {
  position: absolute; left: 50%; top: 0;
  transform: translate(-50%, -120%);
  background: var(--amber); color: var(--ink-900);
  padding: var(--s2) var(--s4); z-index: 200;
  font-size: 0.9rem; font-weight: 500; text-decoration: none;
  transition: transform var(--t-fast) var(--ease-out);
}
.skip-link:focus { transform: translate(-50%, 0); }

/* ============================================================
   type
   ============================================================ */
h1, h2, h3, .display {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: -0.012em;
  text-wrap: balance;
}

.display {
  font-size: clamp(3rem, 1.5rem + 6.6vw, 7.5rem);
  font-weight: 300;
}

h2 { font-size: clamp(2.1rem, 1.4rem + 2.8vw, 3.9rem); font-weight: 300; }
h3 { font-size: clamp(1.25rem, 1.1rem + 0.6vw, 1.5rem); font-weight: 500; }

p { text-wrap: pretty; }
.lede {
  font-family: var(--serif);
  font-size: clamp(1.3rem, 1.1rem + 0.85vw, 1.7rem);
  line-height: 1.55;
  font-weight: 300;
  color: var(--cream-dim);
  font-style: italic;
}

.eyebrow {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.1rem, 1.02rem + 0.3vw, 1.3rem);
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--amber-soft);
}

.dates {
  font-family: var(--serif);
  font-size: clamp(1rem, 0.94rem + 0.3vw, 1.15rem);
  letter-spacing: 0.06em;
  color: var(--muted);
  font-variant-numeric: lining-nums;
}

/* ============================================================
   header / nav
   ============================================================ */
.site-head {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4);
  padding: var(--s3) clamp(1.25rem, 4vw, 3rem);
  background: linear-gradient(to bottom, rgba(10, 9, 8, 0.82), rgba(10, 9, 8, 0));
  transition: background var(--t-mid) var(--ease-soft),
              backdrop-filter var(--t-mid) var(--ease-soft);
}
.site-head[data-stuck='true'] {
  background: rgba(10, 9, 8, 0.9);
  backdrop-filter: blur(14px) saturate(1.2);
  border-bottom: 1px solid var(--line-soft);
}

.brand {
  display: inline-flex; align-items: center;
  min-height: 44px;              /* tap target, not just text height */
  font-family: var(--serif);
  font-size: 1.2rem;
  letter-spacing: 0.02em;
  text-decoration: none;
  color: var(--cream);
  white-space: nowrap;
  transition: color var(--t-fast) var(--ease-out);
}
.brand em { font-style: italic; color: var(--amber); }
.brand:hover { color: var(--amber-soft); }

.nav { display: flex; align-items: center; gap: clamp(1rem, 2vw, 2rem); }
.nav a {
  display: inline-flex; align-items: center;
  min-height: 44px;              /* tap target */
  font-size: 0.95rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  text-decoration: none;
  color: var(--cream-dim);
  position: relative;
  transition: color var(--t-fast) var(--ease-out);
}
.nav a::after {
  content: '';
  position: absolute; left: 0; bottom: 0.6rem;
  width: 100%; height: 1px;
  background: var(--amber);
  transform: scaleX(0); transform-origin: right;
  transition: transform var(--t-mid) var(--ease-out);
}
.nav a:hover, .nav a[aria-current='page'] { color: var(--cream); }
.nav a:hover::after, .nav a[aria-current='page']::after {
  transform: scaleX(1); transform-origin: left;
}

.nav-toggle { display: none; }

@media (max-width: 800px) {
  .nav-toggle {
    display: grid; place-items: center;
    width: 44px; height: 44px;
    background: none; border: 1px solid var(--line);
    border-radius: var(--radius);
    cursor: pointer;
    /* Positioned, so it paints in step 9 of the painting order — ABOVE .nav, which is
       position:fixed with z-index auto and so paints in step 8. Without this the open
       overlay covered the only control that could close it: the second tap landed on
       the overlay, and with no Escape handler the menu was a trap you left by
       navigating away. The button now doubles as the close control. */
    position: relative; z-index: 1;
    transition: transform var(--t-fast) var(--ease-out),
                border-color var(--t-fast) var(--ease-out);
  }
  .nav-toggle:active { transform: scale(0.94); }
  .nav-toggle:hover { border-color: var(--amber); }
  .nav-toggle svg { width: 20px; height: 20px; stroke: var(--cream); overflow: visible; }

  /* three separate bars so they can fold into a close mark: the state of the menu is
     legible from the button itself, not only from what is behind it */
  .nav-toggle svg path {
    transform-box: fill-box;
    transform-origin: center;
    transition: transform var(--t-mid) var(--ease-out),
                opacity var(--t-fast) var(--ease-out);
  }
  .nav-toggle[aria-expanded='true'] { border-color: var(--line-soft); }
  .nav-toggle[aria-expanded='true'] svg path:nth-child(1) { transform: translateY(5px) rotate(45deg); }
  .nav-toggle[aria-expanded='true'] svg path:nth-child(2) { opacity: 0; }
  .nav-toggle[aria-expanded='true'] svg path:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

  .nav {
    position: fixed; inset: 0;
    flex-direction: column; justify-content: center;
    gap: var(--s5);
    background: rgba(10, 9, 8, 0.97);
    backdrop-filter: blur(20px);
    opacity: 0; visibility: hidden;
    transition: opacity var(--t-mid) var(--ease-out),
                visibility var(--t-mid);
  }
  .nav[data-open='true'] { opacity: 1; visibility: visible; }
  .nav a { font-family: var(--serif); font-size: 2rem; font-weight: 300; letter-spacing: 0; }
  .nav a::after { display: none; }

  /* menu links surface one after another, not as one block */
  @media (prefers-reduced-motion: no-preference) {
    .nav a {
      opacity: 0; transform: translateY(14px);
      transition: opacity var(--t-mid) var(--ease-out),
                  transform var(--t-mid) var(--ease-out);
    }
    .nav[data-open='true'] a { opacity: 1; transform: none; }
    .nav[data-open='true'] a:nth-child(2) { transition-delay: 60ms; }
    .nav[data-open='true'] a:nth-child(3) { transition-delay: 120ms; }
  }
}

/* ============================================================
   hero: dusk over the prairie
   A seamless loop of a generated, faceless prairie sky (Video Express, the
   same still as first and last frame, so the restart never shows). The
   poster IS that still, so the first paint, reduced motion, Save-Data and a
   failed video all show the same scene at rest.
   Scrolling away lets night fall over it: the scene sinks and darkens, the
   stars come out, the names drift and fade. Scrolling back brings the dusk
   back, so the effect is the same on every pass.
   ============================================================ */
.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto;
  isolation: isolate;
  overflow: hidden;
  background: var(--ink-900);
}

/* what paints before the poster has decoded: the same dusk, in gradients */
.sky {
  position: absolute; inset: 0; z-index: -5;
  background: linear-gradient(to bottom,
    #0c0b16 0%, #1e1828 38%, #4a3446 62%, #a4623e 79%, #2a1c12 82%, var(--ink-900) 100%);
}

.hero-media {
  position: absolute; inset: 0; z-index: -4;
  transform-origin: 50% 80%;
}
.hero-video {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 47% 50%;
  scale: 1.03;
}
/* the grade: deepen the sky behind the names, and let the grass settle into
   the page instead of ending on a hard edge */
.hero-media::after {
  content: '';
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 46% 26% at 50% 48%, rgba(10, 9, 8, 0.6), rgba(10, 9, 8, 0) 100%),
    linear-gradient(to bottom,
      rgba(10, 9, 8, 0.5) 0%,
      rgba(10, 9, 8, 0.1) 30%,
      rgba(10, 9, 8, 0) 58%,
      rgba(10, 9, 8, 0.3) 86%,
      var(--ink-900) 100%);
}

@media (max-width: 640px) {
  .hero-media::after {
    background:
      radial-gradient(ellipse 85% 22% at 50% 47%, rgba(10, 9, 8, 0.62), rgba(10, 9, 8, 0) 100%),
      linear-gradient(to bottom,
        rgba(10, 9, 8, 0.5) 0%,
        rgba(10, 9, 8, 0.1) 30%,
        rgba(10, 9, 8, 0) 58%,
        rgba(10, 9, 8, 0.3) 86%,
        var(--ink-900) 100%);
  }
}

.night {
  position: absolute; inset: 0; z-index: -3;
  background: linear-gradient(to bottom, #05050b 0%, #08070f 55%, var(--ink-900) 100%);
  opacity: 0;
  pointer-events: none;
}

/* first stars. Two sets, so they can twinkle out of step */
.stars {
  position: absolute; inset: 0; z-index: -3;
  opacity: 0.45;
  pointer-events: none;
  mask-image: linear-gradient(to bottom, #000 0%, #000 28%, transparent 56%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 28%, transparent 56%);
}
.stars::before, .stars::after {
  content: '';
  position: absolute; inset: 0;
  background-repeat: no-repeat;
}
.stars::before {
  background-image:
    radial-gradient(1px 1px at 11% 14%, rgba(247,241,230,0.9), transparent),
    radial-gradient(1.5px 1.5px at 23% 27%, rgba(247,241,230,0.6), transparent),
    radial-gradient(1px 1px at 34% 9%,  rgba(247,241,230,0.8), transparent),
    radial-gradient(1px 1px at 46% 21%, rgba(247,241,230,0.5), transparent),
    radial-gradient(1.5px 1.5px at 57% 12%, rgba(247,241,230,0.7), transparent),
    radial-gradient(1px 1px at 66% 25%, rgba(247,241,230,0.5), transparent),
    radial-gradient(1px 1px at 75% 8%,  rgba(247,241,230,0.85), transparent),
    radial-gradient(1.5px 1.5px at 84% 18%, rgba(247,241,230,0.55), transparent),
    radial-gradient(1px 1px at 92% 29%, rgba(247,241,230,0.6), transparent),
    radial-gradient(1px 1px at 5% 31%,  rgba(247,241,230,0.45), transparent);
}
.stars::after {
  background-image:
    radial-gradient(1px 1px at 17% 6%,  rgba(247,241,230,0.7), transparent),
    radial-gradient(1px 1px at 29% 18%, rgba(247,241,230,0.55), transparent),
    radial-gradient(1.5px 1.5px at 40% 30%, rgba(247,241,230,0.5), transparent),
    radial-gradient(1px 1px at 52% 5%,  rgba(247,241,230,0.8), transparent),
    radial-gradient(1px 1px at 61% 33%, rgba(247,241,230,0.45), transparent),
    radial-gradient(1.5px 1.5px at 70% 15%, rgba(247,241,230,0.65), transparent),
    radial-gradient(1px 1px at 81% 31%, rgba(247,241,230,0.5), transparent),
    radial-gradient(1px 1px at 88% 7%,  rgba(247,241,230,0.75), transparent),
    radial-gradient(1px 1px at 97% 22%, rgba(247,241,230,0.5), transparent),
    radial-gradient(1.5px 1.5px at 3% 11%,  rgba(247,241,230,0.6), transparent);
}

/* fireflies over the grass: drawn by site.js, nothing without it */
.fireflies {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%;
  pointer-events: none;
}

/* grain: the film already carries grain, this only keeps the dark from banding */
.grain {
  position: absolute; inset: 0; z-index: -1;
  opacity: 0.08; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
}

.hero-inner {
  grid-row: 1;
  align-self: center;
  text-align: center;
  padding: var(--s9) var(--s4) var(--s8);
}

.hero .display {
  color: #fffaf1;
  text-shadow: 0 2px 40px rgba(0, 0, 0, 0.55), 0 1px 4px rgba(0, 0, 0, 0.4);
}
.hero .display .w { display: inline-block; }
.hero .display em {
  font-style: italic;
  color: var(--amber-soft);
}

.hero-sub {
  margin-top: var(--s4);
  font-family: var(--serif);
  font-size: clamp(1.15rem, 1rem + 0.6vw, 1.5rem);
  font-style: italic;
  color: rgba(247, 241, 230, 0.9);
  letter-spacing: 0.02em;
  text-shadow: 0 1px 20px rgba(0, 0, 0, 0.6);
}

.hero-dates {
  margin-top: var(--s3);
  font-family: var(--sans);
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  color: var(--cream);
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.85), 0 0 3px rgba(0, 0, 0, 0.6);
}

.scroll-cue {
  grid-row: 2;
  justify-self: center;
  margin-bottom: var(--s5);
  display: grid; place-items: center; gap: var(--s2);
  font-size: 0.85rem; letter-spacing: 0.04em;
  color: rgba(247, 241, 230, 0.78);
  text-decoration: none;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.9), 0 0 3px rgba(0, 0, 0, 0.7);
  transition: color var(--t-mid) var(--ease-out);
}
.scroll-cue:hover { color: var(--amber-soft); }
.scroll-cue span {
  display: block; width: 1px; height: 44px;
  background: linear-gradient(to bottom, rgba(247,241,230,0.5), transparent);
}

/* stops everything that moves in the hero (WCAG 2.2.2); only shown when there is
   motion to stop and a script to stop it */
.hero-pause {
  display: none;
  position: absolute;
  right: clamp(1rem, 3vw, 2rem); bottom: clamp(1rem, 3vw, 2rem);
  width: 44px; height: 44px;
  place-items: center;
  border-radius: 50%;
  background: rgba(10, 9, 8, 0.35);
  border: 1px solid rgba(247, 241, 230, 0.22);
  color: rgba(247, 241, 230, 0.78);
  cursor: pointer;
  transition: color var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
html.js .hero-pause { display: grid; }
.hero-pause:hover { color: var(--cream); border-color: var(--amber); }
.hero-pause:active { transform: scale(0.94); }
.hero-pause svg {
  width: 16px; height: 16px;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.hero-pause .i-play { display: none; }
.hero-pause[aria-pressed='true'] .i-pause { display: none; }
.hero-pause[aria-pressed='true'] .i-play { display: inline; }
.hero[data-still='true'] .stars::before,
.hero[data-still='true'] .stars::after { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { html.js .hero-pause { display: none; } }

@media (prefers-reduced-motion: no-preference) {
  .stars::before { animation: twinkle 6.5s ease-in-out infinite alternate; }
  .stars::after  { animation: twinkle 9s ease-in-out -4s infinite alternate-reverse; }
  @keyframes twinkle { from { opacity: 0.3; } to { opacity: 1; } }

  .scroll-cue span { animation: drift 2.8s var(--ease-soft) infinite; }
  @keyframes drift {
    0%, 100% { opacity: 0.35; transform: scaleY(0.72); transform-origin: top; }
    50%      { opacity: 1;    transform: scaleY(1);    transform-origin: top; }
  }

  /* arrival: the scene settles, the names come out of the haze one at a time,
     Tori, then Alec, then the ampersand that joins them */
  .hero-video { animation: settle 3.6s var(--ease-out) both; }
  @keyframes settle {
    from { scale: 1.08; opacity: 0.35; }
    to   { scale: 1.03; opacity: 1; }
  }
  .hero .display .w { animation: ink 1.9s var(--ease-out) both; }
  .hero .display .w:nth-child(1) { animation-delay: 0.3s; }
  .hero .display .w:nth-child(3) { animation-delay: 0.6s; }
  .hero .display .w:nth-child(2) { animation-delay: 1.05s; animation-duration: 2.3s; }
  @keyframes ink {
    from { opacity: 0; filter: blur(14px); transform: translateY(0.12em); }
    to   { opacity: 1; filter: blur(0);    transform: none; }
  }
  .hero-inner > :not(h1) { animation: surface 1.3s var(--ease-out) both; }
  .hero-inner > :nth-child(2) { animation-delay: 1.4s; }
  .hero-inner > :nth-child(3) { animation-delay: 1.7s; }
  .scroll-cue, .hero-pause { animation: appear 1.2s var(--ease-soft) 2.3s both; }
  @keyframes surface {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
  }
  @keyframes appear { from { opacity: 0; } to { opacity: 1; } }

  /* night falls as the hero scrolls away, tied to the scroll position */
  @supports (animation-timeline: scroll()) {
    .hero-media, .night, .stars, .hero-inner {
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: scroll(root);
      animation-range: 0 100svh;
    }
    .hero-media { animation-name: dusk-sink; }
    .night      { animation-name: nightfall; }
    .stars      { animation-name: stars-out; }
    .hero-inner { animation-name: names-drift; animation-range: 0 70svh; }
    .scroll-cue {
      animation: appear 1.2s var(--ease-soft) 2.3s both, cue-out linear both;
      animation-timeline: auto, scroll(root);
      animation-range: normal, 0 20svh;
    }
    @keyframes dusk-sink   { to { scale: 1.1; translate: 0 12%; } }
    @keyframes nightfall   { to { opacity: 0.85; } }
    @keyframes stars-out   { to { opacity: 1; } }
    @keyframes names-drift { to { translate: 0 20svh; opacity: 0; } }
    @keyframes cue-out     { to { filter: opacity(0); } }
  }
}

/* ============================================================
   sections
   ============================================================ */
section { position: relative; }
.section { padding-block: clamp(5rem, 10vw, 11rem); }
.section-head { margin-bottom: var(--s7); }
.section-head .eyebrow { display: block; margin-bottom: var(--s3); }
.section-note {
  margin-top: var(--s4);
  max-width: var(--measure);
  color: var(--cream-dim);
}

.rule {
  width: 100%; height: 1px; border: 0;
  background: linear-gradient(to right, transparent, var(--line), transparent);
  margin-block: 0;
}

/* the story — heading and prose sit as two editorial columns.
   No own background: the ::before glow must sit behind the text but
   above the body ground, same trick as .page-head. */
.story::before,
.feature::before,
#galleries::before {
  content: '';
  position: absolute; inset: 0 0 auto 0; z-index: -1;
  height: min(420px, 60%);
  background: radial-gradient(62% 100% at 50% 0%,
    rgba(233, 160, 92, 0.07) 0%, rgba(233, 160, 92, 0) 72%);
}
.story-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: clamp(2rem, 6vw, var(--s8));
  align-items: start;
}
@media (max-width: 900px) {
  .story-grid { grid-template-columns: 1fr; gap: var(--s5); }
}
.section.story { padding-bottom: clamp(3rem, 6vw, 6rem); }
.story-grid .section-head { margin-bottom: 0; }
.story-body { max-width: var(--measure); }
.story-body p + p { margin-top: var(--s4); }
.story-body p {
  font-size: clamp(1.05rem, 1rem + 0.25vw, 1.18rem);
  color: var(--cream-dim);
  line-height: 1.85;
}
.story-body strong { color: var(--cream); font-weight: 500; }

.portrait-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));   /* always side by side: they are a pair */
  gap: clamp(0.75rem, 2.5vw, var(--s4));
  margin-top: var(--s6);
  max-width: 38rem;
}
.portrait {
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: var(--ink-700);
  /* framed like a physical print: dark mat, hairline, deep soft shadow */
  border: 6px solid #171310;
  box-shadow: 0 0 0 1px var(--line-soft), 0 18px 50px rgba(0, 0, 0, 0.35);
}
.portrait img {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(0.92) contrast(1.02);
  transition: transform var(--t-slow) var(--ease-soft),
              filter var(--t-slow) var(--ease-soft);
}
.portrait figcaption {
  position: absolute; inset: auto 0 0 0;
  padding: var(--s5) var(--s4) var(--s3);
  background: linear-gradient(to top, rgba(10,9,8,0.92), transparent);
  font-family: var(--serif);
  font-size: 1.25rem;
  letter-spacing: 0.02em;
}
.portrait figcaption small {
  display: block;
  font-family: var(--sans);
  font-size: 0.82rem;
  letter-spacing: 0.03em;
  color: var(--muted);
  margin-top: 2px;
}
@media (hover: hover) and (pointer: fine) {
  .portrait:hover img { transform: scale(1.03); filter: saturate(1) contrast(1.04); }
}

/* ============================================================
   collection cards — the year / childhood index
   ============================================================ */
.collections {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 1px;
  background: var(--line-soft);
  border-block: 1px solid var(--line-soft);
}
/* the childhood row is three cards; keep them wider than the six year cards */
.collections[data-cols='2'], .collections[data-cols='3'] {
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
}
/* six years: always a full rectangle — 3×2 on desktop, 2×3 on tablet — never
   five plus an orphan */
@media (min-width: 900px) { .collections[data-cols='6'] { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 520px) and (max-width: 899.98px) {
  .collections[data-cols='6'] { grid-template-columns: repeat(2, 1fr); }
}

.card {
  position: relative;
  display: flex; flex-direction: column; justify-content: flex-end;
  min-height: clamp(260px, 30vw, 340px);
  padding: var(--s5) var(--s4);
  background: var(--ink-800);
  text-decoration: none;
  overflow: hidden;
  isolation: isolate;
}
.card-img {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.55;
  filter: saturate(0.82) contrast(1.05);
  transition: opacity var(--t-slow) var(--ease-soft),
              transform var(--t-slow) var(--ease-soft),
              filter var(--t-slow) var(--ease-soft);
}
.card::after {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to top,
    rgba(10, 9, 8, 0.95) 0%,
    rgba(10, 9, 8, 0.55) 45%,
    rgba(10, 9, 8, 0.25) 100%);
  transition: background var(--t-slow) var(--ease-soft);
}
.card h3 {
  font-size: clamp(1.6rem, 1.35rem + 1vw, 2.15rem);
  font-weight: 300;
  transition: color var(--t-mid) var(--ease-out);
}
/* the six years read as engraved numerals */
.collections[data-cols='6'] .card h3 {
  font-variant-numeric: oldstyle-nums;
  letter-spacing: 0.05em;
}
.card .count {
  margin-top: var(--s1);
  font-size: 0.86rem;
  letter-spacing: 0.02em;
  color: var(--cream-dim);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.8);
}
.card .go {
  margin-top: var(--s3);
  display: inline-flex; align-items: center; gap: var(--s2);
  font-size: 0.88rem; letter-spacing: 0.03em;
  color: var(--amber);
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--t-mid) var(--ease-out),
              transform var(--t-mid) var(--ease-out);
}
.card .go svg { width: 14px; height: 14px; stroke: currentColor; }

@media (hover: hover) and (pointer: fine) {
  .card:hover .card-img { opacity: 0.72; transform: scale(1.045); filter: saturate(0.95); }
  .card:hover::after {
    background: linear-gradient(to top,
      rgba(10, 9, 8, 0.92) 0%, rgba(10, 9, 8, 0.42) 55%, rgba(10, 9, 8, 0.15) 100%);
  }
  .card:hover h3 { color: #fffaf1; }
  .card:hover .go { opacity: 1; transform: translateY(0); }
}
.card:focus-visible { outline-offset: -3px; }
.card:focus-visible .go { opacity: 1; transform: translateY(0); }

/* ============================================================
   the print strip — photographs laid out like prints on a table
   ============================================================ */
.strip-sec {
  padding-block: clamp(1.5rem, 3vw, 3rem) clamp(4rem, 8vw, 7rem);
  overflow: hidden;
}
.strip {
  display: flex; justify-content: center; align-items: center;
  padding-block: var(--s5);
}
.print {
  position: relative;
  flex: 0 0 auto;
  width: clamp(150px, 16vw, 224px);
  margin: 0;
  /* a real print: paper border, deeper foot, physical shadow */
  padding: clamp(7px, 0.7vw, 10px);
  padding-bottom: clamp(24px, 2.4vw, 34px);
  background: #f2ecdf;
  transform: rotate(var(--tilt, 0deg));
  box-shadow: 0 3px 9px rgba(0, 0, 0, 0.45), 0 26px 60px rgba(0, 0, 0, 0.5);
  transition: transform var(--t-mid) var(--ease-out),
              box-shadow var(--t-mid) var(--ease-out);
}
.print + .print { margin-left: clamp(-3.2rem, -3vw, -1.1rem); }
.print:nth-child(even) { translate: 0 12px; }
.print img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  filter: saturate(0.95) contrast(1.02);
}
@media (hover: hover) and (pointer: fine) {
  .print:hover {
    transform: rotate(0deg) scale(1.05);
    z-index: 2;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5), 0 40px 90px rgba(0, 0, 0, 0.6);
  }
}
@media (max-width: 760px) {
  .strip {
    justify-content: flex-start;
    overflow-x: auto;
    padding-inline: var(--s5);
    scrollbar-width: none;
  }
  .strip::-webkit-scrollbar { display: none; }
  .print { width: 152px; }
  .print + .print { margin-left: -0.9rem; }
}
.strip-note {
  text-align: center;
  margin-top: var(--s4);
  font-family: var(--serif); font-style: italic;
  font-size: 1.05rem;
  color: var(--muted);
  letter-spacing: 0.04em;
}

/* ============================================================
   the verse — a held breath between childhood and the years after
   ============================================================ */
.verse-sec {
  position: relative;
  text-align: center;
  padding-block: clamp(6rem, 13vw, 12rem);
  isolation: isolate;
}
.verse-amp {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -54%);
  z-index: -1;
  font-family: var(--serif); font-style: italic;
  font-size: clamp(15rem, 34vw, 30rem);
  line-height: 1;
  color: rgba(233, 160, 92, 0.055);
  pointer-events: none; user-select: none;
}
/* a few stars behind the line, the same sky the hero ends in */
.verse-sec::before {
  content: '';
  position: absolute; inset: 6% 4%; z-index: -1;
  pointer-events: none;
  opacity: 0.55;
  background-repeat: no-repeat;
  background-image:
    radial-gradient(1px 1px at 8% 22%,  rgba(247,241,230,0.8), transparent),
    radial-gradient(1.5px 1.5px at 19% 64%, rgba(247,241,230,0.55), transparent),
    radial-gradient(1px 1px at 27% 12%, rgba(247,241,230,0.6), transparent),
    radial-gradient(1px 1px at 38% 81%, rgba(247,241,230,0.45), transparent),
    radial-gradient(1.5px 1.5px at 61% 9%,  rgba(247,241,230,0.7), transparent),
    radial-gradient(1px 1px at 72% 74%, rgba(247,241,230,0.5), transparent),
    radial-gradient(1px 1px at 83% 28%, rgba(247,241,230,0.75), transparent),
    radial-gradient(1.5px 1.5px at 93% 58%, rgba(247,241,230,0.5), transparent),
    radial-gradient(1px 1px at 52% 92%, rgba(247,241,230,0.4), transparent),
    radial-gradient(1px 1px at 3% 84%,  rgba(247,241,230,0.5), transparent);
}
.verse-line {
  font-family: var(--serif); font-style: italic; font-weight: 300;
  font-size: clamp(2rem, 1.3rem + 3vw, 3.9rem);
  line-height: 1.2;
  color: var(--cream);
  text-wrap: balance;
}

/* ============================================================
   gallery grid
   ============================================================ */
.gallery {
  columns: 300px;
  column-gap: var(--s2);
  padding-block: var(--s5);
}
@media (min-width: 1900px) { .gallery { columns: 340px; } }
@media (max-width: 640px)  { .gallery { columns: 2 150px; column-gap: 6px; } }

.shot {
  break-inside: avoid;
  margin-bottom: var(--s2);
  padding: 0; border: 0;
  background: var(--ink-700);
  width: 100%;
  display: block;
  position: relative;
  overflow: hidden;
  cursor: pointer;
  border-radius: var(--radius);
  transition: transform var(--t-mid) var(--ease-out);
}
@media (max-width: 640px) { .shot { margin-bottom: 6px; } }

.shot img {
  width: 100%; height: auto;
  /* the load fade is slow and gentle; the hover response is not — you sweep
     across a few hundred of these, and a 700ms hover reads as lag */
  transition: opacity var(--t-slow) var(--ease-soft),
              transform var(--t-fast) var(--ease-out),
              filter var(--t-fast) var(--ease-out);
  filter: saturate(0.88) brightness(0.94);
}
.shot::after {
  content: '';
  position: absolute; inset: 0;
  box-shadow: inset 0 0 0 1px var(--line-soft);
  border-radius: var(--radius);
  pointer-events: none;
  transition: box-shadow var(--t-mid) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .shot:hover img { filter: saturate(1.02) brightness(1.04); transform: scale(1.025); }
  .shot:hover::after { box-shadow: inset 0 0 0 1px rgba(233, 160, 92, 0.5); }
}
.shot:active { transform: scale(0.985); }

/* The fade-in on photograph decode was REMOVED on 2026-09-06.
   It hid every img at opacity 0 until site.js flipped data-loaded, gated on html.js.
   But html.js is set by an inline <head> script, so it is present whenever SCRIPTING
   is alive - including when site.js itself 404s, is blocked, or throws. Measured: with
   site.js aborted, 154/154 photographs on childhood-early.html and 190/190 on 2012.html
   stayed invisible, at every viewport and both motion settings. The gate guarded the
   unlikely failure (scripting off) and missed the likely one.
   There is no gate to get right now: the browser paints each WebP as it decodes, which
   is what the fade was imitating, and 1,158 irreplaceable photographs can no longer be
   hidden by a script that did not arrive. */

/* ============================================================
   lightbox
   ============================================================ */
.lightbox {
  position: fixed; inset: 0; z-index: 300;
  display: grid; place-items: center;
  padding: clamp(0.75rem, 3vw, var(--s5));
  /* opaque, deliberately no backdrop-filter: blurring a full-screen layer over a
     10,000px gallery pins the compositor for seconds on long pages */
  background: #060505;
  opacity: 0; visibility: hidden;
  /* visibility is delayed on CLOSE (so the fade-out is visible) but must flip at 0s on
     OPEN. Transitioned symmetrically, it stayed 'hidden' for the whole 0.32s after
     data-open flipped, so site.js's .lb-close.focus() in the same tick did nothing:
     focus stayed on the thumbnail and the next Tab walked to the thumbnails painted
     underneath the open viewer. */
  transition: opacity var(--t-mid) var(--ease-out), visibility 0s var(--t-mid);
}
.lightbox[data-open='true'] {
  opacity: 1; visibility: visible;
  transition: opacity var(--t-mid) var(--ease-out), visibility 0s 0s;
}

.lightbox figure {
  position: relative;
  max-width: min(100%, 1500px);
  max-height: 100%;
  display: grid;
  place-items: center;
  transform: scale(0.97);
  transition: transform var(--t-mid) var(--ease-out);
}
.lightbox[data-open='true'] figure { transform: scale(1); }

.lightbox img {
  max-width: 100%;
  max-height: calc(100svh - 8rem);
  width: auto; height: auto;
  object-fit: contain;
  box-shadow: 0 30px 90px rgba(0, 0, 0, 0.7);
}

.lb-btn {
  position: absolute; z-index: 2;
  display: grid; place-items: center;
  width: 48px; height: 48px;
  background: rgba(20, 17, 15, 0.72);
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--cream);
  cursor: pointer;
  transition: background var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.lb-btn:hover { background: rgba(233, 160, 92, 0.2); border-color: var(--amber); }
.lb-btn:active { transform: scale(0.92); }
.lb-btn svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 1.5; }

.lb-close { top: clamp(0.75rem, 2vw, 1.5rem); right: clamp(0.75rem, 2vw, 1.5rem); }
/* keep a photograph: same-origin, so a plain <a download> is the whole feature */
.lb-save {
  top: clamp(0.75rem, 2vw, 1.5rem);
  right: calc(clamp(0.75rem, 2vw, 1.5rem) + 60px);
  text-decoration: none;
}
.lb-save svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

/* the swap between two photographs is a fade, not a cut: reassigning .src alone
   replaced one face with another in a single frame */
.lightbox img { transition: opacity var(--t-mid) var(--ease-out); }
.lightbox img[data-swapping='true'] { opacity: 0; }
.lb-prev { left: clamp(0.5rem, 2vw, 1.5rem); top: 50%; transform: translateY(-50%); }
.lb-next { right: clamp(0.5rem, 2vw, 1.5rem); top: 50%; transform: translateY(-50%); }
.lb-prev:active, .lb-next:active { transform: translateY(-50%) scale(0.92); }

.lb-count {
  position: absolute; bottom: clamp(0.5rem, 2vw, 1.25rem); left: 50%;
  transform: translateX(-50%);
  font-size: 0.75rem; letter-spacing: 0.18em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

body[data-lb-open='true'] { overflow: hidden; }

/* ============================================================
   films — the home movies
   Native <video controls>; the only custom piece is the poster's
   play mark, which steps aside once playback starts.
   ============================================================ */
.films { display: grid; gap: clamp(2rem, 4vw, var(--s7)); padding-block: var(--s5); }
.films + .films { padding-top: 0; }
.films-grid {
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  column-gap: clamp(1.25rem, 3vw, var(--s5));
}

.film { margin: 0; }
.film-frame {
  position: relative;
  background: #000;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  overflow: hidden;
  /* the poster is the frame, so old 4:3 phone clips keep their shape */
}
.film-frame video { width: 100%; height: 100%; object-fit: contain; background: #000; }
/* in the grid every frame is 16:9 so rows and captions line up; the old 4:3
   phone clips sit letterboxed inside, like a print in a mat */
.films-grid .film-frame { aspect-ratio: 16 / 9 !important; }

.film-play {
  position: absolute; left: 50%; top: 50%;
  translate: -50% -50%;
  width: clamp(64px, 8vw, 84px); aspect-ratio: 1;
  display: grid; place-items: center;
  border-radius: 50%;
  background: rgba(10, 9, 8, 0.55);
  border: 1px solid rgba(247, 241, 230, 0.32);
  backdrop-filter: blur(8px);
  color: var(--cream);
  cursor: pointer;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  transition: background var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out),
              opacity var(--t-mid) var(--ease-out);
}
.film-play svg { width: 40%; height: 40%; fill: currentColor; translate: 6% 0; } /* optical centre */
.film-play:hover { background: rgba(233, 160, 92, 0.32); border-color: var(--amber); }
@media (hover: hover) and (pointer: fine) {
  .film-play:hover { transform: scale(1.05); }
}
.film-play:active { transform: scale(0.94); }
.film[data-playing='true'] .film-play {
  opacity: 0; pointer-events: none;
  transform: scale(1.18);
}

.film figcaption {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--s4);
  margin-top: var(--s3);
}
/* real <h2> so the heading order never skips a level (a film is the direct child of the
   page h1); sized as a card title, not as a section head. Same trick as .foot-h. */
.film h2 { font-weight: 400; font-size: clamp(1.2rem, 1.1rem + 0.5vw, 1.55rem); line-height: 1.25; }
.film figcaption p { margin-top: 2px; color: var(--muted); font-size: 0.95rem; max-width: 40ch; }
.film-meta {
  flex-shrink: 0;
  font-size: 0.86rem; letter-spacing: 0.02em;
  color: var(--muted-2);
  font-variant-numeric: tabular-nums;
}
.film-featured h2 { font-size: clamp(1.6rem, 1.3rem + 1.2vw, 2.4rem); font-weight: 300; }
.film-featured figcaption p { font-size: 1.05rem; color: var(--cream-dim); max-width: var(--measure); }

/* homepage feature: the slideshow, linked to its own page */
.feature-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, var(--s8));
  align-items: center;
}
@media (max-width: 900px) {
  .feature-grid { grid-template-columns: 1fr; gap: var(--s5); }
}
.feature-poster {
  position: relative; display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--ink-700);
  /* same physical frame as the portraits */
  border: 6px solid #171310;
  box-shadow: 0 0 0 1px var(--line-soft), 0 24px 60px rgba(0, 0, 0, 0.4);
  border-radius: var(--radius);
  isolation: isolate;
}
.feature-poster img {
  width: 100%; height: 100%; object-fit: cover;
  filter: saturate(0.85) brightness(0.9);
  transition: transform var(--t-slow) var(--ease-soft), filter var(--t-slow) var(--ease-soft);
}
.feature-poster .film-play { pointer-events: none; }
@media (hover: hover) and (pointer: fine) {
  .feature-poster:hover img { transform: scale(1.03); filter: saturate(1) brightness(1); }
  .feature-poster:hover .film-play { background: rgba(233, 160, 92, 0.32); border-color: var(--amber); transform: scale(1.05); }
}
.feature-poster:focus-visible { outline-offset: 4px; }
.feature-body .eyebrow { display: block; margin-bottom: var(--s3); }
.feature-body p {
  margin-top: var(--s4);
  max-width: var(--measure);
  color: var(--cream-dim);
  font-size: clamp(1.05rem, 1rem + 0.25vw, 1.18rem);
  line-height: 1.85;
}
.feature-body .back-link { margin-top: var(--s4); }

/* ============================================================
   page header for interior pages
   ============================================================ */
.page-head {
  position: relative;
  padding: calc(var(--s9) + 2rem) 0 var(--s7);
  border-bottom: 1px solid var(--line-soft);
  overflow: hidden;
}
.page-head::before {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  background: radial-gradient(90% 130% at 50% -20%,
    rgba(233, 160, 92, 0.13) 0%, rgba(233, 160, 92, 0) 62%);
}
.page-head .eyebrow { display: block; margin-bottom: var(--s3); }
.page-head h1 {
  font-size: clamp(2.75rem, 1.5rem + 4.6vw, 5.5rem);
  font-variant-numeric: oldstyle-nums;
  font-weight: 300;
}
.page-head p { margin-top: var(--s4); max-width: var(--measure); color: var(--muted); }
/* the year, monumental and barely there, behind each gallery's heading */
.page-head .ghost-title {
  position: absolute; right: 1vw; bottom: -0.14em; z-index: -1;
  font-family: var(--serif);
  font-size: clamp(11rem, 26vw, 24rem);
  line-height: 0.8;
  font-variant-numeric: oldstyle-nums;
  color: rgba(247, 241, 230, 0.04);
  pointer-events: none; user-select: none;
}

.back-link {
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: 44px;            /* tap target */
  margin-top: var(--s4);
  font-size: 0.92rem; letter-spacing: 0.02em;
  color: var(--amber);
  text-decoration: none;
  transition: color var(--t-fast) var(--ease-out);
}
.back-link svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 1.5; }
.back-link:hover { color: var(--amber-soft); }
/* the arrow travels; gap stays put — gap is layout, transform is not */
.back-link svg, .pager a svg { transition: transform var(--t-mid) var(--ease-out); }
.back-link:hover svg, .pager a:hover svg { transform: translateX(4px); }
.back-link--rev:hover svg, .pager a.pg-rev:hover svg { transform: translateX(-4px); }

/* pager between galleries */
.pager {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s4);
  padding-block: var(--s6);
  border-top: 1px solid var(--line-soft);
}
.pager a {
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: 44px;
  font-family: var(--serif); font-size: 1.15rem;
  color: var(--cream-dim); text-decoration: none;
  transition: color var(--t-fast) var(--ease-out);
}
.pager a:hover { color: var(--amber-soft); }
.pager a svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.5; }
.pager span { font-size: 0.88rem; letter-spacing: 0.02em; color: var(--muted-2); font-variant-numeric: tabular-nums; }

/* ============================================================
   contact
   ============================================================ */
.form { max-width: 34rem; margin-top: var(--s6); display: grid; gap: var(--s4); }
.field { display: grid; gap: var(--s1); }
.field label {
  font-size: 0.88rem; letter-spacing: 0.02em;
  color: var(--muted);
}
.field label .req { color: var(--amber); }
.field input, .field textarea {
  width: 100%;
  padding: var(--s2) var(--s3);
  background: var(--ink-800);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--cream);
  font-size: 1rem;
  transition: border-color var(--t-fast) var(--ease-out),
              background var(--t-fast) var(--ease-out);
}
.field input:hover, .field textarea:hover { border-color: var(--muted-2); }
.field input:focus, .field textarea:focus {
  outline: none;
  border-color: var(--amber);
  background: var(--ink-700);
}
.field textarea { min-height: 9rem; resize: vertical; }

.btn {
  justify-self: start;
  display: inline-flex; align-items: center; gap: var(--s2);
  padding: var(--s3) var(--s5);
  min-height: 48px;
  background: var(--amber);
  color: var(--ink-900);
  border: 1px solid var(--amber);
  border-radius: var(--radius);
  font-size: 0.95rem; font-weight: 500;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.btn:hover { background: var(--amber-soft); }
.btn:active { transform: scale(0.97); }

.form-note {
  font-size: 0.85rem; color: var(--muted-2);
  border-left: 2px solid var(--line);
  padding-left: var(--s3);
}

/* ============================================================
   footer
   ============================================================ */
.site-foot {
  padding-block: var(--s7) var(--s5);
  border-top: 1px solid var(--line-soft);
  background: var(--ink-900);
}
.foot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: var(--s6);
}
/* real <h2> so the heading order never skips a level; styled as a small label */
.foot-grid .foot-h {
  font-family: var(--serif); font-style: italic;
  font-size: 1.15rem; font-weight: 400;
  line-height: 1.4; letter-spacing: 0.01em;
  color: var(--muted);
  margin-bottom: var(--s3);
}
.foot-grid ul { list-style: none; padding: 0; display: grid; }
.foot-grid a {
  display: inline-flex; align-items: center; min-height: 44px;   /* tap target */
  font-size: 0.92rem; color: var(--cream-dim); text-decoration: none;
  transition: color var(--t-fast) var(--ease-out);
}
.foot-grid a:hover { color: var(--amber-soft); }

.foot-note {
  margin-top: var(--s7);
  padding-top: var(--s4);
  border-top: 1px solid var(--line-soft);
  display: flex; flex-wrap: wrap; gap: var(--s3); justify-content: space-between;
  font-size: 0.8rem; color: var(--muted-2);
}
.foot-credit {
  margin-top: var(--s4);
  font-size: 0.8rem; color: var(--muted-2);
}
.foot-credit a {
  color: var(--cream-dim); text-underline-offset: 3px;
  transition: color var(--t-fast) var(--ease-out);
}
.foot-credit a:hover { color: var(--amber-soft); }
.foot-verse {
  font-family: var(--serif); font-style: italic;
  font-size: 1.2rem; color: var(--cream-dim);
}

/* ============================================================
   scroll reveal
   Every hiding rule is scoped to html.js. The head script sets it and takes it
   away again after 2.5s unless site.js reports its observers are wired, so a
   missing or broken script leaves a readable page, never a blank one.
   site.js turns data-shown on as an element comes in, and off again only once
   it has left through the bottom of the screen: every pass down plays the
   same, and scrolling back up never blanks anything.
   ============================================================ */
.ln { display: block; overflow: hidden; padding-block: 0.06em 0.14em; margin-block: -0.06em -0.14em; }
.ln > span { display: block; }

@media (prefers-reduced-motion: no-preference) {
  /* the default: a soft rise out of a blur */
  html.js [data-reveal] {
    opacity: 0;
    transform: translateY(14px);
    filter: blur(8px);
    transition: opacity 0.8s var(--ease-out) var(--d, 0ms),
                transform 0.8s var(--ease-out) var(--d, 0ms),
                filter 0.8s var(--ease-out) var(--d, 0ms);
  }
  html.js [data-reveal][data-shown='true'] { opacity: 1; transform: none; filter: none; }

  /* containers that choreograph their own children stay put themselves */
  html.js .section-head[data-reveal],
  html.js .feature-body[data-reveal],
  html.js .story-body[data-reveal],
  html.js .collections[data-reveal],
  html.js .strip[data-reveal],
  html.js .verse-sec[data-reveal] { opacity: 1; transform: none; filter: none; }

  /* headings: each line rises out of its own mask */
  html.js [data-reveal] .ln > span {
    transform: translateY(105%);
    transition: transform 1.1s var(--ease-out);
  }
  html.js [data-reveal][data-shown='true'] .ln > span { transform: none; }
  html.js [data-reveal] .ln:nth-child(2) > span { transition-delay: 90ms; }
  html.js [data-reveal] .ln:nth-child(3) > span { transition-delay: 180ms; }

  /* the words around a heading: the label first, the rest once the heading lands */
  html.js .section-head[data-reveal] > :not(h2),
  html.js .feature-body[data-reveal] > :not(h2),
  html.js .story-body[data-reveal] > p {
    opacity: 0;
    transform: translateY(12px);
    filter: blur(6px);
    transition: opacity 0.9s var(--ease-out),
                transform 0.9s var(--ease-out),
                filter 0.9s var(--ease-out);
  }
  html.js .section-head[data-reveal][data-shown='true'] > :not(h2),
  html.js .feature-body[data-reveal][data-shown='true'] > :not(h2),
  html.js .story-body[data-reveal][data-shown='true'] > p { opacity: 1; transform: none; filter: none; }
  html.js .section-head[data-reveal] > :not(h2):not(.eyebrow),
  html.js .feature-body[data-reveal] > :not(h2):not(.eyebrow) { transition-delay: 320ms; }
  html.js .story-body[data-reveal] > p:nth-child(2) { transition-delay: 120ms; }
  html.js .story-body[data-reveal] > p:nth-child(3) { transition-delay: 240ms; }

  /* photographs develop: the print fills in from the bottom of its frame.
     The clip is on the img, never on the element the observer watches */
  html.js .section-head[data-reveal] .portrait img,
  html.js .feature-poster[data-reveal] img {
    clip-path: inset(100% 0 0 0);
    transition: clip-path 1.4s var(--ease-in-out) 0.4s,
                transform var(--t-slow) var(--ease-soft),
                filter var(--t-slow) var(--ease-soft);
  }
  html.js .section-head[data-reveal] .portrait + .portrait img { transition-delay: 0.58s, 0s, 0s; }
  html.js .section-head[data-reveal][data-shown='true'] .portrait img,
  html.js .feature-poster[data-reveal][data-shown='true'] img { clip-path: inset(0 0 0 0); }
  html.js .feature-poster[data-reveal] { filter: none; }

  /* collections reveal card by card, and each picture settles as it lands */
  html.js .collections[data-reveal] .card {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity var(--t-slow) var(--ease-out),
                transform var(--t-slow) var(--ease-out);
  }
  html.js .collections[data-reveal][data-shown='true'] .card { opacity: 1; transform: none; }
  html.js .collections[data-reveal] .card:nth-child(2) { transition-delay: 80ms; }
  html.js .collections[data-reveal] .card:nth-child(3) { transition-delay: 160ms; }
  html.js .collections[data-reveal] .card:nth-child(4) { transition-delay: 240ms; }
  html.js .collections[data-reveal] .card:nth-child(5) { transition-delay: 320ms; }
  html.js .collections[data-reveal] .card:nth-child(6) { transition-delay: 400ms; }
  html.js .collections[data-reveal] .card-img {
    scale: 1.12;
    transition: opacity var(--t-slow) var(--ease-soft),
                transform var(--t-slow) var(--ease-soft),
                filter var(--t-slow) var(--ease-soft),
                scale 1.8s var(--ease-out);
  }
  html.js .collections[data-reveal][data-shown='true'] .card-img { scale: 1; }

  /* prints land one by one and settle into their tilt */
  html.js .strip[data-reveal] .print {
    opacity: 0;
    transform: rotate(0deg) translateY(26px);
    transition: opacity 0.8s var(--ease-out),
                transform 0.8s var(--ease-out);
  }
  html.js .strip[data-reveal][data-shown='true'] .print {
    opacity: 1;
    transform: rotate(var(--tilt, 0deg));
  }
  html.js .strip[data-reveal] .print:nth-child(2) { transition-delay: 70ms; }
  html.js .strip[data-reveal] .print:nth-child(3) { transition-delay: 140ms; }
  html.js .strip[data-reveal] .print:nth-child(4) { transition-delay: 210ms; }
  html.js .strip[data-reveal] .print:nth-child(5) { transition-delay: 280ms; }
  html.js .strip[data-reveal] .print:nth-child(6) { transition-delay: 350ms; }
  html.js .strip[data-reveal] .print:nth-child(7) { transition-delay: 420ms; }

  /* the verse comes out of the dark one word at a time */
  html.js .verse-sec[data-reveal] .w {
    display: inline-block;
    opacity: 0;
    filter: blur(10px);
    transform: translateY(0.18em);
    transition: opacity 1.4s var(--ease-out),
                filter 1.4s var(--ease-out),
                transform 1.4s var(--ease-out);
  }
  html.js .verse-sec[data-reveal][data-shown='true'] .w { opacity: 1; filter: blur(0); transform: none; }
  html.js .verse-sec[data-reveal] .w:nth-child(2) { transition-delay: 150ms; }
  html.js .verse-sec[data-reveal] .w:nth-child(3) { transition-delay: 300ms; }
  html.js .verse-sec[data-reveal] .w:nth-child(4) { transition-delay: 450ms; }
  .verse-sec::before { animation: twinkle 7s ease-in-out infinite alternate; }

  /* gallery photographs rise in as they arrive; site.js staggers each batch
     left to right through --d */
  html.js .gallery .shot {
    opacity: 0;
    translate: 0 28px;
    transition: opacity 0.8s var(--ease-out) var(--d, 0ms),
                translate 1s var(--ease-out) var(--d, 0ms),
                transform var(--t-mid) var(--ease-out);
  }
  html.js .gallery .shot[data-shown='true'] { opacity: 1; translate: none; }

  /* interior pages arrive: the label, then the title line by line, then the rest.
     Plain keyframes, no script involved, so nothing here can be left hidden. */
  .page-head .eyebrow,
  .page-head p,
  .page-head .back-link { animation: surface 1.1s var(--ease-out) both; }
  .page-head .eyebrow { animation-delay: 0.05s; }
  .page-head p { animation-delay: 0.38s; }
  .page-head .back-link { animation-delay: 0.52s; }
  .page-head h1 .ln > span { animation: line-up 1.2s var(--ease-out) 0.14s both; }
  .page-head h1 .ln + .ln > span { animation-delay: 0.23s; }
  /* arriving by a page transition, the title is already flying in from its card */
  html.vt-in .page-head h1 .ln > span { animation: none; }
  @keyframes line-up { from { transform: translateY(105%); } to { transform: none; } }
  .page-head .ghost-title { animation: ghost-in 2.4s var(--ease-out) 0.2s both; }
  @keyframes ghost-in { from { opacity: 0; filter: blur(16px); } }

  /* depth tied to the scroll position, so it replays by nature */
  @supports (animation-timeline: view()) {
    .portrait img {
      scale: 1.12;
      animation: pan linear both;
      animation-timeline: view();
    }
    @keyframes pan { from { translate: 0 -5%; } to { translate: 0 5%; } }

    .verse-amp {
      animation: amp-float linear both;
      animation-timeline: view();
    }
    @keyframes amp-float {
      from { scale: 0.84; opacity: 0.35; }
      50%  { opacity: 1; }
      to   { scale: 1.1;  opacity: 0.35; }
    }

    .page-head .ghost-title {
      animation: ghost-in 2.4s var(--ease-out) 0.2s both, ghost-drift linear both;
      animation-timeline: auto, scroll(root);
      animation-range: normal, 0 70svh;
    }
    @keyframes ghost-drift { to { translate: 0 35%; } }

    @media (min-width: 761px) {
      .strip {
        animation: strip-drift linear both;
        animation-timeline: view();
      }
      @keyframes strip-drift { from { translate: 3.5% 0; } to { translate: -3.5% 0; } }
    }
  }
}

/* ============================================================
   transitions between pages
   The same-origin page change cross-fades instead of cutting, the header
   holds still, and a card title on the home page flies up to become the
   title of the page it opens (the 2012 card becomes the 2012 heading).
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
.site-head { view-transition-name: site-head; }
::view-transition-group(*) {
  animation-duration: 0.6s;
  animation-timing-function: var(--ease-drawer);
}
::view-transition-old(root),
::view-transition-new(root) { animation-duration: 0.38s; }

/* the lightbox: the photograph grows out of its thumbnail and flies home again */
::view-transition-group(photo) {
  animation-duration: 0.5s;
  animation-timing-function: var(--ease-drawer);
}
.lightbox[data-vt],
.lightbox[data-vt] figure { transition: none; }
.lightbox[data-vt] figure { transform: none; }

/* reduced motion: no keyframes, no movement, no page morphs. Opacity and colour
   transitions stay: they are feedback, not motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
  .shot:hover img, .card:hover .card-img, .portrait:hover img,
  .film-play:hover, .lightbox figure { transform: none; }
  .back-link:hover svg, .pager a:hover svg { transform: none; }
}

@media print {
  .site-head, .scroll-cue, .lightbox, .nav-toggle,
  .hero-media, .night, .stars, .fireflies, .grain { display: none !important; }
  body { background: #fff; color: #000; }

  /* CP-01: nothing is scrolled on paper, so no reveal ever fires: everything that
     waits for one is forced visible, or index.html prints blank sheets */
  [data-reveal], [data-reveal] *, .gallery .shot, .hero *, .page-head * {
    opacity: 1 !important;
    transform: none !important;
    translate: none !important;
    scale: none !important;
    filter: none !important;
    clip-path: none !important;
    animation: none !important;
  }

  /* CP-03: only `body` was recoloured, so every token-coloured element kept its
     near-white screen colour on white paper - the memorial prose measured 1.81:1 and the
     children's names 1.04:1, i.e. invisible. Re-point the tokens rather than chase
     selectors; this changes ink only, never a word. */
  :root {
    --cream: #111;
    --cream-dim: #222;
    --muted: #444;
    --muted-2: #444;
    --amber: #8a4b12;
    --amber-soft: #8a4b12;
  }
  .hero .display, .hero-sub, .hero-dates, .verse-line {
    color: #111 !important;
    text-shadow: none !important;
  }

  /* CP-02: CSS multicol does not fragment across printed pages, so the galleries threw
     out long runs of blank sheets. Grid fragments. */
  .gallery {
    columns: auto;
    column-count: auto;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: min-content;
    align-items: start;
    gap: 6px;
  }
  .shot { break-inside: avoid; }
}
