/* ==========================================================================
   Mona With the Children — stylesheet
   One file, no dependencies. Sections below, in order:

   1.  Fonts (self-hosted)
   2.  Tokens — colour, type, spacing, motion (all on :root)
   3.  Reset & base elements
   4.  Layout utilities (container, section, grid, prose measure)
   5.  Typography (headings, links, lists)
   6.  Hero (home page banner, sizing and the "name" pull-quote's leaves)
   7.  Buttons
   8.  Motion system (reveal-on-scroll, drawn rules, staggered groups)
   9.  Site header & navigation (incl. mobile nav)
   10. Site footer
   11. Image slots (the brand-coloured placeholder panels)
   12. Portrait ring (the "completing ring" hover/focus detail)
   13. Cards, eyebrows, pull quotes, dividers
   14. Forms
   15. Accessibility helpers (skip link, visually-hidden, focus)
   16. Rolling gallery (Home page's auto-scrolling photo/video strip)
   17. 404 page
   18. Responsive breakpoints: 640px / 900px / 1200px

   Palette approved by the organisation's directors, 27 August 2026
   (Brand/BRAND-GUIDE.md §4). This replaced an earlier, rejected concept's
   palette wholesale — nothing here is derived from the old values, and
   the contrast audit below was re-run from scratch against the rendered
   cascade, not carried over from the old one. See DECISIONS.md.

   Contrast ratios actually used on this site, measured against WCAG 2.1 AA
   (4.5:1 body text, 3:1 large text/UI, computed with the standard relative
   luminance formula) — the full table lives in README.md, this note is
   just so anyone editing colours here knows why the choices below aren't
   arbitrary:

     ink/deep-teal (#0A595E) on white (#FFFFFF) ....... 8.07:1
     ink/deep-teal (#0A595E) on paper (#F4F8F8) ........ 7.54:1
     white     (#FFFFFF) on deep-teal ................... 8.07:1
     dark-band-body (#D9E4E5) on deep-teal ............... 6.21:1
     white     (#FFFFFF) on teal (#0B7880) ................ 5.23:1
     ink-muted (#4A6B6E) on white ........................... 5.80:1
     deep-teal (#0A595E) on sunrise-gold (#FFC414) ............ 5.05:1
     error     (#9C3F24) on white ............................... 6.67:1

   Three brand colours fail normal-text AA on white and are deliberately
   restricted to decorative use — shapes, icons, rules, large display type
   only, never body text, labels or links:
     bright-teal (#27A2B4) on white ....... 3.04:1  (large text/UI only)
     leaf-green  (#4E9B05) on white ....... 3.49:1  (large text/UI only)
     amber       (#F79911) on white ....... 2.21:1  (never for text)
     sunrise-gold (#FFC414) on white ...... 1.60:1  (never for text on white — pairs with deep-teal instead)
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Poppins";
  src: url("/assets/fonts/poppins/poppins-medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Poppins";
  src: url("/assets/fonts/poppins/poppins-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Lora";
  src: url("/assets/fonts/lora/lora-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Lora";
  src: url("/assets/fonts/lora/lora-semibold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Brand colour — the identity approved by the organisation's directors,
     27 August 2026 (Brand/BRAND-GUIDE.md §4). This replaces the earlier
     "Under One Sun" concept palette wholesale; nothing here is derived
     from the old values. Contrast ratios below are against #FFFFFF
     unless noted, from BRAND-GUIDE.md's own measurements. */
  --deep-teal: #0a595e; /* primary ink: body text, headings, dark bands — 8.07:1 */
  --teal-ink: #0a5666; /* wordmark-matching ink, used sparingly — 8.28:1 */
  --teal: #0b7880; /* mid teal: secondary text, links, rules, hover states — 5.23:1 */
  --bright-teal: #27a2b4; /* accent — shapes, icons, large display type only — 3.04:1 */
  --leaf-green: #4e9b05; /* growth accent — shapes/icons only — 3.49:1 */
  --green-mid: #69b018; /* highlight — shapes only, never text — 2.68:1 */
  --amber: #f79911; /* warmth accent — shapes, icons, rules only — 2.21:1 */
  --sunrise-gold: #ffc414; /* sun gold — ink-on-gold buttons, rules, large headings only — 1.60:1 on white, 5.05:1 with deep-teal text/ground */
  --ink: #0a595e; /* body text = deep teal, per the brand guide's own "primary ink" role */
  --ink-muted: #4a6b6e; /* muted text on white — brand guide's own value, 5.80:1 */
  --error: #9c3f24; /* not a brand colour — a conventional error red, deliberately outside the brand family so an error state is never mistaken for an accent. 6.67:1 on white */
  --white: #ffffff; /* page ground */
  --paper: #f4f8f8; /* soft ground: cards and alternate/raised sections */
  --cream: #ffffff; /* kept as a token name for the one inline SVG fill reference in index.html; equals --white under this palette */

  /* Tints used for washes and glows, derived from the brand colours above */
  --gold-wash-16: rgba(255, 196, 20, 0.16);
  --gold-wash-30: rgba(255, 196, 20, 0.3);
  --gold-glow: rgba(255, 196, 20, 0.55);
  --teal-wash-08: rgba(10, 89, 94, 0.08);
  --teal-wash-12: rgba(10, 89, 94, 0.12);
  --white-wash-90: rgba(255, 255, 255, 0.9);
  /* Softened body-weight text for paragraphs inside a .section--teal band —
     brighter than nothing, quieter than pure white, so headings (white)
     still read as the loudest thing in the band. 6.21:1 on deep-teal. */
  --dark-band-body: #d9e4e5;

  /* Type families */
  --font-display: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, sans-serif;
  --font-body: "Lora", Georgia, "Times New Roman", serif;

  /* Fluid type scale — mobile value first, desktop value at the high end
     of the clamp. All in rem so a visitor's browser zoom still works. */
  --text-2xs: clamp(0.75rem, 0.72rem + 0.15vw, 0.8125rem);
  --text-xs: clamp(0.8125rem, 0.78rem + 0.15vw, 0.875rem);
  --text-sm: clamp(0.9375rem, 0.9rem + 0.2vw, 1rem);
  --text-base: clamp(1.0625rem, 1.02rem + 0.2vw, 1.125rem);
  --text-md: clamp(1.1875rem, 1.1rem + 0.4vw, 1.3125rem);
  --text-lg: clamp(1.375rem, 1.25rem + 0.6vw, 1.625rem);
  --text-xl: clamp(1.625rem, 1.4rem + 1vw, 2rem);
  --text-2xl: clamp(1.875rem, 1.5rem + 1.75vw, 2.75rem);
  --text-3xl: clamp(2.25rem, 1.7rem + 2.5vw, 3.25rem);
  --text-4xl: clamp(2.75rem, 1.9rem + 3.75vw, 4.5rem);

  /* Spacing — 8px scale */
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.5rem;
  --space-4: 2rem;
  --space-5: 3rem;
  --space-6: 4rem;
  --space-7: 6rem;
  --space-8: 8rem;
  --section-padding: clamp(4rem, 10vw, 8rem);

  /* Layout */
  --max-width: 1140px;
  --measure: 66ch;
  --radius-sm: 4px;
  --radius-md: 14px;
  --radius-lg: 28px;
  --radius-full: 999px;

  /* Motion */
  --ease-entrance: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-interact: cubic-bezier(0.4, 0, 0.2, 1);
  --duration-interact: 250ms;
  --duration-interact-fast: 200ms;
  --duration-entrance: 850ms;
  --duration-ring: 400ms;
  --duration-rule: 600ms;
  --stagger-step: 90ms;

  /* Shadow — used sparingly, for raised white cards on the page ground */
  --shadow-card: 0 1px 2px rgba(10, 89, 94, 0.06), 0 12px 32px -16px rgba(10, 89, 94, 0.18);
  --shadow-lift: 0 24px 48px -20px rgba(10, 89, 94, 0.28);
}

/* --------------------------------------------------------------------------
   3. Reset & base elements
   -------------------------------------------------------------------------- */

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

* {
  margin: 0;
}

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

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  /* Blanket safety net: every transition and animation on the site — button
     hovers, the nav underline, the mobile-menu icon, the skip link — drops
     to effectively instant. The more specific reduced-motion rules further
     down this file (for the reveal system, the hero, the portrait ring)
     additionally fix the *visible state* those need, not just the speed;
     this rule is the catch-all for everything else so nothing is missed by
     name one at a time. */
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.6;
  min-height: 100vh;
  overflow-x: hidden;
}

img,
picture,
svg {
  display: block;
  max-width: 100%;
}

img {
  height: auto;
}

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: none;
  cursor: pointer;
}

ul,
ol {
  list-style: none;
  padding: 0;
}

table {
  border-collapse: collapse;
  width: 100%;
}

/* --------------------------------------------------------------------------
   4. Layout utilities
   -------------------------------------------------------------------------- */

.container {
  max-width: var(--max-width);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 5vw, 2.5rem);
}

main {
  display: block;
}

.section {
  padding-block: var(--section-padding);
  position: relative;
}

.section--tight {
  padding-block: clamp(2.5rem, 6vw, 4.5rem);
}

.section--teal {
  background: var(--deep-teal);
  color: var(--cream);
}

/* Anything with its own hardcoded colour needs an explicit override here,
   or it keeps that colour even inside a dark band and can go unreadable —
   this is exactly the gap that made two paragraphs on the live site
   render dark ink on a dark ground. Headings, the eyebrow, links and
   ghost buttons already have their own overrides just below/elsewhere in
   this file; this covers the rest. See DECISIONS.md. */
.section--teal p,
.section--teal .lead {
  color: var(--dark-band-body);
}

.section--teal .pull-quote,
.section--teal .pull-quote cite {
  color: var(--white);
}

.section--teal .card {
  /* A white card creates its own light context regardless of the section
     it sits inside — it must never inherit the band's light text colour. */
  color: var(--ink);
}

.section--cream-raised {
  /* Name kept from the old palette, where this class raised a section from
     a warm cream page to pure white. Under the new palette the page
     itself is white, so this now raises a section to the brand's soft
     ground instead — the same *role* (a section that reads as a shade
     lighter/different from the page around it), just a different pair of
     actual colours. Renaming the class across seven pages felt like more
     churn than the payoff was worth; see DECISIONS.md. */
  background: var(--paper);
  overflow: hidden;
}

/* A faint dot-grid texture at the outer edges of a plain section, fading
   toward the centre so it reads as ambient texture rather than a graphic
   competing with the actual content — content itself always sits in a
   clear, texture-free centre column via .container's own max-width. */
.section--cream-raised::before,
.section--cream-raised::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: min(220px, 18vw);
  background-image: radial-gradient(var(--teal-wash-12) 1.5px, transparent 1.5px);
  background-size: 22px 22px;
  pointer-events: none;
  z-index: 0;
}

.section--cream-raised::before {
  left: 0;
  mask-image: linear-gradient(to right, black, transparent);
  -webkit-mask-image: linear-gradient(to right, black, transparent);
}

.section--cream-raised::after {
  right: 0;
  mask-image: linear-gradient(to left, black, transparent);
  -webkit-mask-image: linear-gradient(to left, black, transparent);
}

.section--cream-raised > .container {
  position: relative;
  z-index: 1;
}

.section--overflow {
  overflow: hidden;
}

.prose {
  max-width: var(--measure);
}

.prose > * + * {
  margin-top: var(--space-3);
}

.prose p {
  font-size: var(--text-base);
}

.grid {
  display: grid;
  gap: var(--space-5);
}

.grid-2 {
  grid-template-columns: 1fr;
}

.grid-3 {
  grid-template-columns: 1fr;
}

.grid-4 {
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4);
}

.grid-people {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4) var(--space-3);
}

@media (min-width: 640px) {
  .grid-people {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 900px) {
  .grid-people {
    grid-template-columns: repeat(5, 1fr);
  }
}

@media (min-width: 640px) {
  .grid-4 {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 900px) {
  .grid-2 {
    grid-template-columns: 1fr 1fr;
  }
  .grid-3 {
    grid-template-columns: repeat(3, 1fr);
  }
  .grid-4 {
    grid-template-columns: repeat(4, 1fr);
  }
}

.split {
  display: grid;
  gap: var(--space-5);
  align-items: center;
}

@media (min-width: 900px) {
  .split {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-7);
  }
  /* Visually reverses the two columns without touching source order (so
     screen readers and keyboard tab order still meet the text before the
     image). Targets first/last-child rather than a direction:rtl trick, so
     it can't misfire on bidi-sensitive inline content. */
  .split--reverse > *:first-child {
    order: 2;
  }
  .split--reverse > *:last-child {
    order: 1;
  }
}

/* The "gap" motif: an element that overflows its column on purpose.
   Only meaningful once .split is actually two columns (>=900px) — below
   that, .split is a single stacked column and there's no "edge" for
   anything to bleed toward, so this must not apply there. Applying it at
   mobile widths used to pull every split image flush to the right edge
   while the rest of the page kept its normal padding. */
@media (min-width: 900px) {
  .bleed-right {
    margin-right: calc(clamp(1.25rem, 5vw, 2.5rem) * -1);
  }
}

@media (min-width: 1200px) {
  .bleed-right {
    margin-right: calc((100vw - var(--max-width)) / -2 - clamp(1.25rem, 5vw, 2.5rem) + 3rem);
  }
}

.center-block {
  max-width: 44rem;
  margin-inline: auto;
  text-align: center;
}

.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;
}

/* --------------------------------------------------------------------------
   5. Typography
   -------------------------------------------------------------------------- */

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  color: var(--deep-teal);
  line-height: 1.15;
  text-wrap: balance;
}

h1 {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-3xl);
}

.hero__heading {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-4xl);
  line-height: 1.08;
}

/* --------------------------------------------------------------------------
   6. Hero
   -------------------------------------------------------------------------- */

.hero-section {
  padding-block: clamp(2rem, 6vw, 4.5rem) clamp(3rem, 8vw, 6rem);
}

/* Mobile: the hero stacks to one column (text, then photo, below). Centred
   text/buttons read as more deliberate here than the left-aligned desktop
   layout, which only works because it sits next to the photo. */
@media (max-width: 899.98px) {
  .hero__content {
    text-align: center;
  }
  .hero__content .btn-row {
    justify-content: center;
  }
}

h2 {
  font-weight: 700;
  font-size: var(--text-2xl);
}

h3 {
  font-weight: 700;
  font-size: var(--text-xl);
}

h4 {
  font-weight: 700;
  font-size: var(--text-md);
}

.section--teal h1,
.section--teal h2,
.section--teal h3,
.section--teal h4 {
  color: var(--cream);
}

p {
  max-width: var(--measure);
}

p.lead {
  font-size: var(--text-md);
  color: var(--ink);
}

small,
.text-small {
  font-size: var(--text-sm);
}

strong {
  font-weight: 600;
}

/* Body links: gold underline grows in from the left on hover/focus. */
a {
  color: var(--deep-teal);
  text-decoration: none;
}

main a:not(.btn):not(.nav-link):not(.card):not(.portrait-ring) {
  background-image: linear-gradient(var(--sunrise-gold), var(--sunrise-gold));
  background-position: 0 100%;
  background-repeat: no-repeat;
  background-size: 0% 2px;
  padding-bottom: 2px;
  transition: background-size var(--duration-interact) var(--ease-interact);
}

main a:not(.btn):not(.nav-link):not(.card):not(.portrait-ring):hover,
main a:not(.btn):not(.nav-link):not(.card):not(.portrait-ring):focus-visible {
  background-size: 100% 2px;
}

.section--teal a:not(.btn) {
  color: var(--cream);
}

.section--teal main a:not(.btn) {
  background-image: linear-gradient(var(--sunrise-gold), var(--sunrise-gold));
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--text-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--deep-teal);
}

.section--teal .eyebrow {
  /* Raw sunrise-gold on the new deep-teal measures 5.05:1 — passes on its
     own, no lightened substitute needed (the old palette's gold only
     measured 4.01:1 in this spot, which is why one existed before). */
  color: var(--sunrise-gold);
}

/* The sun from the mark, as a bold list-bullet icon — a filled centre
   circle with eight thick, rounded rays. Previously a leaf silhouette
   drawn with clip-path: path(), but that path's coordinates were centred
   on the origin (roughly -8.5..8.5) while the box's own coordinate space
   starts at its top-left corner — only the bottom-right sliver of the
   leaf actually fell inside the box, which is why it rendered as an odd
   little mark rather than a recognisable leaf. An SVG with its own
   viewBox sidesteps that entirely: it scales correctly with the box at
   any font size, unlike a fixed-pixel clip-path. */
.leaf-bullet {
  display: inline-block;
  width: 1.15em;
  height: 1.15em;
  color: var(--sunrise-gold);
  flex: none;
}

.leaf-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.leaf-list li {
  display: flex;
  align-items: baseline;
  gap: 0.75em;
}

.leaf-list .leaf-bullet {
  transform: translateY(0.15em);
}

/* --------------------------------------------------------------------------
   7. Buttons
   -------------------------------------------------------------------------- */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  overflow: hidden;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--text-sm);
  text-decoration: none;
  border-radius: var(--radius-full);
  padding: 0.95em 1.7em;
  min-height: 44px;
  border: 2px solid transparent;
  --spot-x: 50%;
  --spot-y: 50%;
  --spot-color: rgba(255, 255, 255, 0.4);
  transition: background-color var(--duration-interact) var(--ease-interact),
    color var(--duration-interact) var(--ease-interact),
    border-color var(--duration-interact) var(--ease-interact),
    transform var(--duration-interact) var(--ease-interact);
}

/* The hover "spotlight": a soft glow that follows the pointer, set via
   --spot-x/--spot-y from main.js on mousemove. Without JS (or before the
   first move), it defaults to dead centre — still a real hover effect,
   just a static one rather than one that tracks the cursor. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(120px circle at var(--spot-x) var(--spot-y), var(--spot-color), transparent 70%);
  opacity: 0;
  transition: opacity var(--duration-interact) var(--ease-interact);
  pointer-events: none;
}

.btn:hover::before,
.btn:focus-visible::before {
  opacity: 1;
}

.btn:active {
  transform: translateY(1px);
}

.btn-primary {
  background: var(--deep-teal);
  color: var(--white);
}

.btn-primary:hover,
.btn-primary:focus-visible {
  background: var(--teal);
}

.btn-gold {
  background: var(--sunrise-gold);
  color: var(--ink);
}

.btn-gold:hover,
.btn-gold:focus-visible {
  /* A slight darken of sunrise-gold for the interactive state. Darkening
     it much further starts to fail against the deep-teal label text
     (both colours converge toward mid-dark), so this stays close to the
     resting colour — deep-teal on it still measures 4.64:1. */
  background: #f5bc12;
}

.btn-ghost {
  background: transparent;
  border-color: currentColor;
  color: var(--deep-teal);
  /* A white glow (the shared default) would be invisible against this
     button's transparent/white-page background, so ghost buttons get a
     teal-tinted spotlight instead. */
  --spot-color: var(--teal-wash-12);
}

.btn-ghost:hover,
.btn-ghost:focus-visible {
  background: var(--teal-wash-08);
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* --------------------------------------------------------------------------
   8. Motion system
   -------------------------------------------------------------------------- */

/* Default state: fully visible. JavaScript adds "reveal-armed" only once it
   has also set up the IntersectionObserver that will reveal the element, so
   a script failure never leaves content invisible — see main.js. */
.reveal,
.reveal-group > * {
  opacity: 1;
  transform: none;
}

/* "Materialise" entrance: content rises, sharpens into focus and settles
   at full scale, rather than a plain fade. --duration-entrance/--ease-entrance
   are shared with the rest of the motion system (buttons, the drawn rule)
   so everything on the page still feels like one family of movement, just
   a more pronounced version of it for whole sections coming into view. */
.reveal-armed {
  opacity: 0;
  transform: translateY(36px) scale(0.97);
  filter: blur(6px);
  transition: opacity var(--duration-entrance) var(--ease-entrance),
    transform var(--duration-entrance) var(--ease-entrance),
    filter var(--duration-entrance) var(--ease-entrance);
  will-change: opacity, transform, filter;
}

.reveal-armed.is-visible {
  opacity: 1;
  transform: none;
  filter: blur(0);
}

.reveal-group.reveal-armed {
  opacity: 1;
  transform: none;
  filter: none;
  transition: none;
}

.reveal-group.reveal-armed > * {
  opacity: 0;
  transform: translateY(36px) scale(0.97);
  filter: blur(6px);
  transition: opacity var(--duration-entrance) var(--ease-entrance),
    transform var(--duration-entrance) var(--ease-entrance),
    filter var(--duration-entrance) var(--ease-entrance);
  transition-delay: calc(var(--i, 0) * var(--stagger-step));
  will-change: opacity, transform, filter;
}

.reveal-group.reveal-armed.is-visible > * {
  opacity: 1;
  transform: none;
  filter: blur(0);
}

/* Drawn rule: a gold hairline that grows left-to-right on entry. */
.rule {
  border: none;
  height: 2px;
  width: 100%;
  background: var(--sunrise-gold);
  transform-origin: left;
  transform: scaleX(1);
}

.rule.reveal-armed {
  opacity: 1;
  transform: scaleX(0);
  transition: transform var(--duration-rule) var(--ease-entrance);
}

.rule.reveal-armed.is-visible {
  transform: scaleX(1);
}

@media (prefers-reduced-motion: reduce) {
  .reveal-armed,
  .reveal-group.reveal-armed > *,
  .rule.reveal-armed {
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
  .rule.reveal-armed {
    transform: scaleX(1) !important;
  }
}

/* Hero sunrise — pure CSS, runs once on load, doesn't wait for scroll
   since the hero is already on screen when the page opens. */
@keyframes sunrise-rise {
  from {
    opacity: 0;
    transform: translate(-50%, 24px);
  }
  to {
    opacity: 0.8;
    transform: translate(-50%, 0);
  }
}

@keyframes settle-up {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.hero-settle {
  opacity: 0;
  animation: settle-up var(--duration-entrance) var(--ease-entrance) forwards;
  animation-delay: calc(var(--i, 0) * 120ms);
}

.hero__sunrise {
  position: absolute;
  left: 50%;
  bottom: -72%;
  width: min(1000px, 110vw);
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 38%,
    var(--gold-glow) 0%,
    var(--gold-wash-30) 48%,
    rgba(227, 169, 59, 0) 72%
  );
  animation: sunrise-rise 900ms var(--ease-entrance) forwards;
  pointer-events: none;
  z-index: 0;
}

@media (prefers-reduced-motion: reduce) {
  .hero-settle {
    animation: none;
    opacity: 1;
  }
  .hero__sunrise {
    animation: none;
    opacity: 0.8;
    transform: translate(-50%, 0);
  }
}

/* --------------------------------------------------------------------------
   9. Site header & navigation
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--cream);
  border-bottom: 1px solid var(--teal-wash-12);
  transition: background-color var(--duration-interact) var(--ease-interact),
    border-color var(--duration-interact) var(--ease-interact);
}

/* Cross-page header transition: opts every page into the browser's
   native Cross-Document View Transitions (the @view-transition rule),
   then names the header element so it's specifically the one the
   browser captures and morphs between pages — the big Home header
   visibly shrinking into the small header of whichever page comes
   next. No JS-driven animation, no SPA routing: this is a real
   multi-page site, each page a full navigation, and the browser does
   the interpolation on its own from these two CSS rules. main.js
   section 6 then narrows *which* navigations actually get this: only
   ones leaving the home page. Every other navigation (back to home,
   or between two non-home pages) explicitly skips the transition, so
   nothing here should be read as "every navigation transitions" — CSS
   opts every page in broadly, JS cancels it everywhere except the one
   direction that's wanted.

   This file is shared by every page (one <link rel="stylesheet">
   each), so a single @view-transition rule here satisfies the spec's
   "both the document being left and the document being arrived at
   must opt in" requirement automatically — there's no per-page HTML
   to keep in sync.

   NOTE: an earlier attempt at this used
   <meta name="view-transition" content="same-origin"> instead — that
   was the *old* Chrome 111 opt-in mechanism, deprecated in Chrome 126
   in favour of this at-rule. It doesn't error or warn when present;
   it just silently does nothing, which is exactly what happened here
   before this fix (confirmed via the pagereveal event's
   e.viewTransition being false on every navigation). The at-rule below
   is the current, correct opt-in — verified against MDN.

   Supported in Chrome/Edge 126+ and Safari 18.2+; every other browser
   (Firefox, older versions) simply ignores an at-rule and selector it
   doesn't recognise and performs a normal, instant navigation instead
   — nothing to build a fallback for by hand. Off entirely under
   reduced motion, matching every other animation on this site. */
@view-transition {
  navigation: auto;
}

@media (prefers-reduced-motion: no-preference) {
  .site-header__inner {
    view-transition-name: site-header-inner;
  }
}

::view-transition-group(site-header-inner) {
  animation-duration: var(--duration-interact);
  animation-timing-function: var(--ease-interact);
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* Home page only: once JS confirms the page is scrolled to the top,
   the header starts larger and fully transparent over the hero, then
   solidifies and shrinks the moment the page scrolls — see main.js
   section 5, which toggles .at-top only on a header carrying the
   --hero modifier. Every other page's header stays at its normal
   (smaller) size throughout — no enlarge-at-top state at all — so
   there's nothing to shrink on those pages themselves; the "shrinks
   as you arrive" feel between a big Home header and a small About/etc.
   header instead comes from the cross-page view-transition below,
   which morphs the header smoothly across the navigation itself. */
.site-header--hero.at-top .site-header__inner {
  min-height: 92px;
}

.site-header--hero.at-top .site-header__logo img {
  height: 56px;
}

.site-header--hero.at-top {
  background: transparent;
  border-bottom-color: transparent;
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-block: var(--space-2);
  min-height: 72px;
  transition: min-height var(--duration-interact) var(--ease-interact);
}

.site-header__logo {
  display: block;
  flex: none;
}

.site-header__logo img {
  height: 40px;
  width: auto;
  transition: height var(--duration-interact) var(--ease-interact);
}

.site-nav {
  display: none;
}

.site-nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.nav-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  /* Text alone here sits under 44px tall — this keeps the visible label
     compact while still giving the actual clickable/tappable area the
     44px minimum interactive targets need. */
  min-height: 44px;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--text-sm);
  color: var(--ink);
  text-decoration: none;
}

.nav-link::after {
  content: "";
  position: absolute;
  left: 0;
  /* .nav-link is a 44px-tall click target (see above) with its text
     vertically centred, so the underline needs to sit just under the
     text itself, not at the very bottom of that taller box. */
  bottom: 8px;
  width: 100%;
  height: 2px;
  background: var(--sunrise-gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--duration-interact) var(--ease-interact);
}

.nav-link:hover::after,
.nav-link:focus-visible::after,
.nav-link[aria-current="page"]::after {
  transform: scaleX(1);
}

.nav-link--cta {
  color: var(--deep-teal);
  font-weight: 600;
}

.nav-toggle {
  /* Hidden until main.js confirms it can actually run the open/close
     behaviour (see the ".nav-js" rule below). Without that, this button
     would sit on the page doing nothing when clicked — worse than not
     having it, since the full link list is already visible in that case.
     See BUILD-LOG.md Phase 8. */
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-sm);
  flex: none;
}

/* Scoped to below the desktop breakpoint so this can never outrank the
   "always hide on desktop" rule further down purely on specificity —
   the two rules apply at mutually exclusive widths instead. */
@media (max-width: 899.98px) {
  .nav-js .nav-toggle {
    display: flex;
  }
}

.nav-toggle__bar {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--deep-teal);
  transition: transform var(--duration-interact) var(--ease-interact),
    opacity var(--duration-interact) var(--ease-interact);
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(2) {
  opacity: 0;
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

.mobile-nav {
  border-top: 1px solid var(--teal-wash-12);
  background: var(--cream);
}

.mobile-nav[hidden] {
  display: none;
}

.mobile-nav__list {
  padding: var(--space-3) clamp(1.25rem, 5vw, 2.5rem) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.mobile-nav__list a {
  display: block;
  padding: 0.85em 0.25em;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--text-md);
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--teal-wash-12);
  min-height: 44px;
}

.mobile-nav__list a[aria-current="page"] {
  color: var(--deep-teal);
  font-weight: 600;
}

@media (min-width: 900px) {
  .site-nav {
    display: block;
  }
  .nav-toggle,
  .mobile-nav {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   10. Site footer
   -------------------------------------------------------------------------- */

.site-footer {
  background: var(--deep-teal);
  color: var(--cream);
}

.site-footer__inner {
  display: grid;
  gap: var(--space-5);
  padding-block: var(--space-6);
}

.site-footer__brand {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.site-footer__mark {
  margin-bottom: var(--space-1);
}

.site-footer__name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-md);
  color: var(--white);
}

.site-footer__descriptor {
  /* Raw sunrise-gold on the new deep-teal measures 5.05:1 — passes
     directly, no dedicated tint needed under this palette. */
  color: var(--sunrise-gold);
  font-size: var(--text-sm);
}

.site-footer h2 {
  color: var(--white);
  font-size: var(--text-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 600;
  margin-bottom: var(--space-2);
}

.site-footer__nav ul,
.site-footer__contact {
  display: flex;
  flex-direction: column;
}

.site-footer__contact {
  gap: 0.6em;
}

.site-footer__nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

.site-footer a {
  color: var(--cream);
}

.site-footer__legal {
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  padding-block: var(--space-3);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  justify-content: space-between;
  font-size: var(--text-2xs);
  /* Re-checked against the new deep-teal: still 5.05:1, still passes. */
  color: #b9d2cb;
}

.site-footer__legal a {
  color: #b9d2cb;
}

@media (min-width: 640px) {
  .site-footer__inner {
    grid-template-columns: 1.2fr 1fr 1fr;
  }
}

/* --------------------------------------------------------------------------
   11. Image slots
   -------------------------------------------------------------------------- */

.image-slot {
  position: relative;
  overflow: hidden;
  background: linear-gradient(155deg, var(--deep-teal) 0%, var(--teal) 62%, var(--sunrise-gold) 145%);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
}

.image-slot::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cpath d='M 42.71,38.57 A 84,84 0 1 0 157.29,38.57' stroke='%23FFFFFF' stroke-opacity='0.35' stroke-width='4' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 46%;
  opacity: 0.9;
}

.image-slot img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Until the real photo file exists at this path, the browser's built-in
     "broken image" icon and alt text would otherwise sit on top of the
     gradient panel below. Sizing the <img> with position:absolute+inset:0
     (rather than percentage width/height in normal flow) means a broken
     image can never inflate its own container while hunting for an
     intrinsic size, and font-size:0 plus a transparent colour hides the
     fallback icon and label without touching a real, successfully-loaded
     photo at all. No onerror, no JavaScript. */
  color: transparent;
  font-size: 0;
}

/* Hero photo: a proper blur-to-sharp materialise, timed to when the
   actual image file has decoded — not to a fixed clock. The rest of
   the hero (.hero-settle) uses a fixed animation-delay, which is fine
   for text that's already in the HTML, but a large photo can still be
   mid-download after that timer finishes: the box fades in on
   schedule, then the photo itself pops in afterwards with no
   transition of its own the moment the network delivers it — a blip,
   not an entrance. main.js section 8 adds --armed once it's watching
   the image's own load event, then --loaded the instant it actually
   fires, so the blur only ever clears in step with real pixels
   arriving. Default (no JS, or before either class lands) is fully
   sharp and visible — the gradient placeholder underneath is only
   ever seen if a visitor is genuinely waiting on the photo. */
.image-slot--hero img.is-armed {
  opacity: 0;
  filter: blur(20px);
  transition: opacity var(--duration-entrance) var(--ease-entrance),
    filter var(--duration-entrance) var(--ease-entrance);
}

.image-slot--hero img.is-armed.is-loaded {
  opacity: 1;
  filter: blur(0);
}

@media (prefers-reduced-motion: reduce) {
  .image-slot--hero img.is-armed {
    transition: none;
  }
}

.image-slot--square {
  aspect-ratio: 1 / 1;
}

.image-slot--landscape {
  aspect-ratio: 4 / 3;
}

.image-slot--wide {
  aspect-ratio: 16 / 9;
}

.image-slot--hero {
  /* Matches the hero photo's own native ratio (1400x1050) exactly, at
     every width, so object-fit:cover never has anything to crop — the
     full photo always shows. A taller mobile-only ratio used to crop
     people off the edges of the group photo. */
  aspect-ratio: 4 / 3;
}

/* A row of small photographs with no individual captions — used once, for
   the "part of a worldwide effort" strip on About us, where the point is
   the pattern across all four images, not any single one. */
.image-strip {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}

@media (min-width: 640px) {
  .image-strip {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* --------------------------------------------------------------------------
   12. Portrait ring
   -------------------------------------------------------------------------- */

.portrait-ring {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  display: block;
  border-radius: 50%;
}

.portrait-ring:focus-visible {
  outline: 3px solid var(--deep-teal);
  outline-offset: 6px;
  border-radius: 50%;
}

.portrait-ring__photo {
  position: absolute;
  inset: 6%;
  border-radius: 50%;
  overflow: hidden;
}

.portrait-ring__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.portrait-ring__svg circle {
  fill: none;
  stroke: var(--sunrise-gold);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 339.3;
  stroke-dashoffset: 54;
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
  transition: stroke-dashoffset var(--duration-ring) var(--ease-interact);
}

.portrait-ring:hover .portrait-ring__svg circle,
.portrait-ring:focus-visible .portrait-ring__svg circle {
  stroke-dashoffset: 0;
}

@media (prefers-reduced-motion: reduce) {
  .portrait-ring__svg circle {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   13. Cards, dividers, pull quotes
   -------------------------------------------------------------------------- */

.card {
  background: var(--white);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.card h3 {
  font-size: var(--text-lg);
}

.arc-divider {
  display: block;
  width: 100%;
  height: clamp(40px, 8vw, 90px);
  color: var(--sunrise-gold);
}

.pull-quote {
  font-family: var(--font-body);
  font-style: normal;
  font-weight: 600;
  font-size: var(--text-xl);
  color: var(--deep-teal);
  line-height: 1.35;
  max-width: 38rem;
}

.pull-quote cite {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--font-display);
  font-style: normal;
  font-weight: 500;
  font-size: var(--text-sm);
  color: var(--ink);
}

/* Decorative leaf vines in the side margins of the "name" pull-quote
   section — light turquoise, never overlapping the text. Only shown once
   there's a wide enough gutter beyond the 1140px content column to fully
   clear it (at 1440px viewport that's ~110px clear on each side, more
   than the 84px-wide graphic needs) — below that width there's no safe
   gap to put them in, so they simply don't render rather than risk
   crowding the text. main.js section 10 nudges them a little closer to
   the text as the section scrolls through view, for a slow, elegant
   drift rather than a static graphic; without JS (or with reduced
   motion) they just sit still at their resting position. */
.name-section {
  position: relative;
}

.name-section .container {
  position: relative;
  z-index: 1;
}

.name-section__leaves {
  display: none;
  position: absolute;
  top: 50%;
  width: 84px;
  height: 560px;
  transform: translateY(-50%);
  color: var(--bright-teal);
  opacity: 0.32;
  pointer-events: none;
  z-index: 0;
}

@media (min-width: 1440px) {
  .name-section__leaves {
    display: block;
  }
}

.name-section__leaves--left {
  left: 0;
}

.name-section__leaves--right {
  right: 0;
}

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.tag {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--text-2xs);
  letter-spacing: 0.04em;
  padding: 0.4em 0.9em;
  border-radius: var(--radius-full);
  background: var(--teal-wash-08);
  color: var(--deep-teal);
}

/* Collaborator cards — a name-and-profession grid designed to look
   finished with no photographs at all. Each card carries a lettered
   avatar instead of a portrait ring: a real headshot can replace it later
   by swapping the initials markup for an <img> in the same slot, without
   touching the rest of the card. */
.grid-collaborators {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}

@media (min-width: 640px) {
  .grid-collaborators {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1000px) {
  .grid-collaborators {
    grid-template-columns: repeat(3, 1fr);
  }
}

.collaborator-card {
  background: var(--white);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  box-shadow: var(--shadow-card);
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  /* No min-height here: .grid-collaborators is a plain CSS grid with the
     default align-items: stretch, so every card in a row already matches
     its tallest sibling automatically — a fixed pixel floor would only
     ever fight that, never improve on it. */
}

.collaborator-card__avatar {
  flex: none;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--paper);
  color: var(--deep-teal);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-sm);
}

.collaborator-card__name {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--deep-teal);
}

.collaborator-card__field {
  font-size: var(--text-sm);
  margin-top: 0.15em;
}

.collaborator-card__bio {
  font-size: var(--text-sm);
  color: var(--ink-muted);
  margin-top: 0.5em;
}

/* Retail partner logos — kept modest and greyscale on purpose: this reads
   as "we work with these people," not as an endorsement strip. Restoring
   full colour later is a one-line CSS change, no new files needed. */
.partner-logos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-5);
  margin-top: var(--space-4);
}

.partner-logos img {
  height: 24px;
  width: auto;
  filter: grayscale(100%);
  opacity: 0.7;
  transition: opacity var(--duration-interact) var(--ease-interact),
    filter var(--duration-interact) var(--ease-interact);
}

.partner-logos img:hover,
.partner-logos img:focus-visible {
  filter: grayscale(0%);
  opacity: 1;
}

/* --------------------------------------------------------------------------
   14. Forms
   -------------------------------------------------------------------------- */

.form-field {
  display: flex;
  flex-direction: column;
  gap: 0.5em;
  margin-bottom: var(--space-3);
}

.form-field label {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--text-sm);
  color: var(--deep-teal);
}

.form-field input,
.form-field textarea {
  background: var(--white);
  border: 1.5px solid var(--teal-wash-12);
  border-radius: var(--radius-sm);
  padding: 0.85em 1em;
  font-family: var(--font-body);
  font-size: var(--text-base);
  min-height: 44px;
}

.form-field textarea {
  min-height: 140px;
  resize: vertical;
}

.form-field input:focus-visible,
.form-field textarea:focus-visible {
  outline: 3px solid var(--deep-teal);
  outline-offset: 1px;
  border-color: var(--deep-teal);
}

.form-field--error input,
.form-field--error textarea {
  /* Deliberately not a brand colour — see the --error token's definition. */
  border-color: var(--error);
}

.form-error {
  color: var(--error);
  font-size: var(--text-sm);
}

.form-hint {
  font-size: var(--text-2xs);
  color: var(--ink-muted);
}

.form-status {
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  margin-bottom: var(--space-3);
}

.form-status--success {
  /* leaf-green wash. Composited over white this is #e3efd7; the text
     colour measures 8.39:1 against that, re-checked under the new palette. */
  background: rgba(78, 155, 5, 0.16);
  color: #274a29;
}

.form-status--error {
  /* amber wash. Composited over white this is #fef1de; the text colour
     measures 8.33:1 against that, re-checked under the new palette. */
  background: rgba(247, 153, 17, 0.14);
  color: #7a3018;
}

.form-status[hidden] {
  display: none;
}

/* honeypot field: hidden from sighted and keyboard users, but present in
   the DOM for bots that fill in every field they can find */
.form-field--trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   15. Accessibility helpers
   -------------------------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: var(--space-2);
  top: -60px;
  background: var(--deep-teal);
  color: var(--white);
  padding: 0.85em 1.4em;
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--text-sm);
  z-index: 100;
  transition: top var(--duration-interact) var(--ease-interact);
}

.skip-link:focus {
  top: var(--space-2);
}

:focus-visible {
  outline: 3px solid var(--deep-teal);
  outline-offset: 2px;
}

.section--teal :focus-visible,
.site-footer :focus-visible {
  outline-color: var(--sunrise-gold);
}

a:focus:not(:focus-visible),
button:focus:not(:focus-visible) {
  outline: none;
}

/* --------------------------------------------------------------------------
   16. Rolling gallery (Home page's auto-scrolling photo/video strip,
       plus the click-to-enlarge lightbox)
   -------------------------------------------------------------------------- */

/* Deliberately full-bleed (no .container wrapper in the markup) — a
   gallery strip reads as more cinematic running edge-to-edge than boxed
   inside the page's usual measure. */
.rolling-gallery {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
}

.rolling-gallery__track {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: max-content;
  padding-inline: var(--space-4);
}

.rolling-gallery__item {
  position: relative;
  flex: none;
  height: 320px;
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-card);
  background: var(--paper);
  cursor: pointer;
  transition: transform var(--duration-interact) var(--ease-interact);
}

.rolling-gallery__item:hover,
.rolling-gallery__item:focus-visible {
  transform: scale(1.03);
}

.rolling-gallery__item picture,
.rolling-gallery__item img,
.rolling-gallery__item video {
  position: relative;
  z-index: 1;
  display: block;
  height: 100%;
  width: auto;
  object-fit: cover;
  pointer-events: none;
}

/* Default state: fully visible, same as .reveal above — main.js only adds
   --armed once it's also set up the load-tracking that will clear it, so a
   script failure (or no JS at all) never leaves a tile permanently blank. */
.rolling-gallery__item--armed:not(.is-loaded)::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(
    100deg,
    var(--paper) 35%,
    var(--teal-wash-12) 50%,
    var(--paper) 65%
  );
  background-size: 200% 100%;
  animation: rolling-gallery-shimmer 1.6s ease-in-out infinite;
}

@keyframes rolling-gallery-shimmer {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}

.rolling-gallery__item--armed picture,
.rolling-gallery__item--armed img,
.rolling-gallery__item--armed video {
  opacity: 0;
  transition: opacity var(--duration-entrance) var(--ease-entrance);
}

.rolling-gallery__item--armed.is-loaded picture,
.rolling-gallery__item--armed.is-loaded img,
.rolling-gallery__item--armed.is-loaded video {
  opacity: 1;
}

/* main.js section 11 clones the track and adds this class once the clone
   and the animation are both actually ready — so a visitor without
   JavaScript keeps a plain, perfectly usable, manually-scrollable strip
   (the default state above) instead of a half-built animation. */
.rolling-gallery--animated {
  overflow: hidden;
}

.rolling-gallery--animated .rolling-gallery__track {
  animation: rolling-gallery-scroll linear infinite;
}

.rolling-gallery--animated:hover .rolling-gallery__track,
.rolling-gallery--animated.rolling-gallery--paused .rolling-gallery__track {
  animation-play-state: paused;
}

@keyframes rolling-gallery-scroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .rolling-gallery--animated .rolling-gallery__track {
    animation: none;
    transform: none;
  }
  .rolling-gallery--animated {
    overflow-x: auto;
  }
  .rolling-gallery__item--armed:not(.is-loaded)::before {
    animation: none;
  }
  .rolling-gallery__item--armed picture,
  .rolling-gallery__item--armed img,
  .rolling-gallery__item--armed video {
    transition: none;
  }
}

/* Click-to-enlarge lightbox: a darkened backdrop with just the enlarged
   photo/video, a close button anchored to the content's own corner (not
   the screen edge, so it stays right next to whatever's actually being
   looked at), and previous/next arrows — deliberately no player controls
   on the video, since the point is a closer look, not a scrubber. Fades
   in/out rather than appearing instantly; skipped under reduced motion. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: rgba(4, 20, 22, 0.92);
  opacity: 0;
  transition: opacity var(--duration-interact-fast) var(--ease-interact);
}

.lightbox[hidden] {
  display: none;
}

.lightbox.is-visible {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .lightbox {
    transition: none;
  }
}

.lightbox__content {
  position: relative;
  max-width: min(92vw, 900px);
  max-height: 88vh;
}

.lightbox__content img,
.lightbox__content video {
  position: relative;
  z-index: 0;
  display: block;
  max-width: 100%;
  max-height: 88vh;
  width: auto;
  height: auto;
  margin-inline: auto;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lift);
}

/* A soft darkening at the corner/edges the controls sit on, fading out
   toward the centre — gives the close/prev/next icons something to read
   against on a light photo, without a hard-edged shape breaking up the
   image itself. */
.lightbox__content::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  border-radius: var(--radius-md);
  background: radial-gradient(
      ellipse 220px 160px at top right,
      rgba(0, 0, 0, 0.6) 0%,
      transparent 70%
    ),
    linear-gradient(to right, rgba(0, 0, 0, 0.45) 0%, transparent 18%),
    linear-gradient(to left, rgba(0, 0, 0, 0.45) 0%, transparent 18%);
}

/* No visible "chip" behind these — a plain icon with a soft dark
   drop-shadow reads clearly against any photo or video underneath it,
   without a circular background competing with the image itself. */
.lightbox__close,
.lightbox__nav {
  position: absolute;
  z-index: 2;
  border: none;
  background: none;
  padding: var(--space-2);
  color: var(--white);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.7))
    drop-shadow(0 0 10px rgba(0, 0, 0, 0.4));
  opacity: 0.9;
  transition: opacity var(--duration-interact) var(--ease-interact),
    transform var(--duration-interact) var(--ease-interact);
}

.lightbox__close:hover,
.lightbox__close:focus-visible,
.lightbox__nav:hover,
.lightbox__nav:focus-visible {
  opacity: 1;
  transform: scale(1.1);
}

.lightbox__close {
  top: 4px;
  right: 4px;
}

.lightbox__close svg {
  width: 22px;
  height: 22px;
}

.lightbox__nav {
  top: 50%;
  transform: translateY(-50%);
}

.lightbox__nav:hover,
.lightbox__nav:focus-visible {
  transform: translateY(-50%) scale(1.1);
}

.lightbox__nav svg {
  width: 28px;
  height: 28px;
}

.lightbox__nav--prev {
  left: 4px;
}

.lightbox__nav--next {
  right: 4px;
}

@media (max-width: 639.98px) {
  .rolling-gallery__item {
    height: 220px;
  }
}

/* --------------------------------------------------------------------------
   17. 404 page
   -------------------------------------------------------------------------- */

.error-page {
  min-height: 60vh;
  display: flex;
  align-items: center;
}

.error-page__code {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: clamp(4rem, 16vw, 8rem);
  /* Raw sunrise-gold on white measures 1.60:1 — fails even the "large
     text" 3:1 threshold at any size. This deeper amber measures 4.18:1. */
  color: #b36b0c;
  line-height: 1;
}

/* --------------------------------------------------------------------------
   18. Responsive breakpoints
   -------------------------------------------------------------------------- */

.hide-until-tablet {
  display: none;
}

@media (min-width: 640px) {
  .hide-until-tablet {
    display: initial;
  }
}

@media (min-width: 1200px) {
  .container {
    padding-inline: 2.5rem;
  }
}
