/* ============================================================
   TEMPLATE STYLES.CSS
   Sunny Web Shops — starter template for new client builds
   Mobile-first, ADA compliant, no frameworks
 
   FONT LOADING — paste in every HTML <head>:
   <link rel="preconnect" href="https://fonts.googleapis.com">
   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
   <link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,500;1,400;1,500&family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400&family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet">
 
   ARCHITECTURE
   Typographic styling lives inside named section contexts —
   not on bare HTML elements globally. The .content block owns
   all prose styling (headings, paragraphs, lists, links, tables,
   code, figures, hr, mark, abbr, etc.) via cascade from the
   container class. New contexts (.content--blog, .content--landing,
   etc.) inherit from .content and override only what changes.
 
   Chrome elements (header, footer, nav, buttons, forms) are
   styled entirely through their own BEM selectors and do not
   depend on global element rules.
 
   Only the absolute minimum lives globally: the reset, body/html
   base, inline semantics (strong/em/small), a link baseline for
   chrome, and the focus ring.
 
   TABLE OF CONTENTS
   1.  Project Variables      ← Edit these per project
   2.  System Variables       ← Do not edit
   3.  Reset & Base           ← Minimal — chrome-level only
   4.  Chrome: Navigation
   5.  Chrome: Footer
   6.  Chrome: Buttons
   7.  Chrome: Forms
   8.  Chrome: Accessibility & Utilities
   9.  Layout & Grid          ← .container · .section--flush · three-col
   10. Spacing Utilities
   11. Color Utilities
   12. Animation
   13. Print
   14. Content Block          ← All prose typography lives here
   15. Content Block Variants ← .content--card + stub for new ones
   16. Cards                  ← Grid layouts for card components
   17. Hero Image Components  ← .img-hero · .hero-text
   18. Slideshow              ← .slideshow · arrows · play/pause
   19. Lightbox               ← .lightbox-grid · .lightbox overlay
   20. Cookie Notice          ← .cookie-notice · fixed bottom bar
   21. FAQ Accordion          ← .faq · .faq__item · .faq__answer
   22. Full-Bleed Image       ← .img-bleed · no container · natural height
   23. CTA Block              ← .cta · centered · cascades h2 + p
   24. Testimonials           ← .testimonials · .testimonial-item · 3-up
   25. Contact Form Section   ← .contact-form · column-constrained
   26. Cards: 1-up            ← .cards__grid--one · column-constrained
   27. TOC Overrides          ← .toc · .toc__nav · .toc__link · .toc__top
   28. Homepage Hero          ← .hero-home · full-bleed, no header
   29. Archive Card Grid      ← .archive-grid/.archive-card · Events & Slideshows landing pages
 
   THE THREE HTML FILES
   long-form.html      — the master page template. Every client
                          page starts here: header, footer, hero,
                          .section.content > .container > .three-col
                          > .three-col__mid > .main-content (prose).
                          .three-col__left/.content-left and
                          .three-col__right/.content-right are empty
                          by default.
 
   long-form-toc.html  — same as long-form.html, with a <aside class
                          ="toc"> populated in .content-left. Use
                          only when a page needs an in-page table of
                          contents in the left column; otherwise use
                          long-form.html.
 
   components.html     — library of additional section-level
                          components (§1–§14 above), each a
                          self-contained <section>. To build a page,
                          pull whichever sections a wireframe calls
                          for and insert them as siblings of
                          .section.content in long-form.html (or
                          long-form-toc.html).
   ============================================================ */
 
 
/* ============================================================
   1. PROJECT VARIABLES
   These are the only values that change from project to project.
   ============================================================ */
 
:root {
 
  /* Accent — saturated blue for links, CTAs, active states.
     Distinct from --color-heading so links are unmistakably links. */
  --accent:               #1A6FAD;
  --accent-hover:         #135585;
 
  /* Fonts                                                     */
  --font-heading:         'Lora', Georgia, serif;
  --font-body:            'DM Sans', Helvetica Neue, Arial, sans-serif;
 
  /* Backgrounds & Surfaces                                    */
  --color-page:           #FFFFFF;
  --color-surface:        #F7F5F2;
  --color-surface-mid:    #EDEBE8;
 
  /* Borders & Dividers                                        */
  --color-border:         #EDEBE8;
  --color-border-strong:  #C8C4BF;
 
  /* Text
     --color-heading is desaturated blue-gray — headings only.
     Distinct from --accent so headings and links never confuse. */
  --color-heading:        #39566C;
  --color-text:           #1A1A1A;
  --color-text-secondary: #4A4A4A;
  --color-text-muted:     #8A8A8A;
}
 
 
/* ============================================================
   2. SYSTEM VARIABLES
   Architecture. Do not edit between projects.
   ============================================================ */
 
:root {
 
  /* --- Spacing Scale (base unit: 8px) ---                   */
  --space-half:   4px;
  --space-1:      8px;
  --space-2:     16px;
  --space-3:     24px;
  --space-4:     32px;
  --space-5:     48px;
  --space-6:     64px;
  --space-7:     96px;
  --space-8:    128px;
 
  /* --- Typography Scale ---
     Reference values used by chrome BEM components.
     Prose typography uses --ct- variables (Section 14).      */
  --text-caption:      14px;
  --text-body:         16px;
  --text-lead:         18px;
  --text-h3:           22px;
  --text-h2:           28px;
  --text-h1-desktop:   56px;
 
  /* --- Line Heights ---                                      */
  --leading-body:      1.6;
  --leading-heading:   1.1;
  --leading-caption:   1.4;
 
  /* --- Font Weights ---                                      */
  --weight-regular:    400;
  --weight-medium:     500;
  --weight-bold:       700;
 
  /* --- Column & Layout ---                                   */
  --column-width:      720px;
  --column-width-wide: 800px;
  --container-max:    1200px;
  --container-wide:   1440px;
  --page-margin:       16px;
  --page-margin-tablet:32px;
 
  /* --- Breakpoints (reference only)
     Mobile:  0–859px  |  Desktop: 860–1199px
     Wide:    1200–1439px  |  Full: 1440px+               */
 
  /* --- Border Radius ---                                     */
  --radius-sm:    3px;
  --radius:       6px;
  --radius-lg:   12px;
 
  /* --- Transitions ---                                       */
  --transition-fast:   150ms ease;
  --transition-base:   250ms ease;
  --transition-slow:   400ms ease;
 
  /* --- Functional Colors ---                                 */
  --color-error:       #C0392B;
  --color-success:     #27AE60;
  --color-warning:     #E67E22;
  --color-focus:       #0066CC;
 
  /* --- Shadows ---                                           */
  --shadow-sm:   0 1px 3px rgba(0,0,0,0.08);
  --shadow:      0 2px 8px rgba(0,0,0,0.10);
  --shadow-lg:   0 8px 24px rgba(0,0,0,0.12);
  --shadow-xl:   0 16px 48px rgba(0,0,0,0.14);
}
 
 
/* ============================================================
   3. RESET & BASE
   Minimal. Only what chrome elements genuinely need globally.
   Typographic element styling (h1–h3, p, ul, ol, blockquote,
   figure, a full treatment, hr, code, table, mark, abbr) is
   NOT set here — it lives inside named section contexts (§14+).
   ============================================================ */
 
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
 
html {
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
}
 
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  /* overflow-x lives here, not on html — overflow-x: hidden on the
     root element forces html to become its own scroll container,
     which reliably breaks position: sticky on descendants. That's why
     .site-header wasn't staying stuck on real, tall event pages,
     especially once the mega-panel's own scroll-lock also touched
     body's overflow. Same horizontal-overflow protection, applied one
     level down, doesn't have that side effect. */
  overflow-x: hidden;
  background-color: var(--color-page);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: var(--weight-regular);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
 
/* display:flex here (not just flex:1) matters: main growing to fill
   body's leftover space makes ITS OWN height definite, but a plain
   block box doesn't pass that down as a "fill me" context to ITS
   children just because it has a definite height — only an actual
   flex/grid container reliably does. Confirmed via DevTools: main's
   computed height was correct (633px), but a percentage-height child
   of it still resolved to its own content size, not main's height.
   Making main a flex container itself (its children still stack
   vertically exactly like normal block flow, via the default
   align-items:stretch + flex-direction:column) is what lets
   full-bleed sections like .slideshow-hero use flex:1 to genuinely
   fill it. */
main {
  flex: 1;
  display: flex;
  flex-direction: column;
}
 
/* Images never overflow containers                           */
img, video, iframe {
  display: block;
  max-width: 100%;
  height: auto;
}
 
/* Inline semantics — needed everywhere                       */
strong { font-weight: var(--weight-bold); }
em     { font-style: italic; }
small  { font-size: var(--text-caption); }
 
/* Link baseline — chrome links (footer, nav) need this.
   Full typographic treatment is defined per content context. */
a {
  color: var(--accent);
  text-decoration: underline;
  transition: color var(--transition-fast);
}
 
a:hover   { color: var(--accent-hover); }
a:visited { color: var(--accent); }
 
/* Nav links — always strip underline regardless of context   */
nav a { text-decoration: none; }
 
/* HR baseline — just enough for chrome use (form dividers).
   Spacing, color, and margin are set inside content contexts. */
hr {
  border: none;
  border-top: 1px solid var(--color-border);
}
 
/* Focus ring — global, applies to all interactive elements   */
:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 3px;
}
 
a:focus-visible { border-radius: var(--radius-sm); }
 
 
/* ============================================================
   4. CHROME: NAVIGATION
   ============================================================ */
 
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background-color: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  box-shadow: var(--shadow-sm);
  transition:
    border-color var(--transition-base),
    box-shadow var(--transition-base);
}

/* Opt-out for pages where sticky nav costs more viewport than it's
   worth — currently the slideshow-hero pages, where there's very
   little to scroll and the header would otherwise permanently eat
   into the space available to the image. */
.site-header--static {
  position: static;
}
 
.site-header.is-scrolled {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow);
}
 
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-half) var(--page-margin);
  max-width: var(--container-max);
  margin: 0 auto;
  min-height: 48px;
}
 
@media (min-width: 860px) {
  .site-header__inner {
    padding-left: var(--page-margin-tablet);
    padding-right: var(--page-margin-tablet);
  }
}

/* Trimmed a few pixels off the header's minimum height on mobile —
   this affects every page site-wide, not just the slideshows, but
   the request was specifically for a bit more room for the photo on
   slideshow pages, and a slightly shorter header helps there directly
   without needing a slideshow-page-specific override.

   36px still comfortably clears the hamburger button's own fixed
   32px size (with a little room to spare) — the floor below which
   this can't shrink further without the button itself getting
   cramped or clipped. */
@media (max-width: 859px) {
  .site-header__inner {
    min-height: 36px;
  }
}
 
.site-logo {
  display: block;
  text-decoration: none;
  flex-shrink: 0;
}

/* Replaces what used to be an inline style
   (style="font-family:'Lora',serif; font-size:24px;
   color:var(--color-heading); letter-spacing:-0.01em;") repeated
   identically in every page's HTML — a leftover from before the
   cascade-only styling rule was locked in, and never brought in
   line with it. Moved here so it can be overridden by a normal,
   !important-free media query below, instead of needing a special
   exception to reach past an inline style. */
.site-logo__text {
  font-family: 'Lora', serif;
  font-size: 24px;
  color: var(--color-heading);
  letter-spacing: -0.01em;
}

@media (max-width: 859px) {
  .site-logo__text {
    font-size: var(--text-lead);
  }
}

/* Compact mobile header — slideshow category pages only (opt-in via
   .site-header--slideshow, added per-page). Shows a short category
   title (e.g. "Snow") centered between the wordmark and the
   hamburger, so a visitor arriving cold via a direct link still gets
   oriented at a glance even with the in-page title/tagline hidden to
   save vertical space. Desktop is untouched — this only activates at
   the same width where the full nav already collapses to the
   hamburger. Title text is set from JS (see the slideshow script),
   synced from the same h1 the rest of the page already uses as its
   one source of truth — this span's own text is just a fallback. */
.site-header__title {
  display: none;
}

@media (max-width: 859px), (max-height: 500px) {
  .site-header--slideshow .site-header__inner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    /* Grid's default align-items is stretch, not center — the old
       flex layout had vertical centering for free, but switching to
       grid for this 3-column layout silently lost it. Using
       baseline (not center) specifically: the wordmark (Lora, larger)
       and the compact title (DM Sans, small caption size) have
       different font metrics, so centering their BOXES doesn't
       guarantee their actual text baselines line up — box-centering
       two different type sizes can still look visually "off" from
       each other even though each is technically centered in its own
       cell. Baseline alignment is the standard fix for exactly this
       logo+label pairing pattern. */
    align-items: baseline;
  }

  /* The desktop nav wrapper is already empty at narrow widths (its
     .site-nav ul is display:none — see below), so it's hidden
     outright here rather than left as an invisible grid cell. The
     real mobile navigation is the separate .mobile-nav panel the
     hamburger opens, untouched by any of this. */
  .site-header--slideshow .site-header__inner > nav {
    display: none;
  }

  .site-header--slideshow .site-logo {
    justify-self: start;
  }

  .site-header--slideshow .site-header__title {
    display: block;
    justify-self: center;
    font-family: 'DM Sans', sans-serif;
    font-size: var(--text-caption);
    font-weight: var(--weight-medium);
    color: var(--color-text);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    white-space: nowrap;
  }

  .site-header--slideshow .hamburger {
    justify-self: end;
    /* The header switched to align-items:baseline for the wordmark/
       title text pairing above — a non-text element like this button
       has no real baseline, so it would default to aligning by its
       bottom edge instead. Keeping it explicitly centered regardless
       of that. */
    align-self: center;
    /* 24x24 is the actual WCAG 2.5.5 AA minimum tap-target size —
       not just "smaller," this is the real floor below which it
       shouldn't shrink further on a touch device. */
    width: 24px;
    height: 24px;
  }

  /* Thicker but lighter — matching the wordmark's color
     (--color-heading, a muted slate blue) instead of the sitewide
     --color-text (near-black), which read as too dark/heavy against
     this compact header. Line thickness and the is-open "X" offset
     below are both scaled down again to match the smaller 24px box —
     worth a visual check once built. */
  .site-header--slideshow .hamburger__line {
    height: 2.5px;
    background-color: var(--color-heading);
  }

  .site-header--slideshow .hamburger.is-open .hamburger__line:nth-child(1) {
    transform: translateY(5px) rotate(45deg);
  }

  .site-header--slideshow .hamburger.is-open .hamburger__line:nth-child(3) {
    transform: translateY(-5px) rotate(-45deg);
  }

  /* Trimmed further to match the 24px hamburger (plus a small amount
     of breathing room) — this is now close to the real floor for this
     compact header, not just an arbitrary smaller number. */
  .site-header--slideshow .site-header__inner {
    min-height: 28px;
  }
}

/* A wide-but-short viewport (e.g. Galaxy A51 landscape, 914×412) is
   >=860px wide, so it clears the normal "show the full desktop nav"
   width threshold — meaning the hamburger button would otherwise stay
   hidden there too (see the min-width:860px rule further up that
   hides it), even though the block above has already hidden the full
   nav for being short on height. Without this, a short-but-wide
   device would end up with NEITHER the full nav NOR the hamburger
   visible — no way to reach navigation at all. This selector's two
   classes beat that rule's one on specificity, so no !important
   needed. Scoped to slideshow pages only, same as everything else
   here — this doesn't touch the hamburger's behavior anywhere else
   on the site. */
@media (max-height: 500px) {
  .site-header--slideshow .hamburger {
    display: flex;
  }
}

/* Same wide-but-short scenario as above, one layer deeper: the
   sitewide rule that keeps .mobile-nav permanently closed on any
   >=860px-wide screen (further down this file) uses !important,
   deliberately, so the panel can never be reopened by accident on a
   real desktop. A wide-but-short slideshow page legitimately needs
   that panel to actually open now that it's the only way to reach
   navigation there — matching the one other documented !important
   exception already in this file (the desktop image max-height
   override). Scoped tightly: only affects display, only when the
   panel is actually .is-open, only on slideshow pages, only on short
   viewports — a real desktop visit is completely unaffected. */
@media (max-height: 500px) {
  .slideshow-page .mobile-nav.is-open {
    display: flex !important;
  }
}

/* .slideshow__side--next is the ONE grid item in column 3 — both the
   arrow and the pause button live inside it (see the HTML
   restructure), not as separate grid items competing for the same
   cell. Applies at EVERY viewport size, desktop included — the
   previous version only had this scoped to the mobile media query,
   which meant desktop (where the HTML structure is identical, since
   markup doesn't have media queries) had no styling for this wrapper
   at all, and the button/arrow just fell into plain document flow.

   align-self:stretch makes THIS item (only) fill the row's full
   height — matching the photo's own rendered height exactly, since
   the photo is the tallest content establishing that row's size. The
   outer .slideshow grid's align-items:center (used for the other
   items) is overridden just for this one, on purpose.

   32px width (narrowed from an earlier 44px) — just enough for the
   20px icons with a little breathing room, freeing real width back to
   the photo on narrow phones, where every gutter pixel is width the
   photo doesn't get. Matched on .slideshow__btn--prev below so both
   columns are the same size — two differently-sized columns can never
   produce symmetric gutters, no matter how well-centered each one's
   own content is. */
.slideshow-page .slideshow__btn--prev {
  width: 32px;
  /* Leftover from the old text-glyph design — padding-right:30px
     (still active, unscoped, from further down this file) was meant
     to push an unconstrained-width button away from the column edge.
     Combined with this NEW fixed 32px width, it left almost no room
     for the actual 20px icon — squishing it down to nearly nothing,
     which is why the left arrow was effectively invisible on real
     desktop widths (an older mobile-only override was accidentally
     covering for this at narrower widths, which is why it only
     showed up here). Not needed anymore either way — the wrapper's
     own width and centering now handle spacing entirely. */
  padding-right: 0;
}

.slideshow-page .slideshow__side--next {
  grid-column: 3;
  align-self: stretch;
  position: relative;
  width: 32px;
}

/* Arrow centered against the FULL column height — top:50% + a
   transform is what guarantees this matches the left arrow's position
   exactly (centered via the outer grid's align-items:center across
   that same full row height). Any flexbox approach sharing space with
   the pause button below would center the arrow against a SMALLER
   remaining height once the pause button's own space was subtracted —
   visually close, but measurably not the same center as the left
   arrow. Two independent reference points need two independent
   positioning rules, not one technique doing double duty. */
.slideshow-page .slideshow__side--next .slideshow__btn--next {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* Same leftover as the prev button (see the comment on that rule)
     — padding-left:30px (unscoped, active from further down this
     file) made this button's own box asymmetric. Since it's centered
     via a transform that centers the WHOLE box, padding included,
     that asymmetry was quietly dragging the visible icon off from
     the true center — exactly the desktop-only misalignment you
     spotted in the closeup. */
  padding-left: 0;
}

/* Pause button sits flush on the photo's own baseline — bottom:0
   with NO padding pushing it up off that edge (an earlier version had
   padding-bottom here, which is exactly what was floating it slightly
   above the baseline instead of resting on it). Sized down (smaller
   icon, tighter padding) to sit closer in scale to the arrows above
   it, rather than reading as a separate, larger control. */
.slideshow-page .slideshow__footer {
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
}

.slideshow-page .slideshow__playpause {
  background: none;
  color: rgba(255, 255, 255, 0.6);
  padding: 2px 4px;
}

.slideshow-page .slideshow__btn svg {
  display: block;
}

.slideshow-page .slideshow__playpause svg {
  /* Inline elements (SVG included) reserve a few pixels below
     themselves for text baseline/descender space — the classic
     "mystery gap under an image" CSS behavior. display:block removes
     that reserved space, which is very likely what was floating the
     icon a few pixels above the photo's true bottom edge even though
     the button itself was correctly positioned at bottom:0. */
  display: block;
  width: 16px;
  height: 16px;
}

.slideshow-page .slideshow__playpause:hover {
  background: none;
  color: var(--color-page);
}

/* Single source of truth for the photo's height ceiling on this page
   — both the landscape/short-viewport rule and the portrait override
   read this same value, rather than each hardcoding its own copy of
   the number where they could silently drift apart after a future
   tweak. Change it once, here, and both rules follow. */
.slideshow-page {
  --slideshow-photo-max-height: 84dvh;
}

/* Footer hidden on mobile — slideshow category pages only (opt-in
   via .slideshow-page on <body>, added per-page). Frees a whole
   fixed-height region from the photo's available space, moving this
   view closer to the lightbox's immersive feel. The footer's real
   job (copyright + Accessibility link) still lives on every other
   page and on this same page's desktop view — nothing sitewide is
   actually losing it, just this one specific mobile view. */
@media (max-width: 859px), (max-height: 500px) {
  .slideshow-page .site-footer {
    display: none;
  }

  /* The compact header's title now does the "what am I looking at"
     job for a cold arrival — this in-page title/tagline block would
     just be a redundant echo of it below the header, at the exact
     moment space is tightest. Hides the WHOLE header block (not just
     shrinking it further) — the tagline inside it is already
     display:none from the rule above, so this only has the h1 left
     to remove anyway. Scoped to .slideshow-page, so the other 11
     category pages (not yet migrated to the compact header) are
     unaffected and keep their own in-page title exactly as before. */
  .slideshow-page .slideshow-hero__header {
    display: none;
  }

  /* Overrides the general 78dvh cap (further up this file) — that
     value was calibrated for the OLD chrome overhead: full title
     block + full footer, both real content at the time. With both
     gone on this page (compact header title, footer hidden), the
     actual fixed overhead shrank, freeing up real room — but 92dvh
     (tried first) overshot badly and clipped the photo/hid the pause
     button entirely. Recalculating properly this time instead of
     guessing again: header (~28px) + .slideshow-hero's own top/bottom
     padding (~24px) + the pause-button row (~32px) ≈ 84px total fixed
     overhead, which on a ~375px real short viewport works out to
     roughly 78-80% available for the photo — much closer to the
     original 78dvh than the 92dvh guess was. Still an estimate, not
     a measured value — test and report which direction to nudge. */
  .slideshow-page .slideshow__track img {
    max-height: var(--slideshow-photo-max-height);
  }

  /* Both .slideshow-hero__header (hidden above) and .site-footer
     (hidden below) used to occupy real space that this padding was
     calibrated around — .site-footer especially, since var(--space-5)
     bottom padding was originally sized to give the visible footer
     room to breathe underneath the photo. With both gone entirely on
     this page, that same padding just reads as extra unused space
     rather than deliberate breathing room. Tightened to real,
     existing space tokens (not new arbitrary values) on both ends. */
  .slideshow-page .slideshow-hero {
    padding-top: var(--space-1);
    padding-bottom: var(--space-2);
  }
}
 
.site-logo img {
  height: 40px;
  width: auto;
}
 
.site-nav {
  display: none;
  list-style: none;
  padding: 0;
  margin: 0;
}
 
@media (min-width: 860px) {
  .site-header__inner > nav {
    align-self: stretch;
  }

  .site-nav {
    height: 100%;
    display: flex;
    align-items: center;
    gap: var(--space-1);
  }
 
  .site-nav a {
    font-size: var(--text-caption);
    font-weight: var(--weight-medium);
    color: var(--color-text);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 4px 6px;
    border-radius: var(--radius-sm);
    border-bottom: 2px solid transparent;
    transition:
      color var(--transition-fast),
      background-color var(--transition-fast),
      border-color var(--transition-fast);
  }
 
  .site-nav a:hover {
    color: var(--accent);
    background-color: var(--color-surface);
  }
 
  .site-nav a.is-active,
  .site-nav a[aria-current="page"] {
    color: var(--color-text);
    border-bottom: 1px solid var(--color-border-strong);
  }
 
  .site-nav a.nav-special {
    color: var(--accent);
    font-weight: var(--weight-bold);
    border: 1.5px solid var(--accent);
    border-radius: var(--radius-sm);
    padding: 3px 8px;
  }
 
  .site-nav a.nav-special:hover {
    background-color: var(--accent);
    color: #fff;
  }
 
  .site-nav a.nav-special.is-active,
  .site-nav a.nav-special[aria-current="page"] {
    background-color: var(--accent);
    color: #fff;
    border-color: var(--accent);
  }
}
 
.hamburger {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 32px;
  height: 32px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  border-radius: var(--radius-sm);
}
 
.hamburger:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 3px;
}
 
.hamburger__line {
  display: block;
  width: 100%;
  height: 2px;
  background-color: var(--color-text);
  transition:
    transform var(--transition-base),
    opacity var(--transition-base);
  transform-origin: center;
}
 
.hamburger.is-open .hamburger__line:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.hamburger.is-open .hamburger__line:nth-child(2) { opacity: 0; }
.hamburger.is-open .hamburger__line:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
 
@media (min-width: 860px) { .hamburger { display: none; } }
 
.mobile-nav {
  display: none;
  position: fixed;
  inset: 0;
  top: 64px;
  z-index: 200;
  background-color: var(--color-surface);
  padding: var(--space-4) var(--page-margin);
  overflow-y: auto;
}

/* The 64px top offset above matches the NORMAL site header's height
   — but the compact slideshow header (built tonight) is much shorter
   at mobile/short widths, down to a ~28px min-height. Without this,
   the panel started well below where that shorter header actually
   ends, leaving a gap where the underlying photo showed through.
   Sibling selector since the header and this panel are siblings in
   the DOM, not nested — matches whenever a .site-header--slideshow
   precedes a .mobile-nav, at any width (harmless on desktop, where
   this panel is hidden outright regardless). Eyeballed close to the
   header's real height — check for a lingering sliver of gap or a
   slight overlap and report which way to nudge it. */
.site-header--slideshow ~ .mobile-nav {
  top: 32px;
}
 
.mobile-nav.is-open {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
 
.mobile-nav > a {
  font-family: var(--font-heading);
  font-size: var(--text-h3);
  color: var(--color-text);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
  display: block;
}
 
.mobile-nav > a:hover       { color: var(--accent); }
.mobile-nav > a.nav-special { color: var(--accent); font-weight: var(--weight-medium); }
 
@media (min-width: 860px) { .mobile-nav { display: none !important; } }

/* Applies to ALL mobile widths (not just short-height landscape) —
   portrait has plenty of vertical room and doesn't NEED this
   tightening, but using the same spacing in both orientations avoids
   a jarring difference when someone rotates their phone. Consistency
   between orientations, not just fitting a constraint. */
@media (max-width: 859px), (max-height: 500px) {
  .mobile-nav {
    padding-top:    var(--space-2);
    padding-bottom: var(--space-2);
    /* A more generous, deliberate left margin — the default
       page-margin (16px, same as everything else site-wide) had the
       links hugging the left edge, which read as unbalanced given how
       much empty space sits unused to the right on a wide landscape
       phone. Left and right are intentionally different here (not a
       symmetric padding value) since the goal is a considered indent
       for a short list of links, not edge-to-edge symmetry the way a
       full paragraph of text would want. */
    padding-left:   var(--space-5);
  }
  .mobile-nav.is-open {
    gap: 4px;
  }
  .mobile-nav > a {
    font-size: var(--text-lead);
    padding-bottom: 4px;
  }
}
 
 
/* ============================================================
   5. CHROME: FOOTER
   ============================================================ */
 
.site-footer {
  background-color: var(--color-surface);
  border-top: 1px solid var(--color-border);
  box-shadow: 0 -3px 10px rgba(0,0,0,0.05);
  padding: var(--space-4) var(--page-margin) var(--space-3);
  margin-top: auto;
  text-align: center;
}

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

.site-footer__brand {
  font-family: var(--font-heading);
  font-size: var(--text-h3);
  font-weight: var(--weight-regular);
  color: var(--color-heading);
  letter-spacing: -0.015em;
  margin-bottom: 4px;
}

.site-footer__tagline {
  font-size: var(--text-body);
  color: var(--color-text-secondary);
  margin: 0;
}

.site-footer__legal {
  margin-top: var(--space-2);
  padding-top: var(--space-1);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-caption);
  /* --color-text-muted measured at 3.17:1 against this footer's
     background — fails WCAG AA's 4.5:1 minimum for normal-size text
     (confirmed via axe-core audit + computed-style contrast math).
     --color-text-secondary is the same fix already applied once
     before to .event-gallery__hint for this identical failure
     pattern — 8.15:1 here, comfortably passing. */
  color: var(--color-text-secondary);
  /* Single centered line now — text-align: center already comes
     from .site-footer, so no flex/stacking machinery needed here
     anymore now that the nav row is gone. */
}

.site-footer__legal a {
  color: var(--color-text-secondary);
  text-decoration: underline;
  text-decoration-color: var(--color-border-strong);
  text-underline-offset: 2px;
  transition: color var(--transition-fast), text-decoration-color var(--transition-fast);
}

.site-footer__legal a:hover {
  color: var(--accent);
  text-decoration-color: var(--accent);
}

/* Same "trim it down" pass already applied to the header — the
   footer's desktop spacing (32px top padding, 22px brand text, 16px
   gap before the legal line) reads as more generous than mobile
   needs. Reuses existing tokens throughout, no new values invented. */
@media (max-width: 859px) {
  .site-footer {
    padding: var(--space-2) var(--page-margin) var(--space-2);
  }

  .site-footer__top {
    margin-bottom: var(--space-half);
  }

  .site-footer__brand {
    font-size: var(--text-lead);
  }

  .site-footer__legal {
    margin-top: var(--space-half);
    padding-top: var(--space-half);
    line-height: var(--leading-caption); /* tighter than the inherited
      body default — matches the same reasoning applied earlier to
      .event-gallery__hint, so a wrapped second line doesn't read with
      extra loose leading */
  }
}

 
 
/* ============================================================
   6. CHROME: BUTTONS
   ============================================================ */
 
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  line-height: 1;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  border: 2px solid transparent;
  cursor: pointer;
  text-decoration: none;
  transition:
    background-color var(--transition-fast),
    border-color var(--transition-fast),
    color var(--transition-fast),
    box-shadow var(--transition-fast);
}
 
.btn:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 3px;
}
 
.btn:visited            { opacity: 1; }
.btn--primary:visited   { color: #FFFFFF; }
.btn--secondary:visited { color: var(--accent); }
.btn--ghost:visited     { color: var(--accent); }
.btn--text:visited      { color: var(--accent); }
 
.btn--primary {
  background-color: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
}
 
.btn--primary:hover {
  background-color: #FFFFFF;
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: 0 4px 12px rgba(26, 111, 173, 0.25);
}
 
.btn--secondary {
  background-color: transparent;
  border-color: var(--accent);
  color: var(--accent);
}
 
.btn--secondary:hover {
  background-color: var(--accent);
  color: #FFFFFF;
}
 
.btn--ghost {
  background-color: transparent;
  border-color: var(--accent);
  color: var(--accent);
}
 
.btn--ghost:hover {
  background-color: var(--accent);
  color: #FFFFFF;
}
 
.btn--text {
  background-color: transparent;
  border-color: transparent;
  color: var(--accent);
  padding-left: 0;
  padding-right: 0;
}
 
.btn--text:hover {
  color: var(--accent-hover);
  text-decoration: underline;
}
 
.btn--sm {
  font-size: var(--text-caption);
  padding: var(--space-1) var(--space-2);
}
 
.btn-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: center;
  margin-top: var(--space-3);
}
 
 
/* ============================================================
   7. CHROME: FORMS (Formspree-ready)
   Required structure:
   <form class="form"
     action="https://formspree.io/f/YOUR_FORM_ID" method="POST">
     <input type="hidden" name="_subject" value="...">
   </form>
   ============================================================ */
 
.form { width: 100%; }
 
.form-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}
 
label {
  font-size: var(--text-caption);
  font-weight: var(--weight-medium);
  color: var(--color-text);
  letter-spacing: 0.03em;
}
 
label .required {
  color: var(--color-error);
  margin-left: 2px;
}
 
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="search"],
input[type="url"],
input[type="password"],
textarea,
select {
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--text-body);
  color: var(--color-text);
  background-color: var(--color-page);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  transition:
    border-color var(--transition-fast),
    box-shadow var(--transition-fast);
  appearance: none;
  -webkit-appearance: none;
}
 
input[type="text"],
input[type="email"],
input[type="tel"],
textarea { max-width: 480px; }
 
input::placeholder,
textarea::placeholder { color: var(--color-text-muted); }
 
input:focus,
textarea:focus,
select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(26, 111, 173, 0.15);
}
 
textarea {
  min-height: 140px;
  resize: vertical;
  line-height: var(--leading-body);
}
 
.form-group--error input,
.form-group--error textarea,
.form-group--error select { border-color: var(--color-error); }
 
.form-group--error input:focus,
.form-group--error textarea:focus,
.form-group--error select:focus {
  box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.15);
}
 
.form-error-message {
  font-size: var(--text-caption);
  color: var(--color-error);
}
 
.form-success {
  background-color: #EBF7F0;
  border: 1px solid var(--color-success);
  border-radius: var(--radius);
  padding: var(--space-3);
  color: #1A5C35;
  font-size: var(--text-body);
  display: none;
}
 
.form-success.is-visible { display: block; }
 
input:disabled,
textarea:disabled,
select:disabled {
  background-color: var(--color-surface);
  color: var(--color-text-muted);
  cursor: not-allowed;
  opacity: 0.7;
}
 
 
/* ============================================================
   8. CHROME: ACCESSIBILITY & UTILITIES
   ============================================================ */
 
.skip-link {
  position: absolute;
  top: -100%;
  left: var(--space-2);
  background-color: var(--accent);
  color: #FFFFFF;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
  z-index: 600;
  transition: top var(--transition-fast);
}
 
.skip-link:focus { top: var(--space-2); }
 
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
 
.back-to-top {
  position: fixed;
  bottom: var(--space-4);
  right: var(--space-4);
  z-index: 50;
  width: 40px;
  height: 40px;
  background-color: var(--accent);
  color: #FFFFFF;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity var(--transition-base),
    box-shadow var(--transition-fast);
  box-shadow: var(--shadow);
}
 
.back-to-top.is-visible    { opacity: 1; pointer-events: auto; }
.back-to-top:hover         { box-shadow: var(--shadow-lg); }
.back-to-top:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 3px;
}
 
/* Text utilities — globally available, applied by hand      */
.text-lead {
  font-size: var(--text-lead);
  line-height: 1.5;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-4);
  max-width: 66ch;
}
 
.text-caption {
  font-size: var(--text-caption);
  line-height: var(--leading-caption);
  color: var(--color-text-secondary);
}
 
.text-byline {
  font-size: var(--text-caption);
  color: var(--color-text-muted);
  font-weight: var(--weight-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
 
@media (forced-colors: active) {
  .btn             { border: 2px solid ButtonText; }
  .hamburger__line { background-color: ButtonText; }
}
 
 
/* ============================================================
   9. LAYOUT & GRID
   Three-tier width hierarchy:
   1. Column-constrained — 720px centered (.content, 2-card)
   2. Container-width    — full container with margins (most blocks)
   3. Full-bleed         — edge to edge (.section--flush)
   ============================================================ */
 
.container {
  width: 100%;
  max-width: var(--container-max);
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--page-margin);
  padding-right: var(--page-margin);
}
 
@media (min-width: 860px) {
  .container {
    padding-left: var(--page-margin-tablet);
    padding-right: var(--page-margin-tablet);
  }
}
 
@media (min-width: 1200px) {
  .container {
    padding-left: 0;
    padding-right: 0;
  }
}
 
.section {
  padding-bottom: var(--space-3);
}
 
@media (min-width: 1200px) {
  .section {
    padding-bottom: var(--space-4);
  }
}
 
.section--surface { background-color: var(--color-surface); }
 
/* .section--flush — zero padding and margin. Use in place of
   .section for components that must sit edge-to-edge or
   directly against an adjacent image (full-width hero,
   full-bleed image). Replaces ad-hoc inline styles. */
.section--flush { padding: 0; margin: 0; }
 
.grid-2 {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
 
@media (min-width: 860px) { .grid-2 { grid-template-columns: repeat(2, 1fr); } }
 
.grid-3 {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
 
@media (min-width: 860px) { .grid-3 { grid-template-columns: repeat(3, 1fr); } }
 
.clearfix::after {
  content: '';
  display: table;
  clear: both;
}
 
/* ── Three-column layout ─────────────────────────────────────
   Purely structural. No padding, no color, no typography.
   Content blocks (.content-left, .main-content, .content-right)
   sit inside each column and own all their own styling.
 
   Mobile stacking order: source order — left, mid, right.
   A TOC in .content-left appears above the main content with
   no order override needed.
   Desktop 1200px+: left | mid | right as a flex row.
 
   Usage:
   <section class="section content">
     <div class="container">
       <div class="three-col">
         <div class="three-col__left">
           <div class="content-left"> ... </div>
         </div>
         <div class="three-col__mid">
           <div class="main-content"> ... </div>
         </div>
         <div class="three-col__right">
           <div class="content-right"> ... </div>
         </div>
       </div>
     </div>
   </section>
   ──────────────────────────────────────────────────────────── */
 
.three-col {
  display: flex;
  flex-direction: column;
}
 
.three-col__left,
.three-col__mid,
.three-col__right {
  width: 100%;
}
 
/* Mobile stacking: source order is left, mid, right — TOC
   appears above the main content with no override needed.   */
 
@media (min-width: 1200px) {
  .three-col {
    flex-direction: row;
    align-items: flex-start;
  }
 
  .three-col__left  { width: 240px; flex-shrink: 0; order: 0; }
  .three-col__mid   { width: 720px; flex-shrink: 0; order: 0; }
  .three-col__right { width: 240px; flex-shrink: 0; order: 0; }
}
 
/* ── Content blocks ──────────────────────────────────────────
   Generic structural wrappers inside each column.
   Width 100% by default — fill the column.
   All styling (padding, background, typography) is applied
   by the specific block or modifier class placed inside.    */
 
.content-left,
.main-content,
.content-right {
  width: 100%;
  max-width: var(--column-width);
  margin-left: auto;
  margin-right: auto;
}
 
/* Sticky sidebar — applied to the column element itself.
   padding-top aligns the TOC with the H1 in the mid column. */
@media (min-width: 1200px) {
  .three-col__left {
    position: sticky;
    top: calc(64px + var(--space-4));
    max-height: calc(100vh - 96px);
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: none;
    align-self: flex-start;
    padding-top: calc(var(--space-5) + var(--space-4));
  }
 
  .three-col__left::-webkit-scrollbar { display: none; }
}
 
/* All TOC rules use .content .toc descendant selectors to beat
   the .content a cascade (border-bottom, padding-bottom, hover
   background). Everything is zeroed and flush left.           */
 
.toc {
  width: 100%;
  font-size: 14px;
  font-family: var(--font-body);
  padding: 0;
  margin: 0;
}
 
.toc__label {
  font-size: 11px;
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-secondary);
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: 0;
  margin: 0 0 var(--space-1) 0;
}
 
.toc__toggle {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 11px;
  font-weight: var(--weight-regular);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  text-decoration: none;
  padding: 0;
  font-family: var(--font-body);
}
 
@media (min-width: 1200px) { .toc__toggle { display: none; } }
 
/* Beat .content ul padding-left and list-style with high specificity */
.toc__nav {
  list-style: none;
  padding: 0;
  margin: 0;
  display: none;
}
.toc__nav.is-open { display: block; }
@media (min-width: 1200px) { .toc__nav { display: block; } }
 
.content .toc__item,
.toc__item { margin: 0; padding: 0; list-style: none; }
 
/* Beat .content a border-bottom, padding-bottom, and hover bg  */
.toc__link,
.content .toc__link,
.content a.toc__link {
  display: block;
  padding: 3px 0 3px 0;
  color: var(--accent);
  text-decoration: none;
  border-bottom: none !important;
  line-height: 1.3;
  transition: color var(--transition-fast);
}
 
.toc__link:hover,
.content .toc__link:hover,
.content a.toc__link:hover {
  color: var(--accent-hover);
  background-color: transparent;
  border-bottom: none !important;
}
 
.toc__link.is-active,
.content .toc__link.is-active {
  color: var(--accent);
  font-weight: var(--weight-medium);
}
 
.toc__top,
.content a.toc__top {
  display: block;
  font-size: 11px;
  color: var(--color-text-muted);
  text-decoration: none;
  border-bottom: none !important;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
 
.toc__top:hover,
.content a.toc__top:hover {
  color: var(--accent);
  background-color: transparent;
}
 
/* Context links sidebar                                      */
.context-links {
  background-color: var(--color-surface);
  border-left: 3px solid var(--accent);
  padding: var(--space-2);
  margin: 0 0 var(--space-3) 0;
}
 
.context-links__label {
  font-size: var(--text-caption);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
}
 
.context-links__list {
  list-style: circle;
  padding-left: var(--space-2);
  margin: 0;
  line-height: 1.3;
}
 
.context-links__list li { margin-bottom: var(--space-half); }
 
.context-links__list a {
  font-size: var(--text-caption);
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
  padding: 2px 4px;
  margin-left: -4px;
  border-radius: var(--radius-sm);
  transition: color var(--transition-fast), background-color var(--transition-fast);
}
 
.context-links__list a:hover {
  color: #FFFFFF;
  background-color: var(--accent);
  text-decoration: none;
}
 
@media (min-width: 1200px) {
  .context-links {
    float: right;
    width: 200px;
    margin-left: var(--space-4);
    margin-right: calc((100vw - var(--column-width)) / -4);
    margin-bottom: var(--space-3);
    margin-top: var(--space-half);
  }
}
 
.section-body::after { content: ''; display: table; clear: both; }
 
 
/* ============================================================
   10. SPACING UTILITIES
   ============================================================ */
 
.mt-1 { margin-top:    var(--space-1); }  .mb-1 { margin-bottom: var(--space-1); }
.mt-2 { margin-top:    var(--space-2); }  .mb-2 { margin-bottom: var(--space-2); }
.mt-3 { margin-top:    var(--space-3); }  .mb-3 { margin-bottom: var(--space-3); }
.mt-4 { margin-top:    var(--space-4); }  .mb-4 { margin-bottom: var(--space-4); }
.mt-5 { margin-top:    var(--space-5); }  .mb-5 { margin-bottom: var(--space-5); }
.mt-6 { margin-top:    var(--space-6); }  .mb-6 { margin-bottom: var(--space-6); }
 
.pt-1 { padding-top:    var(--space-1); }  .pb-1 { padding-bottom: var(--space-1); }
.pt-2 { padding-top:    var(--space-2); }  .pb-2 { padding-bottom: var(--space-2); }
.pt-3 { padding-top:    var(--space-3); }  .pb-3 { padding-bottom: var(--space-3); }
.pt-4 { padding-top:    var(--space-4); }  .pb-4 { padding-bottom: var(--space-4); }
.pt-5 { padding-top:    var(--space-5); }  .pb-5 { padding-bottom: var(--space-5); }
.pt-6 { padding-top:    var(--space-6); }  .pb-6 { padding-bottom: var(--space-6); }
 
 
/* ============================================================
   11. COLOR UTILITIES
   ============================================================ */
 
.bg-page    { background-color: var(--color-page); }
.bg-surface { background-color: var(--color-surface); }
.bg-mid     { background-color: var(--color-surface-mid); }
.bg-accent  { background-color: var(--accent); color: #FFFFFF; }
 
.text-primary   { color: var(--color-text); }
.text-secondary { color: var(--color-text-secondary); }
.text-muted     { color: var(--color-text-muted); }
.text-accent    { color: var(--accent); }
 
 
/* ============================================================
   12. ANIMATION
   ============================================================ */
 
@keyframes slideDown {
  from { opacity: 0; transform: translateY(-16px); }
  to   { opacity: 1; transform: translateY(0); }
}
 
.toast { animation: slideDown var(--transition-base) ease both; }
 
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
 
 
/* ============================================================
   13. PRINT
   ============================================================ */
 
@media print {
 
  *, *::before, *::after {
    background: transparent !important;
    color: #000000 !important;
    box-shadow: none !important;
  }
 
  body { font-size: 12pt; line-height: 1.5; display: block; }
  main { flex: none; }
 
  .site-header, .site-footer, .hamburger, .mobile-nav,
  .btn, .skip-link, .back-to-top { display: none !important; }
 
  h1, h2, h3        { page-break-after: avoid; }
  p, blockquote, li { orphans: 3; widows: 3; }
  img               { max-width: 100% !important; page-break-inside: avoid; }
 
  a[href]::after {
    content: " (" attr(href) ")";
    font-size: 10pt;
    color: #555 !important;
  }
 
  a[href^="#"]::after,
  a[href^="javascript"]::after { content: ""; }
 
  .main-content,
  .content-left,
  .content-right { max-width: 100%; }
 
  .content .figure--left,
  .content .figure--right { float: none; width: 100%; margin: 0 0 24px 0; }
}
 
 
/* ============================================================
   14. CONTENT BLOCK
   The primary typographic environment for page content.
   All element styling cascades from .content — no classes
   on child elements. This is the canonical definition of every
   typographic element: headings, paragraphs, lists, links,
   blockquotes, figures, hr, tables, code, mark, and abbr.
   Variants (Section 15) inherit everything and override
   only what changes.
 
   Usage:
     <section class="section content">
       <div class="container">
         <div class="three-col">
           <div class="three-col__mid">
             <div class="main-content">
               [pure HTML — no classes on children]
             </div>
           </div>
         </div>
       </div>
     </section>
 
   The .content class lives on the outer <section> — the
   typographic cascade below (.content h1, .content p, etc.)
   reaches .main-content's children regardless of the
   .three-col / .three-col__mid wrappers in between, since
   descendant selectors match at any depth. .main-content
   itself owns the 720px constraint (see §9).
 
   Image patterns (class on <figure>, not on <img>):
     Full width:  no class
     Centered:    class="figure--centered"  → 70% of column
     Float left:  class="figure--left"      → 45%, text wraps right
     Float right: class="figure--right"     → 45%, text wraps left
   ============================================================ */
 
/* --- Custom properties ---
   Every typographic decision is a --ct- variable.
   Variants override only the tokens that differ.            */
.content {
  --ct-font-heading:        'Lora', Georgia, serif;
  --ct-font-body:           'DM Sans', Helvetica Neue, Arial, sans-serif;
  --ct-font-mono:           'Roboto Mono', 'Courier New', monospace;
  --ct-weight-heading:      400;
  --ct-weight-body:         400;
  --ct-size-body:           16px;
  --ct-size-lead:           18px;
  --ct-size-h1:             clamp(32px, 7vw, 56px);
  --ct-size-h2:             28px;
  --ct-size-h3:             22px;
  --ct-size-caption:        14px;
  --ct-leading-body:        1.6;
  --ct-leading-heading:     1.1;
  --ct-tracking-h1:         -0.01em;
  --ct-tracking-h2:         0;
  --ct-tracking-h3:         0;
  --ct-color-heading:       var(--color-heading);
  --ct-color-text:          var(--color-text);
  --ct-color-text-sec:      var(--color-text-secondary);
  --ct-color-text-muted:    var(--color-text-muted);
  --ct-color-link:          var(--accent);
  --ct-color-link-hover:    var(--accent-hover);
  --ct-color-link-visited:  var(--accent);
  --ct-color-link-bg-hover: rgba(26, 111, 173, 0.08);  /* muted tint on hover */
  --ct-color-rule:          #AAAAAA;  /* darker than border-strong for visible HR */
  --ct-color-code-bg:       var(--color-surface);
  --ct-color-code-text:     var(--color-heading);
  --ct-color-mark:          #FFF3CD;
  --ct-ul-style:            disc;
  --ct-ol-style:            decimal;
  --ct-list-indent:         32px;
  --ct-list-item-gap:       6px;
  --ct-blockquote-border:   var(--accent);
  --ct-table-border:        var(--color-border-strong);
  --ct-table-stripe:        var(--color-surface);
}
 
/* --- Headings --- */
 
.content h1,
.content h2,
.content h3 {
  font-family:       var(--ct-font-heading);
  font-weight:       var(--ct-weight-heading);
  line-height:       var(--ct-leading-heading);
  color:             var(--ct-color-heading);
  scroll-margin-top: 88px;
}
 
.content h1 {
  font-size:      var(--ct-size-h1);
  letter-spacing: var(--ct-tracking-h1);
  margin-bottom:  24px;
}
 
.content h2 {
  font-size:      var(--ct-size-h2);
  letter-spacing: var(--ct-tracking-h2);
  margin-top:     32px;
  margin-bottom:  24px;
}
 
.content h3 {
  font-size:         var(--ct-size-h3);
  letter-spacing:    var(--ct-tracking-h3);
  margin-top:        32px;
  margin-bottom:     16px;
  scroll-margin-top: 76px;
}
 
/* If a heading is the first element in .main-content, it
   shouldn't carry the top margin meant to separate it from
   preceding prose. (h1 needs no reset — it has no margin-top
   to begin with.) */
.content .main-content > h2:first-child,
.content .main-content > h3:first-child { margin-top: 0; }
 
/* --- Paragraphs --- */
.content p {
  font-family:   var(--ct-font-body);
  font-weight:   var(--ct-weight-body);
  font-size:     var(--ct-size-body);
  line-height:   var(--ct-leading-body);
  color:         var(--ct-color-text);
  margin-bottom: 24px;
  max-width:     68ch;
}

/* Mobile-only tighten for the Events/Slideshows landing-page intro
   specifically — NOT the general .content h1/p rules above, which
   stay exactly as-is everywhere else (About, Contact, any future
   prose page). The selector's precision is what makes this safe:
   `.container > h1` (a DIRECT child) only matches on these two
   landing pages, since they deliberately don't use the .three-col
   wrapper that About/Contact nest their h1 inside — see the
   architecture notes on why. A full prose page naturally invites
   scrolling, so a big H1 there costs nothing real; a card-grid page
   wants to get to the cards quickly, so the same big H1 + tagline +
   generous padding was eating real estate that could show more of
   the actual content above the fold. */
@media (max-width: 859px) {
  .content.pt-3 > .container > h1 {
    font-size:     26px;
    margin-bottom: 8px;
  }
  .content.pt-3 > .container > p {
    margin-bottom: 12px;
  }
  .content.pt-3:has(> .container > h1) {
    /* .content.pt-3 alone also matches About/Contact's intro section
       — they use the identical utility class. :has() narrows this to
       sections whose container has a DIRECTLY-nested h1 (true only on
       the landing pages; About/Contact nest theirs inside .three-col
       instead), so this can't leak onto pages it wasn't meant for. */
    padding-top:    12px;
    /* The default .section padding-bottom (24px, shared sitewide) was
       the actual remaining gap before the card grid — top padding
       alone didn't touch it. */
    padding-bottom: 8px;
  }
}
 
/* --- Links ---
   Dotted border-bottom instead of text-decoration underline.
   Hover adds a very muted background tint — no color change.
   The global baseline (§3) handles chrome only.             */
.content a {
  color:           var(--ct-color-link);
  text-decoration: none;
  border-bottom:   1px dotted var(--ct-color-link);
  padding-bottom:  1px;
  transition:
    background-color var(--transition-fast),
    color var(--transition-fast);
}
 
.content a:hover {
  color:            var(--ct-color-link);
  background-color: var(--ct-color-link-bg-hover);
  border-radius:    2px;
}
 
.content a:visited { color: var(--ct-color-link-visited); }
 
/* --- Lists --- */
.content ul,
.content ol {
  font-family:   var(--ct-font-body);
  font-size:     var(--ct-size-body);
  line-height:   var(--ct-leading-body);
  color:         var(--ct-color-text);
  padding-left:  var(--ct-list-indent);
  margin-bottom: 24px;
}
 
.content ul { list-style-type: var(--ct-ul-style); }
.content ol { list-style-type: var(--ct-ol-style); }
.content li { margin-bottom: var(--ct-list-item-gap); }
 
/* --- Blockquote --- */
.content blockquote {
  font-family:  var(--ct-font-heading);
  font-size:    var(--ct-size-h3);
  font-weight:  var(--ct-weight-heading);
  font-style:   italic;
  line-height:  1.4;
  color:        var(--ct-color-text-sec);
  border-left:  3px solid var(--ct-blockquote-border);
  padding-left: 24px;
  margin:       24px 0;
}
 
@media (min-width: 860px) { .content blockquote { margin-left: 32px; } }
 
/* --- HR ---
   Full treatment — spacing, color, weight.
   The global baseline (§3) provides only a bare border-top.  */
.content hr {
  border: none;
  border-top: 1px solid var(--ct-color-rule);
  margin: 40px 0;
}
 
/* --- Inline code & pre blocks --- */
.content code {
  font-family:      var(--ct-font-mono);
  font-size:        0.875em;
  font-weight:      500;
  background-color: var(--ct-color-code-bg);
  color:            var(--ct-color-code-text);
  padding:          2px 5px;
  border-radius:    var(--radius-sm);
}
 
.content pre {
  font-family:      var(--ct-font-mono);
  font-size:        0.875em;
  font-weight:      500;
  background-color: var(--ct-color-code-bg);
  color:            var(--ct-color-code-text);
  padding:          var(--space-3);
  border-radius:    var(--radius);
  overflow-x:       auto;
  margin-bottom:    24px;
  line-height:      1.6;
}
 
.content pre code { background: none; padding: 0; font-size: inherit; font-weight: inherit; }
 
/* --- Tables --- */
.content table {
  width: 100%;
  border-collapse: collapse;
  font-family:     var(--ct-font-body);
  font-size:       var(--ct-size-body);
  margin-bottom:   24px;
}
 
.content th {
  font-family:     var(--ct-font-body);
  font-weight:     var(--weight-medium);
  font-size:       var(--ct-size-caption);
  text-transform:  uppercase;
  letter-spacing:  0.05em;
  color:           var(--ct-color-text-muted);
  padding:         8px 12px;
  border-bottom:   2px solid var(--ct-table-border);
  text-align:      left;
}
 
.content td {
  padding:         10px 12px;
  border-bottom:   1px solid var(--ct-color-rule);
  color:           var(--ct-color-text);
  vertical-align:  top;
}
 
.content tr:nth-child(even) td { background-color: var(--ct-table-stripe); }
 
/* --- Mark (highlight) --- */
.content mark {
  background-color: var(--ct-color-mark);
  color:            inherit;
  padding:          1px 3px;
  border-radius:    2px;
}
 
/* --- Abbr --- */
.content abbr[title] {
  text-decoration:       underline dotted;
  text-underline-offset: 2px;
  cursor:                help;
}
 
/* --- Figures & images --- */
.content img {
  width:   100%;
  height:  auto;
  display: block;
}
 
.content figure { margin: 32px 0; }
 
.content figcaption {
  font-family: var(--ct-font-body);
  font-size:   var(--ct-size-caption);
  font-style:  italic;
  line-height: 1.4;
  color:       var(--ct-color-text-sec);
  margin-top:  8px;
}
 
/* Centered — 70% of column width                            */
.content .figure--centered {
  width:        70%;
  margin-left:  auto;
  margin-right: auto;
}
 
/* Float patterns — full width mobile, float desktop.
   Text wraps naturally under the float.
   margin-bottom gives clearance when text wraps beneath.     */
.content .figure--left,
.content .figure--right { width: 100%; margin-bottom: 16px; }
 
@media (min-width: 860px) {
  .content .figure--left {
    float: left;
    width: 45%;
    margin-top: 0;
    margin-right: 24px;
    margin-bottom: 8px;
    margin-left: 0;
  }
 
  .content .figure--right {
    float: right;
    width: 45%;
    margin-top: 0;
    margin-left: 24px;
    margin-bottom: 8px;
    margin-right: 0;
  }
}
 
/* Clear floats after the prose column so the next section
   starts clean below any active float.                      */
.content .main-content::after {
  content: '';
  display: table;
  clear: both;
}
 
/* --- Desktop scaling --- */
@media (min-width: 860px) {
  .content p,
  .content ul,
  .content ol { font-size: var(--ct-size-lead); }
}
 
 
/* ============================================================
   15. CONTENT BLOCK VARIANTS
 
   Each variant inherits everything from .content and overrides
   only what changes — via --ct- tokens where possible, or
   direct element rules where layout changes are needed.
 
   ── BUILT-IN: .content--card ────────────────────────────────
   Inverts the base font pairing: DM Sans headings, Lora body.
   Used inside .card shells. Image sits flush at top.
   Padding applied per element (h3, p) rather than container.
 
   ── STUB ────────────────────────────────────────────────────
   Copy the commented block below, rename [name], delete
   lines you don't need. Override only what differs.
   ============================================================ */
 
/* --- .content--card --- */
 
.content--card {
  --ct-font-heading:    'DM Sans', Helvetica Neue, Arial, sans-serif;
  --ct-font-body:       'Lora', Georgia, serif;
  --ct-weight-heading:  500;
  --ct-size-body:       15px;
  --ct-size-h3:         17px;
  --ct-leading-body:    1.6;
  --ct-leading-heading: 1.2;
  --ct-tracking-h3:     0;
  --ct-list-indent:     18px;
  --ct-list-item-gap:   4px;
  padding:              0;
}
 
.content--card h3 {
  font-family:    var(--ct-font-heading);
  font-weight:    var(--ct-weight-heading);
  font-size:      var(--ct-size-h3);
  letter-spacing: var(--ct-tracking-h3);
  line-height:    var(--ct-leading-heading);
  color:          var(--ct-color-heading);
  margin:         16px 24px 8px 24px;
}
 
.content--card p {
  font-family:  var(--ct-font-body);
  font-size:    var(--ct-size-body);
  line-height:  1.5;
  color:        var(--ct-color-text);
  margin:       0 24px 16px 24px;
  overflow:     hidden;
}
 
.content--card p:last-child { margin-bottom: 12px; }
 
.content--card ul,
.content--card ol {
  font-family:  var(--ct-font-body);
  font-size:    var(--ct-size-body);
  line-height:  var(--ct-leading-body);
  color:        var(--ct-color-text);
  padding-left: var(--ct-list-indent);
  margin:       0 24px 16px 24px;
}
 
.content--card ul { list-style-type: var(--ct-ul-style, disc); }
.content--card ol { list-style-type: var(--ct-ol-style, decimal); }
.content--card li { margin-bottom: var(--ct-list-item-gap); }
 
.content--card figure { margin: 0; }
 
.content--card img { width: 100%; height: auto; display: block; }
 
@media (min-width: 860px) {
  .content--card img { height: 220px; object-fit: cover; }
}
 
.content--card figcaption {
  font-family:   var(--ct-font-heading);
  font-size:     12px;
  font-style:    italic;
  line-height:   1.4;
  color:         var(--ct-color-text-sec);
  padding:       8px 0 0 24px;
  margin-bottom: 0;
}
 
 
/* ============================================================
   NEW CONTEXT VARIANT STUB
   Copy this entire commented block, rename [name], fill in
   only what differs from the .content base. Delete any lines
   you don't need — the base values remain in effect.
 
   Common use cases:
     .content--blog      larger body, looser leading, drop caps
     .content--landing   centered headings, no p max-width
     .content--sidebar   smaller type, tighter spacing
     .content--minimal   no blockquote border, plain hr, no stripe
 
   .content--[name] {
 
     -- Custom properties: override only what changes --------
     --ct-font-heading:        ;
     --ct-font-body:           ;
     --ct-font-mono:           ;
     --ct-weight-heading:      ;
     --ct-weight-body:         ;
     --ct-size-body:           ;
     --ct-size-lead:           ;
     --ct-size-h1:             ;
     --ct-size-h2:             ;
     --ct-size-h3:             ;
     --ct-size-caption:        ;
     --ct-leading-body:        ;
     --ct-leading-heading:     ;
     --ct-tracking-h1:         ;
     --ct-tracking-h2:         ;
     --ct-tracking-h3:         ;
     --ct-color-heading:       ;
     --ct-color-text:          ;
     --ct-color-text-sec:      ;
     --ct-color-text-muted:    ;
     --ct-color-link:          ;
     --ct-color-link-hover:    ;
     --ct-color-link-visited:  ;
     --ct-color-link-bg-hover: ;   hover background tint
     --ct-color-rule:          ;
     --ct-color-code-bg:       ;
     --ct-color-code-text:     ;
     --ct-color-mark:          ;
     --ct-ul-style:            ;
     --ct-ol-style:            ;
     --ct-list-indent:         ;
     --ct-list-item-gap:       ;
     --ct-blockquote-border:   ;
     --ct-table-border:        ;
     --ct-table-stripe:        ;
   }
 
   -- Element overrides: only when layout changes -------
   .content--[name] h1         { text-align: center; }
   .content--[name] p          { max-width: none; }
   .content--[name] a          { text-decoration: none; border-bottom: 1px solid; }
   .content--[name] blockquote { border-left: none; padding-left: 0; }
   .content--[name] hr         { margin: 24px 0; }
   .content--[name] figure     { margin: 48px 0; }
   .content--[name] figcaption { text-align: center; }
   .content--[name] pre        { border-radius: 0; }
   .content--[name] table      { font-size: var(--ct-size-caption); }
   ============================================================ */
 
 
/* ============================================================
   16. CARDS
   Three grid layouts sharing the same .card shell and
   .content--card typographic variant.
 
   Width hierarchy:
     .cards__grid        column-constrained — self-constrains to 720px
     .cards__grid--three container width
     .cards__grid--four  container width
 
   All grids are full-width of their wrapper. .cards__grid
   self-constrains to 720px; --three and --four fill the container.
 
   Usage:
     <section class="section">
       <div class="container">
         <div class="cards__grid">
           <div class="card">
             <div class="content--card">
               [pure HTML — no classes on children]
             </div>
           </div>
         </div>
       </div>
     </section>
   ============================================================ */
 
.cards__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  max-width: var(--column-width);
  margin-left: auto;
  margin-right: auto;
}
 
@media (min-width: 860px) {
  .cards__grid { grid-template-columns: repeat(2, 1fr); }
}
 
.cards__grid--three {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}
 
@media (min-width: 860px) {
  .cards__grid--three {
    grid-template-columns: repeat(3, 1fr);
  }
}
 
.cards__grid--four {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}
 
@media (min-width: 600px) {
  .cards__grid--four { grid-template-columns: repeat(2, 1fr); }
}
 
@media (min-width: 860px) {
  .cards__grid--four {
    grid-template-columns: repeat(4, 1fr);
    gap: 20px;
  }
}
 
/* Card shell — flex column so .card__link pins to bottom     */
.card {
  background-color: #F7F7F7;
  border:           1px solid #CCCCCC;
  border-radius:    0;
  box-shadow:       0 2px 8px rgba(0,0,0,0.07);
  overflow:         hidden;
  display:          flex;
  flex-direction:   column;
}
 
/* .content--card fills available space, pushing link down   */
.card .content--card {
  flex: 1;
}
 
/* Pinned card link — always at the bottom of the card.
   Lives outside .content--card in the HTML — it's chrome,
   not prose. One empty-line gap above (via p:last-child
   margin of 24px), 8px below to card edge.
   Usage: <a href="#" class="card__link">Learn more</a>       */
.card__link {
  display:         block;
  font-family:     var(--font-body);
  font-size:       var(--text-caption);
  font-weight:     var(--weight-medium);
  color:           var(--accent);
  text-decoration: none;
  padding:         0 24px 8px 24px;
  transition:      color var(--transition-fast);
}
 
.card__link:hover   { color: var(--accent-hover); text-decoration: underline; text-underline-offset: 3px; }
.card__link:visited { color: var(--accent); }
 
.card__link:focus-visible {
  outline:        2px solid var(--color-focus);
  outline-offset: 2px;
  border-radius:  var(--radius-sm);
}
 
.card__link::after { content: " →"; }
 
 
/* ============================================================
   17. HERO IMAGE COMPONENTS
   img-hero    — full-width image, max-height 80vh
   hero-text   — image with text block overlaid top-left
 
   Usage (.hero-text):
   <section class="hero-text">
     <img src="..." alt="...">
     <div class="hero-text__block">
       <h2>...</h2>
       <p class="text-lead">...</p>
     </div>
   </section>
 
   NOTE — heading level: the long-form page template owns the
   page's single <h1> in .main-content. The hero overlay heading
   is an <h2>, even when it reads visually as the page title.
 
   NOTE — per-project styling: .hero-text__block's background
   color and position (top/left, max-width) are placeholders.
   Surface tint and placement are design decisions made per
   project once colors and layout are settled.
   ============================================================ */
 
.img-hero {
  width:         100%;
  max-height:    80vh;
  object-fit:    cover;
  display:       block;
  margin-bottom: var(--space-5);
}
 
.hero-text {
  position: relative;
  width:    100%;
}
 
.hero-text img {
  width:      100%;
  display:    block;
  object-fit: cover;
}
 
.hero-text__block {
  position:         absolute;
  top:              var(--space-4);
  left:             0;
  max-width:        560px;
  background-color: rgba(255, 245, 224, 0.82);
  padding:          var(--space-3) var(--space-5);
}
 
.hero-text__block h2 {
  font-family:    var(--font-heading);
  font-weight:    var(--weight-regular);
  font-size:      clamp(1.8rem, 5vw, 3rem);
  line-height:    1.1;
  color:          var(--color-heading);
  margin-bottom:  12px;
}
 
.hero-text__block p {
  font-family: var(--font-body);
  font-size:   var(--text-lead);
  line-height: 1.4;
  color:       var(--color-text-secondary);
  margin:      0;
  max-width:   44ch;
}
 
@media (max-width: 599px) {
  .hero-text__block {
    position:  relative;
    top:       auto;
    left:      auto;
    max-width: 100%;
    padding:   var(--space-3) var(--page-margin);
  }
}
 
 
/* ============================================================
   18. SLIDESHOW
   Arrows + a single centered play/pause button. One slide visible
   at a time. Images fill the slideshow width at a fixed aspect
   ratio. Sits inside .container at container width, or go
   full-bleed by wrapping in a section with no container.

   Deliberately no dot indicators — with categories running up to
   48 photos, dots never worked as real navigation (nobody counts
   to the 23rd dot), and on mobile a single row of them physically
   couldn't fit without pushing other controls off-screen. Removed
   entirely rather than just hidden: emulates the plain, minimal
   feel of a physical digital picture frame — just the photo, with
   the least interface necessary (prev, next, pause) rather than
   the most.
 
   Usage:
   <section class="section--flush slideshow-hero">
     <div class="container">
       <div class="slideshow-hero__inner">
         <div class="slideshow-hero__header">
           <h1>Category Title</h1>
           <p>One-line intro.</p>
         </div>
         <div class="slideshow" aria-label="..." aria-roledescription="carousel">
           <button class="slideshow__btn slideshow__btn--prev" aria-label="Previous slide">&lt;</button>
           <div class="slideshow__track" id="ss-track">
             <!-- single <img> swapped by JS, opacity crossfade -->
           </div>
           <button class="slideshow__btn slideshow__btn--next" aria-label="Next slide">&gt;</button>
           <div class="slideshow__footer">
             <button class="slideshow__playpause" aria-label="Pause slideshow"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="4" width="3" height="16"></rect><rect x="15" y="4" width="3" height="16"></rect></svg></button>
           </div>
         </div>
         <p class="event-gallery__hint">Instructions text.</p>
       </div>
     </div>
   </section>

   .slideshow-hero is the full-bleed dark backdrop (#2b2b2b, not a
   --color-* token — a deliberate one-off theater treatment) that
   contains the title/intro, the slideshow, AND the hint text — the
   whole component is one continuous dark field, no seams. Everything
   inside shares one width via .slideshow-hero__inner (max-width:
   1200px, centered) so the header text, the slide, and the hint all
   share the same left/right edges instead of each picking their own.

   .slideshow is a 3-column CSS grid — left gutter (1fr), center
   column (auto, sized to the photo), right gutter (1fr) — the same
   "section > left/center/right column" pattern used elsewhere in
   this stylesheet, applied here with the center column's width
   determined by the photo's own 3:2-ratio box rather than a fixed
   prose measure. The arrows are direct grid children living in the
   gutter columns, vertically centered for free via align-items on
   the grid (no absolute positioning, no JS measurement, no negative
   offsets — that approach proved fragile and was replaced with this).
   They're pushed toward the photo edge via justify-self, with a
   fixed 30px padding as the gap — same rule on mobile and desktop,
   no breakpoint-specific values needed. Mobile gets its own override
   (see the @media block on .slideshow and .slideshow__track img)
   since there the photo should fill available WIDTH rather than
   being height-bound. The footer bar spans all three columns on its
   own row (grid-column: 1 / -1) and holds just the play/pause button,
   centered.
   ============================================================ */
 
.slideshow-hero {
  background: #2b2b2b;
  padding: var(--space-3) 0 var(--space-5);
  /* flex:1, now that <main> is itself a flex container (see the base
     rule above) — this is the reliable, standard way to fill
     remaining space, unlike the percentage min-height tried first,
     which DevTools confirmed doesn't resolve here (main having a
     definite height as body's OWN flex item doesn't make main act as
     a "fill me" containing block for its own children — only an
     actual flex/grid container on main itself does that). This is
     what makes the dark background reach the footer instead of
     leaving body's white background exposed in the gap on short
     pages, since main's flex-grow no longer has anywhere else to go.
     display:flex + justify-content:center then vertically centers
     the whole title+photo+controls block as one group — matching the
     event lightbox's centered presentation instead of sitting pinned
     to the top, which was never a deliberate choice to begin with. */
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.slideshow-hero__inner {
  max-width:   1200px;
  /* Was margin-right:auto only — that doesn't center a block, it
     left-aligns it and pushes all the leftover space to the right.
     A real, pre-existing bug (not something introduced this session),
     found while chasing the centering issue — this affects any
     viewport wider than 1200px specifically. margin:0 auto is the
     actual centering rule. */
  margin: 0 auto;
}

.slideshow-hero__header {
  display:     flex;
  align-items: baseline;
  flex-wrap:   wrap;
  gap:         var(--space-3);
  margin-bottom: var(--space-2);
}

.slideshow-hero__header h1 {
  font-family: var(--font-heading);
  font-weight: var(--weight-regular);
  font-size:   var(--text-h2);
  color:       rgba(255,255,255,0.75); /* matches the tagline's muted
    white below it, rather than the brighter var(--color-page) this
    used before — was too visually loud against the dark background */
  line-height: var(--leading-heading);
  margin:      0;
}

.slideshow-hero__header p {
  font-family: var(--font-body);
  font-size:   var(--text-body);
  color:       rgba(255,255,255,0.75);
  margin:      0;
  max-width:   60ch;
}

.slideshow-hero .event-gallery__hint {
  /* Hidden everywhere, desktop and mobile alike — with the pause
     button always visible now, this instruction is redundant (nobody
     needs to be told what a pause button does). CSS-only hide rather
     than removing the <p> from all 12 slideshow pages' markup — keeps
     this a single-file change for shipping; pulling the dead markup
     out entirely is a fine cleanup item for the later slideshow/
     lightbox rebuild, not urgent now. */
  display: none;
}

.slideshow {
  display:               grid;
  grid-template-columns: 1fr auto 1fr;
  align-items:           center;
  user-select:           none;
}
 
.slideshow__track {
  grid-column: 2;
  position:    relative;
  /* The image was never explicitly centered here — it just renders
     at its own natural size and sits flush-left in whatever width
     this column happens to be. Invisible on a narrow phone (the
     column's barely wider than the image anyway), but obvious on a
     wide-but-short viewport (e.g. Galaxy A51 landscape) where this
     column can end up much wider than the image — the image was
     sitting at the far left with a huge empty gap on the right.
     display:flex + justify-content/align-items:center fixes this
     everywhere, not just on mobile — a universal correctness fix
     with no downside on viewports where it was already invisible. */
  display:         flex;
  justify-content: center;
  align-items:     center;
}
 
.slideshow__track img {
  display:      block;
  width:        auto;
  height:       auto;
  max-width:    100%;
  /* Nudged down from the original 85dvh (then 80dvh) — the goal
     isn't max photo size, it's guaranteeing the footer/pause button
     never clips even with heavy browser chrome onscreen (address bar
     + tabs + bookmarks bar all eating into dvh's real available
     height). The pause button must stay visible always (WCAG 2.2.2 —
     auto-advancing content needs a persistent pause control), so this
     value is sized for the WORST-CASE (most chrome, least real
     viewport) scenario. That means fullscreen/no-chrome views will
     show a bit more gray margin below the photo than the minimum —
     expected and fine, not a bug. Eyeballed, not derived — adjust a
     few dvh at a time if the footer still clips in a heavy-chrome
     browser, or if the margin ever feels excessive everywhere. */
  max-height:   78dvh;
  aspect-ratio: 3 / 2;
  object-fit:   cover;
  opacity:      0;
  transition:   opacity 400ms ease;
}
 
.slideshow__track img.is-active { opacity: 1; }
 
.slideshow__caption {
  font-family:  var(--font-body);
  font-size:    var(--text-caption);
  font-style:   italic;
  line-height:  var(--leading-caption);
  color:        var(--color-text-secondary);
  padding:      var(--space-1) var(--space-2);
  background:   var(--color-surface);
  margin:       0;
}
 
/* Prev / next arrow buttons — plain grid children of .slideshow,
   living in the left/right gutter columns (see .slideshow above).
   Vertical centering comes free from the grid's align-items:center;
   horizontal placement is just "pushed toward the photo, then a
   fixed padding" — no absolute positioning, no measurement, no
   negative offsets. This is what actually keeps it reliable across
   both mobile (narrow gutters) and desktop (wide gutters). */
.slideshow__btn {
  background:       none;
  border:           none;
  box-shadow:       none;
  color:            rgba(255,255,255,0.6);
  text-shadow:      0 1px 5px rgba(0,0,0,0.7);
  font-family:      var(--font-body);
  font-size:        32px;
  font-weight:      var(--weight-medium);
  line-height:      1;
  cursor:           pointer;
  display:          flex;
  align-items:      center;
  justify-content:  center;
  transition:       color var(--transition-fast);
}

.slideshow__btn--prev {
  grid-column:   1;
  justify-self:  end;
  padding-right: 30px;
}

.slideshow__btn--next {
  grid-column:  3;
  justify-self: start;
  padding-left: 30px;
}

.slideshow__btn:hover {
  color: var(--color-page);
}

.slideshow__btn:focus-visible {
  outline:        2px solid var(--color-focus);
  outline-offset: 2px;
}

.slideshow__btn:disabled {
  opacity:        0.3;
  cursor:         default;
}

/* Mobile-only override — desktop rules above are untouched.
   On mobile there's no "fill the viewport height" goal (that's a
   desktop-chrome concern); instead the photo should fill all
   available WIDTH between two arrow-sized gutters. Reverses which
   columns get 1fr, and lets the photo's width (not JS-computed
   height) drive its size.

   The !important on max-height is a deliberate, narrow exception to
   the project's no-!important rule — it's the only way to override
   the inline max-height that JS sets on the image (for the desktop
   viewport-fit behavior) without touching the JS itself. */
@media (max-width: 859px), (max-height: 500px) {

  .slideshow {
    grid-template-columns: auto 1fr auto;
  }

  .slideshow__btn--prev { padding-right: 0; }
  .slideshow__btn--next { padding-left:  0; }

  /* Hidden on mobile (both orientations) rather than shrunk — with
     only the h1 left in .slideshow-hero__header, there's nothing left
     to wrap onto a second line, so the awkward wrap-gap issue is gone
     everywhere it could occur, not just tightened around the edges.
     The tagline stays on desktop, where there's room for it. */
  .slideshow-hero__header p {
    display: none;
  }

  /* 28px reads as large/distracting on a small screen with nothing
     beside it (the tagline's gone here — see above). Sized down to
     the existing --text-h3 token (22px) rather than a new value.

     Top padding: trimmed 8px off the base --space-3 value (24px),
     to free more room for the photo below. An initial 4px trim
     turned out barely noticeable once compared side-by-side, hence
     doubling it. Kept equal to the title's margin-bottom below, so
     the title stays centered in this space rather than drifting
     toward the header or the photo as either value changes. */
  .slideshow-hero {
    padding-top: calc(var(--space-3) - 8px);
  }

  /* Centered to match the photo/arrows below it, which are already
     centered as a unit — left-flush title against centered content
     read as a mismatch once the tagline (which used to sit beside it)
     was removed. Margin-bottom kept equal to the top padding above —
     see the comment there. */
  .slideshow-hero__header {
    justify-content: center;
    text-align: center;
    margin-bottom: calc(var(--space-3) - 8px);
  }

  .slideshow-hero__header h1 {
    font-size: var(--text-h3);
  }
}

/* Portrait-only: size the photo by WIDTH, ignoring the JS-computed
   inline max-height (which exists for desktop's viewport-fit
   behavior) — on a narrow, tall viewport there's no "fit the height"
   goal, just "fill the available width." */
@media (max-width: 859px) and (orientation: portrait) {
  .slideshow__track img {
    width:      100%;
    max-width:  100%;
    height:     auto;
    max-height: none !important;
  }
}

/* Overrides the rule directly above, specifically for this page: with
   the title block and footer both gone here, sizing by width alone
   and ignoring height meant the image rendered at a modest size and
   then sat centered in a much taller box — big, symmetric, wasted
   gaps above AND below, not a bug in the centering, just nothing
   asking the image to actually grow into the space available. Reapply
   a real height cap (matching the landscape/short-viewport value) so
   portrait gets the same "grow to fill both constraints" sizing
   instead of width-only. The !important matches the one it's
   overriding — same narrow-exception pattern as elsewhere in this
   file, not a new precedent. */
@media (max-width: 859px) and (orientation: portrait) {
  .slideshow-page .slideshow__track img {
    /* The previous attempt only added max-height on top of the rule
       above — but that rule's width:100% is a HARD width, not a max.
       Since the image is aspect-ratio-locked, a fixed width forces a
       fixed height as a direct consequence, regardless of any
       max-height sitting on top of it — max-height can only ever
       shrink an already-oversized result, never grow an undersized
       one. That's why nothing visibly changed. Replacing width/height
       entirely with the same width:auto + height:auto + max-width +
       max-height combination landscape already uses successfully —
       this lets the browser pick whichever of the two constraints
       actually binds, instead of width always winning by default. */
    width:      auto;
    height:     auto;
    max-width:  100% !important;
    max-height: var(--slideshow-photo-max-height) !important;
  }
}

/* Narrow AND landscape (e.g. a phone rotated sideways) is the opposite
   situation from portrait: plenty of width, very little height. This
   deliberately does NOT touch the image's max-height at all — it
   falls through to the same JS-measured, precise height-fit logic
   already used on desktop (updateStageMaxHeight() sets an inline
   max-height in px based on the real remaining viewport space), which
   is exactly right here too and needs no separate guess. The only
   thing landscape genuinely needs on top of that: the grid's middle
   column is still 1fr (stretchy) at this width, unlike desktop where
   that column is auto-sized to fit the image — so the now-narrower,
   height-driven image needs to be re-centered within it. */
@media (max-width: 859px) and (orientation: landscape) {
  .slideshow__track {
    display: flex;
    justify-content: center;
  }
}

/* Catches ANY short viewport — phones in landscape, and also wider
   devices (tablets, some Android phones in landscape) that clear the
   859px WIDTH breakpoint above but are still short on real HEIGHT
   (e.g. Samsung Galaxy A51/71 landscape: 914×412 — wider than 859px,
   so the width-based query above never fires for it, yet its 412px
   of height clips a photo sized for desktop just as badly as a phone
   does). This was the actual bug: the earlier fix keyed off WIDTH,
   which is the wrong discriminator for a problem that's fundamentally
   about HEIGHT. One rule, keyed on the real constraint, replaces two
   separate width-based guesses that didn't agree with each other.

   55dvh — leaves room for header + title + the now-always-visible
   footer within a ~375-412px real viewport. Test across the actual
   short-viewport devices/orientations and report if the bottom still
   clips anywhere, or if there's slack to size up; adjust this one
   number a few dvh at a time rather than adding more breakpoints. */
@media (max-height: 500px) {
  .slideshow-hero__header h1 {
    font-size: var(--text-h3);
  }

  .slideshow__track img {
    /* Bumped from 55dvh — that guess predated the hint text being
       removed, so it left more slack than actually needed once that
       line's height came out of the total. Desktop's 78dvh (a
       separate rule, unaffected by this) stays exactly as-is —
       already confirmed correct for the worst-case chrome-heavy
       scenario. Test on the real short-viewport devices (iPhone SE
       landscape, Galaxy A51 landscape) and report if there's still
       slack to take up or if this starts clipping; adjust a few dvh
       at a time, same as before. */
    max-height: 60dvh;
  }
}
 
.slideshow__footer {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
}

.slideshow__playpause {
  background:  none;
  border:      none;
  box-shadow:  none;
  color:       rgba(255,255,255,0.6);
  font-size:   16px;
  line-height: 1;
  padding:     4px 8px;
  cursor:      pointer;
  display:     flex;
  align-items: center;
  justify-content: center;
  transition:  color var(--transition-fast);
  flex-shrink: 0;
}

.slideshow__playpause:hover {
  color: var(--color-page);
}

.slideshow__playpause:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}
 
/* ============================================================
   19. LIGHTBOX
   Fixed-column thumbnail grid (4-up desktop, 2-up mobile).
   Last row is centered via flexbox. Clicking a thumbnail opens
   a full-screen overlay with prev/next navigation.
 
   Usage:
   <div class="lightbox-grid" role="list">
     <button class="lightbox-thumb" role="listitem"
       aria-label="Open image 1 of 5"
       data-src="full-size.jpg" data-caption="Caption text">
       <img src="thumb.jpg" alt="...">
     </button>
     ...
   </div>
 
   The overlay is injected by JavaScript — no markup needed.
   ============================================================ */
 
.lightbox-grid {
  display:         flex;
  flex-wrap:       wrap;
  justify-content: center;
  gap:             var(--space-2);
  padding:         0;
}
 
.lightbox-thumb {
  /* 2-up mobile */
  flex:            0 0 calc(50% - var(--space-1));
  padding:         0;
  border:          none;
  background:      none;
  cursor:          zoom-in;
  overflow:        hidden;
  display:         block;
}
 
@media (min-width: 600px) {
  .lightbox-thumb {
    /* 3-up at mid — graceful step on the way to 5-up */
    flex: 0 0 calc(33.333% - (var(--space-2) * 2 / 3));
  }
}
 
@media (min-width: 860px) {
  .lightbox-thumb {
    /* 5-up desktop: subtract 4 gaps from 20% */
    flex: 0 0 calc(20% - (var(--space-2) * 4 / 5));
  }
}
 
.lightbox-thumb img {
  width:        100%;
  aspect-ratio: 4 / 3;
  object-fit:   cover;
  display:      block;
  transition:   opacity var(--transition-fast), transform var(--transition-base);
}
 
.lightbox-thumb:hover img  { opacity: 0.85; transform: scale(1.03); }
 
.lightbox-thumb:focus-visible {
  outline:        2px solid var(--color-focus);
  outline-offset: 2px;
}
 
/* Overlay — injected by JS. Hidden by default, faded/scaled in
   on .is-open. */
.lightbox-overlay {
  display:          flex;
  visibility:       hidden;
  opacity:          0;
  position:         fixed;
  inset:            0;
  z-index:          500;
  background-color: rgba(0,0,0,0.92);
  flex-direction:   column;
  align-items:      center;
  justify-content:  center;
  padding:          var(--space-5) var(--space-4) var(--space-3);
  transition:       opacity var(--transition-base), visibility var(--transition-base);
}
 
.lightbox-overlay.is-open {
  visibility: visible;
  opacity:    1;
}
 
.lightbox-overlay__img {
  max-width:    100%;
  max-height:   75vh;
  object-fit:   contain;
  display:      block;
  transform:    scale(0.96);
  transition:   opacity var(--transition-fast), transform var(--transition-base);
}
 
.lightbox-overlay.is-open .lightbox-overlay__img {
  transform: scale(1);
}
 
/* Added while swapping the image source during prev/next
   navigation — fades the current image out before the new
   src is set, then fades back in. Toggled by JS. */
.lightbox-overlay__img.is-changing { opacity: 0; }
 
.lightbox-overlay__caption {
  font-family:  var(--font-body);
  font-size:    var(--text-caption);
  font-style:   italic;
  color:        rgba(255,255,255,0.65);
  margin-top:   var(--space-2);
  text-align:   center;
  max-width:    56ch;
}
 
.lightbox-overlay__counter {
  font-family:  var(--font-body);
  font-size:    11px;
  font-weight:  var(--weight-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color:        rgba(255,255,255,0.4);
  margin-top:   var(--space-1);
}
 
.lightbox-overlay__close {
  position:    absolute;
  top:         var(--space-2);
  right:       var(--space-2);
  background:  none;
  border:      none;
  color:       rgba(255,255,255,0.7);
  font-size:   28px;
  line-height: 1;
  cursor:      pointer;
  padding:     var(--space-1);
  border-radius: var(--radius-sm);
  transition:  color var(--transition-fast);
}
 
.lightbox-overlay__close:hover { color: #fff; }
 
.lightbox-overlay__close:focus-visible {
  outline:        2px solid var(--color-focus);
  outline-offset: 3px;
}
 
.lightbox-overlay__btn {
  position:         absolute;
  top:              50%;
  transform:        translateY(-50%);
  background-color: rgba(255,255,255,0.12);
  border:           1px solid rgba(255,255,255,0.2);
  color:            #fff;
  width:            44px;
  height:           44px;
  border-radius:    50%;
  cursor:           pointer;
  font-size:        20px;
  display:          flex;
  align-items:      center;
  justify-content:  center;
  transition:       background-color var(--transition-fast);
}
 
.lightbox-overlay__btn:hover { background-color: rgba(255,255,255,0.22); }
 
.lightbox-overlay__btn:focus-visible {
  outline:        2px solid var(--color-focus);
  outline-offset: 3px;
}
 
.lightbox-overlay__btn--prev { left:  var(--space-2); }
.lightbox-overlay__btn--next { right: var(--space-2); }
 
 
/* ============================================================
   20. COOKIE NOTICE
   Fixed bottom bar. Dismisses on button click; stores
   preference in localStorage. Appears only until dismissed.
 
   Usage:
   <div class="cookie-notice" id="cookie-notice" role="region"
     aria-label="Cookie notice" aria-live="polite">
     <p class="cookie-notice__text">We use cookies to...</p>
     <div class="cookie-notice__actions">
       <button class="cookie-notice__btn cookie-notice__btn--accept"
         id="cookie-accept">Accept</button>
       <button class="cookie-notice__btn cookie-notice__btn--decline"
         id="cookie-decline">Decline</button>
     </div>
   </div>
   ============================================================ */
 
.cookie-notice {
  position:         fixed;
  bottom:           0;
  left:             0;
  right:            0;
  z-index:          400;
  background-color: var(--color-heading);
  color:            #fff;
  padding:          var(--space-2) var(--page-margin);
  display:          flex;
  flex-wrap:        wrap;
  align-items:      center;
  justify-content:  center;
  gap:              var(--space-2);
  box-shadow:       0 -2px 12px rgba(0,0,0,0.18);
  transform:        translateY(0);
  transition:       transform var(--transition-base);
}
 
.cookie-notice.is-hidden {
  transform:     translateY(110%);
  pointer-events: none;
}
 
@media (min-width: 860px) {
  .cookie-notice {
    padding-left:  var(--page-margin-tablet);
    padding-right: var(--page-margin-tablet);
    gap:           var(--space-3);
  }
}
 
.cookie-notice__text {
  font-family:  var(--font-body);
  font-size:    var(--text-caption);
  line-height:  1.5;
  color:        rgba(255,255,255,0.9);
  margin:       0;
  max-width:    64ch;
}
 
.cookie-notice__text a {
  color:           rgba(255,255,255,0.9);
  text-decoration: underline;
  text-underline-offset: 2px;
}
 
.cookie-notice__text a:hover { color: #fff; }
 
.cookie-notice__actions {
  display: flex;
  gap:     var(--space-1);
  flex-shrink: 0;
}
 
.cookie-notice__btn {
  font-family:   var(--font-body);
  font-size:     var(--text-caption);
  font-weight:   var(--weight-medium);
  padding:       6px 16px;
  border-radius: var(--radius-sm);
  cursor:        pointer;
  border:        1px solid transparent;
  transition:
    background-color var(--transition-fast),
    color var(--transition-fast),
    border-color var(--transition-fast);
}
 
.cookie-notice__btn--accept {
  background-color: #fff;
  color:            var(--color-heading);
  border-color:     #fff;
}
 
.cookie-notice__btn--accept:hover {
  background-color: rgba(255,255,255,0.88);
}
 
.cookie-notice__btn--decline {
  background-color: transparent;
  color:            rgba(255,255,255,0.8);
  border-color:     rgba(255,255,255,0.35);
}
 
.cookie-notice__btn--decline:hover {
  color:            #fff;
  border-color:     rgba(255,255,255,0.7);
}
 
.cookie-notice__btn:focus-visible {
  outline:        2px solid var(--color-focus);
  outline-offset: 3px;
}
 
 
/* ============================================================
   21. FAQ ACCORDION
   Single-open: opening one item closes others (handled in JS).
   Works inside or outside .content. No icon font needed —
   the toggle indicator is a CSS-drawn chevron.
 
   Usage:
   <div class="faq" role="list">
     <div class="faq__item" role="listitem">
       <button class="faq__question" aria-expanded="false"
         aria-controls="faq-1" id="faq-btn-1">
         Question text
       </button>
       <div class="faq__answer" id="faq-1" role="region"
         aria-labelledby="faq-btn-1" hidden>
         <div class="faq__answer-inner">
           <p>Answer text.</p>
         </div>
       </div>
     </div>
   </div>
   ============================================================ */
 
.faq {
  border-top:   1px solid var(--color-border-strong);
  max-width:    var(--column-width);
  margin-left:  auto;
  margin-right: auto;
}
 
.faq__item {
  border-bottom: 1px solid var(--color-border-strong);
}
 
.faq__question {
  width:           100%;
  display:         flex;
  justify-content: space-between;
  align-items:     center;
  gap:             var(--space-3);
  padding:         var(--space-3) 0;
  background:      none;
  border:          none;
  cursor:          pointer;
  text-align:      left;
  font-family:     var(--font-body);
  font-size:       var(--text-body);
  font-weight:     var(--weight-medium);
  color:           var(--color-heading);
  line-height:     1.4;
  transition:      color var(--transition-fast);
}
 
@media (min-width: 860px) {
  .faq__question { font-size: var(--text-lead); }
}
 
.faq__question:hover { color: var(--accent); }
 
.faq__question:focus-visible {
  outline:        2px solid var(--color-focus);
  outline-offset: 3px;
  border-radius:  var(--radius-sm);
}
 
/* Chevron — pure CSS, rotates on open */
.faq__question::after {
  content:      '';
  display:      block;
  flex-shrink:  0;
  width:        10px;
  height:       10px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform:    rotate(45deg) translateY(-2px);
  transition:   transform var(--transition-base);
}
 
.faq__question[aria-expanded="true"]::after {
  transform: rotate(225deg) translateY(-2px);
}
 
.faq__answer {
  overflow: hidden;
}
 
.faq__answer[hidden] { display: none; }
 
.faq__answer-inner {
  padding-bottom: var(--space-3);
}
 
/* Prose inside FAQ answers cascades from .content when
   .faq lives inside a .content block. When outside, these
   rules provide a reasonable baseline.                    */
.faq__answer-inner p {
  font-family:  var(--font-body);
  font-size:    var(--text-body);
  line-height:  var(--leading-body);
  color:        var(--color-text-secondary);
  margin-bottom: var(--space-2);
  max-width:    68ch;
}
 
.faq__answer-inner p:last-child { margin-bottom: 0; }
 
@media (min-width: 860px) {
  .faq__answer-inner p { font-size: var(--text-lead); }
}
 
 
 
 
/* ============================================================
   22. FULL-BLEED IMAGE
   True edge-to-edge. No container, no margins. Natural height.
   Use landscape images only — portrait images will be very tall.
 
   Usage:
   <section class="section--flush">
     <img class="img-bleed" src="..." alt="...">
   </section>
   ============================================================ */
 
.img-bleed {
  display: block;
  width:   100%;
  height:  auto;
}
 
 
/* ============================================================
   23. CTA BLOCK
   Centered headline, subhead, and button group. Text cascades
   from .cta — no classes on h2 or p.
   Typically sits on .section--surface background.
 
   Usage:
   <section class="section section--surface">
     <div class="container">
       <div class="cta">
         <h2>Headline</h2>
         <p>Subhead text.</p>
         <div class="btn-group">
           <a href="#" class="btn btn--primary">Primary</a>
           <a href="#" class="btn btn--secondary">Secondary</a>
         </div>
       </div>
     </div>
   </section>
   ============================================================ */
 
.cta {
  text-align:  center;
  max-width:   var(--column-width);
  margin-left: auto;
  margin-right: auto;
  padding:     var(--space-5) 0;
}
 
.cta h2 {
  font-family:    var(--font-heading);
  font-weight:    var(--weight-regular);
  font-size:      clamp(28px, 4vw, 40px);
  line-height:    1.15;
  color:          var(--color-heading);
  letter-spacing: -0.01em;
  margin-bottom:  var(--space-2);
}
 
.cta p {
  font-family:   var(--font-body);
  font-size:     var(--text-lead);
  line-height:   1.5;
  color:         var(--color-text-secondary);
  max-width:     52ch;
  margin-left:   auto;
  margin-right:  auto;
  margin-bottom: 0;
}
 
 
/* ============================================================
   24. TESTIMONIALS
   3-up grid of quote + attribution. All type cascades from
   .testimonial-item — no classes on blockquote or p.
   Lora throughout. Attribution italicized.
 
   Usage:
   <section class="section section--surface">
     <div class="container">
       <div class="testimonials">
         <div class="testimonial-item">
           <blockquote>Quote text.</blockquote>
           <p>— Name, Title</p>
         </div>
       </div>
     </div>
   </section>
   ============================================================ */
 
.testimonials {
  display:               grid;
  grid-template-columns: 1fr;
  gap:                   var(--space-4);
  max-width:             var(--container-max);
  margin-left:           auto;
  margin-right:          auto;
}
 
@media (min-width: 860px) {
  .testimonials {
    grid-template-columns: repeat(3, 1fr);
    gap:                   var(--space-4);
  }
}
 
.testimonial-item {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-2);
}
 
.testimonial-item blockquote {
  font-family:  var(--font-heading);
  font-weight:  var(--weight-regular);
  font-style:   normal;
  font-size:    var(--text-lead);
  line-height:  1.55;
  color:        var(--color-text);
  margin:       0;
  padding:      0;
  border:       none;
}
 
.testimonial-item p {
  font-family: var(--font-heading);
  font-weight: var(--weight-regular);
  font-style:  italic;
  font-size:   var(--text-caption);
  line-height: 1.4;
  color:       var(--color-text-muted);
  margin:      0;
}
 
 
/* ============================================================
   25. CONTACT FORM SECTION
   Column-constrained (720px). Heading and intro text cascade
   from .contact-form — no classes on h2 or p.
   Form elements use existing §7 chrome form rules.
 
   Usage:
   <section class="section section--surface">
     <div class="container">
       <div class="contact-form">
         <h2>Get in Touch</h2>
         <p>Intro text.</p>
         [form markup]
       </div>
     </div>
   </section>
   ============================================================ */
 
.contact-form {
  max-width:    var(--column-width);
  margin-left:  auto;
  margin-right: auto;
}
 
.contact-form h2 {
  font-family:    var(--font-heading);
  font-weight:    var(--weight-regular);
  font-size:      var(--text-h2);
  line-height:    var(--leading-heading);
  color:          var(--color-heading);
  letter-spacing: -0.01em;
  margin-bottom:  var(--space-2);
}
 
.contact-form > p {
  font-family:   var(--font-body);
  font-size:     var(--text-lead);
  line-height:   var(--leading-body);
  color:         var(--color-text-secondary);
  margin-bottom: var(--space-4);
  max-width:     56ch;
}
 
 
/* ============================================================
   26. CARDS: 1-UP
   Same .card shell as the grids. Column-constrained (720px).
   Wrap in .container > .cards__grid--one.
 
   Usage:
   <section class="section">
     <div class="container">
       <div class="cards__grid--one">
         <div class="card">
           <div class="content--card">...</div>
           <a href="#" class="card__link">Learn more</a>
         </div>
       </div>
     </div>
   </section>
   ============================================================ */
 
.cards__grid--one {
  max-width:    var(--column-width);
  margin-left:  auto;
  margin-right: auto;
}
 
 
/* ============================================================
   27. TOC OVERRIDES
   These rules live at the end to beat the .content cascade.
   ============================================================ */
 
.toc,
.content .toc {
  font-size: 15px;
  font-family: var(--font-body);
  padding: 0;
  margin: 0;
}
 
/* Force 14px on all TOC children — beats .content desktop scaling */
.toc__nav,
.toc__item,
.content .toc__nav,
.content .toc__item,
.content a.toc__link,
.content a.toc__top {
  font-size: 15px;
}
 
.content .toc__nav,
.toc__nav {
  list-style: none;
  padding: 0;
  margin: 0;
  /* display intentionally not set here — show/hide handled above */
}
 
.content .toc__item,
.toc__item {
  list-style: none;
  margin: 0;
  padding: 0;
}
 
.content a.toc__link,
.toc__link {
  display: block;
  padding: 6px 0;
  color: var(--accent);
  text-decoration: none;
  border-bottom: none;
  line-height: 1.3;
  transition: color var(--transition-fast);
}
 
.content a.toc__link:hover,
.toc__link:hover {
  color: var(--accent-hover);
  background-color: rgba(26, 111, 173, 0.07);
  border-bottom: none;
  border-radius: var(--radius-sm);
  max-width: calc(100% - var(--space-4));
}
 
/* .toc__link.is-active — removed; hover state only */
 
.content a.toc__top,
.toc__top {
  display: block;
  margin-top: var(--space-1);
  font-size: 11px;
  color: var(--color-text-muted);
  text-decoration: none;
  border-bottom: none;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
 
.content a.toc__top:hover,
.toc__top:hover {
  color: var(--accent);
  background-color: transparent;
}
 
 
/* ============================================================
   28. EVENT GALLERY
   Full-width photo grid + lightbox for event archive pages.
   ============================================================ */

/* ── Page header ──────────────────────────────────────────── */
.event-header {
  padding: var(--space-5) var(--page-margin-tablet) var(--space-4);
  text-align: center;
}

.event-header h1 {
  font-family: var(--font-heading);
  /* Scoped override, not the shared --text-h2 token (also used by
     .slideshow-hero__header h1 and .contact-form h2 — changing the
     token itself would have bumped those too). Verified numerically
     against the longest real event title (58 chars, "Edenton Music &
     Water Festival and Heritage Farm Fest") using the actual Lora font
     metrics: stays on one line at every viewport width from 860px up
     to full desktop; wraps only below 860px, same as the original
     fixed 28px did. A fixed-px jump (this project's usual preference
     over fluid clamp()) can't satisfy that constraint here — the
     43 event titles vary too much in length for one fixed breakpoint
     jump to fit the longest ones without wrapping while still looking
     bigger than 28px on the rest. */
  font-size: clamp(28px, calc(3.4vw - 2px), 44px);
  font-weight: var(--weight-regular);
  color: var(--color-heading);
  line-height: var(--leading-heading);
  margin-bottom: var(--space-1);
}

.event-header p {
  font-family: var(--font-body);
  font-size: var(--text-lead);
  color: var(--color-text-secondary);
  max-width: 680px;
  line-height: var(--leading-body);
  margin: 0 auto;
}

/* ── Hint text ────────────────────────────────────────────── */
.event-gallery__hint {
  text-align: center;
  font-size: var(--text-caption);
  color: var(--color-text-secondary); /* was --color-text-muted (3.45:1 — fails AA at this size); secondary is 8.9:1 */
  margin-bottom: var(--space-3);
  font-style: italic;
  line-height: var(--leading-caption); /* tighter than the inherited body
    default — a safety net in case this text ever wraps to two lines */
}

/* ── Thumbnail grid ───────────────────────────────────────── */
.event-gallery {
  padding: 0 24px var(--space-7);
}

.event-gallery__grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 16px;
  max-width: var(--container-wide);
  margin-left: auto;
  margin-right: auto;
}

.event-gallery__item {
  cursor: pointer;
  overflow: hidden;
  background: var(--color-surface-mid);
  aspect-ratio: 3 / 2;
  border: none;
  padding: 0;
  display: block;
  width: calc((100% - 48px) / 4);
}

.event-gallery__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* 3-column step — without this, tablet-landscape widths (iPad mini/
   Air/Pro, roughly 1024-1194px) jumped straight from 2 to 4 columns,
   cramming 4 photos into a width only comfortable for 3. Reserves true
   4-up for genuinely wide desktop windows (>=1200px) instead. */
@media (max-width: 1199px) {
  .event-gallery__item { width: calc((100% - 32px) / 3); }
}

@media (max-width: 860px) {
  .event-gallery__item { width: calc((100% - 16px) / 2); }
}

@media (max-width: 480px) {
  .event-gallery {
    padding-left: var(--page-margin);
    padding-right: var(--page-margin);
  }
  .event-gallery__item { width: 100%; }
}

/* ── Lightbox overlay ─────────────────────────────────────── */
.event-lb {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.92);
  z-index: 1000;
  align-items: center;
  justify-content: center;
}

.event-lb.is-open { display: flex; }

/* The frame is the capped, centered unit holding prev/stage/next as
   true grid children — this is what locks the arrows to the photo's
   own edge (via padding) rather than pinning them to the viewport
   edge, at every width, with no breakpoint jump. .event-lb itself
   stays the full-viewport dark scrim; .event-lb__frame is the
   separate, narrower, capped box centered inside it. */
.event-lb__frame {
  display: grid;
  /* auto columns size exactly to each arrow's own natural width,
     always — they never get squeezed by leftover space. minmax(0, 1fr)
     lets the middle column shrink continuously as the frame narrows,
     with no minimum floor fighting it. */
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  width: 100%;
  max-width: 1500px;
}

.event-lb__stage {
  position: relative;
  /* height, not max-height — the photo's own max-height:100% rule
     below needs a definite parent height to resolve against a
     percentage. max-height alone doesn't count as "definite" per the
     CSS spec, so without this the photo was only ever being
     width-constrained, and could render taller than the viewport
     (worse the less vertical space is available, e.g. heavy browser
     chrome). Harmless for smaller photos — the stage has no visible
     background, so reserving the full 90vh just centers the photo
     within a taller invisible box, same as before. */
  height: 90vh;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Lets this grid child shrink below its content's natural width
     instead of forcing the grid wider — the classic grid/flex
     "won't shrink below content" trap noted elsewhere in this
     project, and easy to hit here since minmax(0, 1fr) alone isn't
     enough if the child itself resists shrinking. */
  min-width: 0;
}

.event-lb__stage img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 150ms ease;
  user-select: none;
  display: block;
}

.event-lb__stage img.is-active { opacity: 1; }

.event-lb__close {
  position: fixed;
  top: 16px;
  right: 16px;
  background: none;
  border: none;
  color: #fff;
  font-size: 32px;
  cursor: pointer;
  line-height: 1;
  opacity: 0.7;
  transition: opacity 150ms ease;
  z-index: 1010;
}

.event-lb__close:hover { opacity: 1; }

/* Prev/next are now plain grid children of .event-lb__frame — no
   position:fixed, no viewport-relative math, no separate mobile
   strategy. One rule, every width: arrows always locked to the
   photo's edge by a fixed 10px gap. No text-shadow needed — unlike
   the slideshow's arrows, these never sit over a photo, only over
   the flat dark scrim, so a plain flat color is enough contrast on
   its own. */
.event-lb__prev,
.event-lb__next {
  position: static;
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.6);
  font-size: 32px;
  width: 48px;
  height: 80px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 150ms ease;
}

.event-lb__prev:hover,
.event-lb__next:hover { color: #fff; }

.event-lb__prev { grid-column: 1; padding-right: 10px; }
.event-lb__next { grid-column: 3; padding-left: 10px; }

.event-lb__prev:disabled,
.event-lb__next:disabled {
  color: rgba(255, 255, 255, 0.15);
  cursor: default;
  pointer-events: none;
}

/* ── Surprise Me nav pill ─────────────────────────────────── */
.nav-surprise {
  display: inline-block;
  border: 1px solid var(--color-heading);
  color: var(--color-heading);
  border-radius: 20px;
  padding: 4px 14px 5px;
  font-size: 14px;
  text-decoration: none;
  transition: background 150ms ease, color 150ms ease;
  margin-left: var(--space-1);
  line-height: 1.2;
}

.nav-surprise:hover {
  background: var(--color-heading);
  color: #fff;
}

/* ── Mobile lightbox ──────────────────────────────────────── */
/* Only the close button gets a small phone-specific tweak now — the
   arrow/photo/gap relationship above is intentionally the same rule
   at every width, so there's nothing left here for prev/next,
   .event-lb__controls, or .event-lb__stage to override. */
@media (max-width: 480px) {
  .event-lb__close {
    font-size: 22px;
    opacity: 0.5;
  }
}

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


/* ============================================================
   28. HOMEPAGE HERO
   Full-viewport single photo, no header, no slideshow — fully
   custom, shares nothing with the long-form/slideshow pattern
   used by every other page. Text overlay only, static per day
   (see index.html's own <script> — day-of-year mod pool size,
   so no photo becomes "Tuesday's photo," per Kip's call).

   --hero-accent is a deliberately different blue from the
   sitewide --accent — darker and more saturated, chosen
   specifically because the nav links sit on a translucent photo
   background rather than --color-page like every other link on
   the site, and needed more weight to stay legible there.
   ============================================================ */

:root {
  --hero-accent: #2f397e;
  --hero-accent-hover: #374394;
}

.hero-home {
  position: relative;
  width: 100%;
  height: 100dvh;
  overflow: hidden;
}

.hero-home__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* object-position set per-photo by JS — see the rotation array
     in index.html. Different photos need different focal points
     to keep the overlay's corner actually quiet. */
}

.hero-home__overlay {
  position: absolute;
  top: 48px;
  right: 48px;
  max-width: 680px;
  text-align: left;
  padding: 32px;
  background: rgba(247, 245, 242, 0.35);
  /* Approximates --color-surface at 35% opacity — pulled way back
     from an earlier, much heavier version that read as a solid
     card instead of a light touch. */
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
  /* Third pass down — 12px, then 4px, then 2px, now 1px. Barely
     perceptible; mostly here to take the hardest edge off busy
     photo detail without visibly softening the photo itself. */
}

.hero-home__brand {
  font-family: 'Lora', Georgia, serif;
  font-size: clamp(30px, 4.5vw, 52px);
  font-weight: 400;
  color: var(--color-heading);
  letter-spacing: -0.01em;
  margin-bottom: 8px;
  /* Not --accent, not a link — a title, not a nav item, even
     though it visually sits above the four that are. */
}

.hero-home__tagline {
  font-family: 'DM Sans', sans-serif;
  font-size: clamp(15px, 1.6vw, 18px);
  font-weight: 400;
  color: var(--color-text-secondary);
  margin: 0 0 28px;
  padding-left: 8px;
  /* Optical alignment, not a real indent — a capital letter at
     52px (the brand) carries more built-in left-bearing in
     absolute pixels than the same letter at 18px does, so the box
     edges line up but the ink doesn't. This nudges the ink back
     into visual alignment. */
}

.hero-home__nav {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  padding-left: var(--space-3);
  /* align-items: flex-start stops flexbox's default stretch
     behavior — without it, every link/button silently fills the
     full width of the overlay block (not just its own text),
     which is why the focus ring was drawing a box around empty
     space and why clicking to the right of a short word like
     "About" still triggered it. */
}

.hero-home__nav button,
.hero-home__nav a {
  display: block;
  font-family: 'Lora', Georgia, serif;
  font-size: clamp(20px, 2.6vw, 28px);
  font-weight: 400;
  line-height: 1.1;
  color: var(--hero-accent);
  /* Darker, more saturated than the sitewide --accent — the
     translucent photo background needed more weight than the
     normal link color to stay reliably legible. */
  background: none;
  border: none;
  text-align: left;
  text-decoration: none;
  padding: 0;
  cursor: pointer;
  transition: color var(--transition-fast);
}

.hero-home__nav button:hover,
.hero-home__nav a:hover { color: var(--hero-accent-hover); }
/* Hover lightens rather than darkens — the reverse of the usual
   convention — because the resting state is already the darker
   of the two. Reads as a highlight, not a dimming. */

.hero-home__nav button:focus-visible,
.hero-home__nav a:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 4px;
}

@media (max-width: 599px) {
  .hero-home__overlay {
    left: 16px;
    right: 16px;
    max-width: none;
    text-align: center;
    background: rgba(247, 245, 242, 0.55);
    /* Bumped up from the desktop 35% — at this width the overlay
       spans nearly the full photo instead of sitting in one
       corner, so it needs to survive a much wider range of what
       might be directly behind it, not just one quiet spot. */
  }
  .hero-home__tagline {
    padding-left: 0;
    font-size: 17px;
    /* Flat value rather than the desktop clamp() — on mobile the
       clamp was already pinned to its 15px floor regardless of
       viewport width, so raising the floor here is the only way
       to actually make it bigger on this breakpoint. */
  }
  .hero-home__nav {
    align-items: center;
    padding-left: 0;
    /* Both the optical-alignment indent and the hierarchy indent
       were built for left-aligned text — centered text doesn't
       need either, and they'd skew a centered block off-center. */
  }
}

/* Short viewports (landscape phones especially) fall outside the
   max-width: 599px query above — e.g. an iPhone in landscape is
   844px wide, well past that cutoff — but has very little vertical
   room. The desktop-style card (48px top offset, full padding, full
   text sizes) was nearly filling the whole height, reading as
   crammed against the bottom rather than actually positioned there.
   Targets height directly since that's the real constraint, not
   width or orientation specifically. Deliberately leaves horizontal
   alignment/position untouched — only the vertical footprint
   shrinks here. */
@media (max-height: 500px) {
  .hero-home__overlay {
    top: 16px;
    padding: 16px 24px;
  }
  .hero-home__brand {
    font-size: clamp(24px, 4vw, 34px);
    margin-bottom: 4px;
  }
  .hero-home__tagline {
    margin-bottom: 12px;
  }
  .hero-home__nav {
    /* Was 4px — part of the deliberate squeeze to fit this whole
       card into a short landscape-phone viewport, but tight enough
       between adjacent tap targets to be a real accessibility
       concern, not just a visual one. 10px keeps the vertical
       footprint reduction (still well under the normal 12px gap
       portrait/desktop use) while giving enough separation for
       accurate tapping. */
    gap: 10px;
  }
  .hero-home__nav button,
  .hero-home__nav a {
    font-size: clamp(16px, 2.2vw, 22px);
  }
}


/* ============================================================
   29. ARCHIVE CARD GRID
   Photo-forward card grid used on the Events and Slideshows
   landing pages (events.html, slideshows.html). A dedicated
   component, not a reuse of .mega-card — that card was purpose-
   built compact and horizontal (108x72px thumb strip) for a
   dropdown overlay with limited vertical room. Now that Events/
   Slideshows are real, primary landing pages instead of a
   dropdown preview, the photo gets real room: a full 3:2 image
   (matching every photo on the site — no cropping ratio was
   invented here), title below, same grid mechanics already
   proven in .cards__grid--four (1 col mobile, 2 col >=600px,
   4 col >=860px).

   Usage:
   <div class="archive-grid">
     <a class="archive-card" href="candlelight-tour-2019.html">
       <img class="archive-card__thumb" src="images-events/standard/20191214-KS2X0658.webp" alt="" loading="lazy">
       <span class="archive-card__title">2019 Candlelight Tour</span>
     </a>
     ...
   </div>

   Title text: year + title as ONE flowing string ("2019
   Candlelight Tour"), no forced <br>. At 42 of 43 events this is
   under 30 characters and sits on one line at normal card widths;
   the one longer title wraps naturally like any other text — no
   per-card exceptions needed. Grid rows stretch to their tallest
   card automatically (default CSS Grid align-items: stretch), so
   a two-line title in one row doesn't misalign its neighbors —
   the extra space lands below the (still top-anchored) title,
   not above it.
   ============================================================ */

.archive-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}

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

@media (min-width: 860px) {
  .archive-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-2);
  }
}

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

.archive-card {
  display: flex;
  flex-direction: column;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  text-decoration: none;
  overflow: hidden;
  transition: border-color var(--transition-fast);
}

.archive-card:hover { border-color: var(--accent); }

.archive-card:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

.archive-card__thumb {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

.archive-card__title {
  flex: 1;
  padding: var(--space-2);
  font-family: var(--font-heading);
  font-weight: var(--weight-regular);
  font-size: 18px;
  line-height: 1.35;
  color: var(--color-heading);
}


/* ============================================================
   32. CONFIRMATION MODAL — contact form success state
   Light-themed (unlike the dark event lightbox) — this is a normal
   page-chrome dialog, not a photo viewer, so it uses the site's
   regular surface/text tokens rather than the lightbox's dark scrim
   treatment. Dismissible ONLY via the explicit close controls below
   (the × or the "Close" button) — no click-outside-to-close, no
   Escape-to-close, per the requirement that this only close on a
   deliberate user action.
   ============================================================ */
.confirm-modal {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1000;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.5);
  padding: var(--page-margin);
}

.confirm-modal.is-open { display: flex; }

.confirm-modal__box {
  position: relative;
  background: var(--color-surface);
  max-width: 420px;
  width: 100%;
  padding: var(--space-4);
  border-radius: 8px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25);
  text-align: center;
}

.confirm-modal__close {
  position: absolute;
  top: 12px;
  right: 12px;
  background: none;
  border: none;
  font-size: 24px;
  line-height: 1;
  color: var(--color-text-secondary);
  cursor: pointer;
  padding: 4px 8px;
}

.confirm-modal__close:hover { color: var(--color-heading); }

.confirm-modal__title {
  font-family: var(--font-heading);
  font-weight: var(--weight-regular);
  font-size: var(--text-h3);
  color: var(--color-heading);
  margin: 0 0 var(--space-2);
}

.confirm-modal__body {
  font-size: var(--text-body);
  color: var(--color-text-secondary);
  line-height: var(--leading-body);
  margin: 0 0 var(--space-4);
}


/* ============================================================
   END OF STYLES.CSS
   ============================================================ */
