* { margin: 0; padding: 0; box-sizing: border-box; }

/* Positioned so the cloud veil (an absolute child of <html>, built by site.js)
   can anchor top:0/bottom:0 to the full document height rather than the
   viewport — that's what lets the docked clouds stretch with the content. */
html { position: relative; }

body {
  font-family: 'Chelsea Market', 'Quicksand', cursive;
  font-size: 17px;
  line-height: 1.6;
  color: #2D3748;
  /* matches the cloud art's white (rgb 253,251,251) so the veil's bottom edge
     lands on an identical color and no seam is visible on inner pages */
  background-color: #FDFBFB;
}

img { max-width: 100%; height: auto; }
a { text-decoration: none; color: inherit; }

h1, h2, h3 {
  font-family: 'Chelsea Market', sans-serif;
  font-weight: 400;
  color: #29536E;
}
h2 { font-size: 40px; line-height: 1.5; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  width: 100%;
  /* A sliver comes off the top of the sky; the grass below now runs all the
     way down, since the Ambers stand near the bottom of the painting. */
  aspect-ratio: 1024 / 739;
  overflow: hidden;
}

/* The art keeps the painting's full proportions and is clipped by .hero, so
   every layer below stays positioned against the whole scene rather than
   against the cropped box. */
.hero-art {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  aspect-ratio: 1024 / 768;
  /* Percentage margins resolve against the container's width, so this pulls
     the painting up by a fixed share of its own scale. */
  margin-top: -2.87%;
}

.hero-art .bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* The hero matches this image's aspect ratio, so "fill" shows all of it
     without distorting. */
  object-fit: fill;
}

/* All three are 1400x1000 frames, so one shared width keeps them at the same
   scale. Her feet sit at a different height inside each frame (94.1%, 95.6%
   and 93.9% down), so each gets its own offset to land every pair of feet on
   the same line across the hillside. */
/* All three are 1400x1000 crops of the same master photo, so one shared
   width keeps every figure at the same true scale. Her feet sit at a
   different height inside each frame (942, 956 and 939 px down), so each
   gets its own bottom offset to land every pair of feet on one line. */
.hero-art .amber {
  position: absolute;
  width: 58%;
  height: auto;
}

.amber-left  { left: 8.6%;  bottom: 6.75%; }
.amber-mid   { left: 25.7%; bottom: 7.52%; }
.amber-right { left: 52%;   bottom: 6.63%; }

.hero-art .tagline {
  position: absolute;
  left: -5%;
  bottom: 11%;
  width: 71%;
  height: auto;
}

/* ---------- Header (transparent, sits over the hero) ---------- */
/* The logo and the nav are each pinned to a spot in the painting — the logo
   inside the white cloud, the nav just above the Ambers' heads. */
.site-header {
  position: absolute;
  inset: 0;
  /* above the cloud veil (3) and the page sheets (4) so the nav and its
     dropdown stay usable while a sheet is open */
  z-index: 6;
  pointer-events: none;
}
.site-header .logo,
.site-header .nav-toggle,
.site-header .main-nav { pointer-events: auto; }

/* hamburger: mobile only */
.nav-toggle { display: none; }

.logo {
  position: absolute;
  left: 10.4%;
  top: 3.6%;
  width: 18%;
}

.logo img {
  display: block;
  width: 100%;
  height: auto;
}

/* Centred over the three figures — their bodies span roughly 31.6%–85.9%
   of the painting, so the midpoint sits right of the page centre. */
.main-nav {
  position: absolute;
  top: 24.5%;
  left: 58.7%;
  transform: translateX(-50%);
}

/* ---------- Navigation ---------- */
.main-nav > ul {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2rem;
}

.main-nav a,
.more-btn {
  font-family: inherit;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #0f3a52;
  text-decoration: none;
  background: none;
  border: none;
  cursor: pointer;
  white-space: nowrap;
}

.main-nav a:hover,
.more-btn:hover {
  color: #1b7ea8;
}

.main-nav a.active {
  color: #4F9EB2;
}

/* "More..." button + chevron */
.more-btn {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
}

.chev {
  width: 9px;
  height: 9px;
  border-right: 2.5px solid currentColor;
  border-bottom: 2.5px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.2s ease;
}

.has-dropdown.open .chev {
  transform: rotate(-135deg) translate(-2px, -2px);
}

/* ---------- Dropdown ---------- */
.has-dropdown {
  position: relative;
}

.dropdown {
  list-style: none;
  display: none;
  position: absolute;
  top: calc(100% + 0.75rem);
  left: 0;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  padding: 0.5rem 0;
  min-width: 230px;
  background: #fff;
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
  z-index: 10;
}

.has-dropdown.open .dropdown {
  display: flex;
}

.dropdown li {
  width: 100%;
}

.dropdown a {
  display: block;
  padding: 0.65rem 1.25rem;
  font-size: 1.15rem;
}

.dropdown a:hover {
  background: #eaf6fb;
}

/* ---------- Responsive ---------- */
/* The nav is sized in vw so it keeps floating just above the Ambers' heads
   instead of wrapping into them as the window narrows. */
@media (max-width: 1400px) {
  .main-nav a, .more-btn { font-size: 1.7vw; }
  .main-nav > ul { gap: 2vw; }
}

/* Below this the painting is too small to hold the nav, so it becomes a fixed
   bar with a drop-down panel behind a hamburger button.

   The bar is FIXED (not static) on purpose: z-index only applies to positioned
   elements, so a static header could never sit above the docked cloud veil
   (z-index 3) or the inner page sheets (z-index 4) — which is what used to
   bury the menu on inner pages. Fixed + z-index 60 keeps it on top always. */
@media (max-width: 1000px) {
  :root {
    --bar-h: 62px;
    /* the artwork "stage": banner art renders at this width, left-anchored,
       so its right edge crops instead of the whole thing shrinking. Kept
       modest (118 vs the old 133) so less of the painting is cut off. */
    --m-stage: 118vw;
    /* how far the home figures (Ambers + tagline) slide left to stay in
       frame and sit a touch left of centre */
    --m-fig-shift: 10vw;
    --m-fig-scale: 0.87;
  }
  body { overflow-x: hidden; }

  .site-header {
    position: fixed;
    inset: 0 0 auto 0;
    z-index: 60;
    height: var(--bar-h);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 0 0.4rem;
    background: transparent;
    /* frost fades at the same .3s as the ::before clouds below, so the
       menu-open gesture reads as one motion instead of three timings */
    transition: background-color .3s ease;
    pointer-events: auto;
  }
  /* Once the reader scrolls (or the menu opens) the painted cloud bank fades in
     behind the bar: solid across the top, breaking into a soft scalloped edge
     that hangs below the bar, so the header reads as part of the same sky as
     the rest of the site. It lives on ::before so it can overhang the bar's own
     height without changing the layout, and sits at z-index -1 so the logo and
     hamburger stay above it.

     The art is the ONLY layer here. Earlier versions backed it with an
     off-white wash (to make the bar fully opaque) and topped it with white
     radial pools (to make the white-on-transparent clouds read) — both are
     gone: together they haloed the art with a band of fog that welled up from
     behind the cloud edge. The clouds now stand on their own, so the header
     ends where the painting ends.

     Opacity is baked into the art rather than layered under it: the cloud body
     is now alpha 255, so nothing ghosts through, while anything under alpha 150
     is left alone so the scalloped bottom edge stays soft. The source's top 24
     rows were fully transparent — stretched over the bar that empty band read
     as a thin slice of page content across the very top — so the art is
     cropped to start at its first painted row (see assets-original). */
  .site-header::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: calc(var(--bar-h) * 2.2);
    background:
      url("assets/clouds/floating-header-mobile.webp?v=3") no-repeat top center / 100% 100%;
    opacity: 0;
    transition: opacity .3s ease;
    pointer-events: none;
    z-index: -1;
  }
  .site-header.scrolled::before,
  .site-header.nav-open::before { opacity: 1; }

  /* the logo sits inside the painting's white cloud, at the same proportions
     as desktop but scaled to the enlarged stage; it deliberately overflows
     the transparent bar */
  .logo {
    position: absolute;
    left: calc(.104 * var(--m-stage));
    top: calc(.026 * var(--m-stage));
    width: calc(.18 * var(--m-stage));
    padding: 0;
  }
  .logo img { width: 100%; height: auto; }

  /* The banner art renders on the enlarged stage, left-anchored, and the
     hero box (viewport-wide, overflow hidden) crops its right side. The
     hero itself must NOT exceed the viewport or the mobile layout viewport
     expands and fixed elements size to it. The home figures slide left to
     stay inside the visible window. */
  .hero {
    width: 100%;
    aspect-ratio: auto;
    height: calc(.72168 * var(--m-stage));
  }
  .hero-art {
    width: var(--m-stage);
    margin-top: calc(-.0287 * var(--m-stage));
  }
  .hero-art .amber,
  .hero-art .tagline {
    translate: calc(-1 * var(--m-fig-shift)) 0;
    scale: var(--m-fig-scale);
  }
  /* Pull the three figures closer together on mobile (desktop spread is
     8.6% / 25.7% / 52%) so the whole trio reads as one group in the narrow
     frame and the right Amber stays fully visible. */
  .hero-art .amber-left  { left: 9%; }
  .hero-art .amber-mid   { left: 23%; }
  .hero-art .amber-right { left: 44%; }

  /* keep the sheets starting at the same proportional depth into the (now
     taller) banner as desktop: ~30% of the stage from the top */
  #page-view { margin-top: -56.25%; }

  /* hamburger → X */
  .nav-toggle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 46px;
    height: 46px;
    padding: 0 10px;
    background: none;
    border: none;
    cursor: pointer;
  }
  .nav-toggle .bar {
    height: 2.5px;
    border-radius: 2px;
    background: #1B9BD8;   /* bright blue, matching the logo's lighter strokes */
    transition: transform .28s ease, opacity .18s ease;
  }
  .nav-open .nav-toggle .bar:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
  .nav-open .nav-toggle .bar:nth-child(2) { opacity: 0; }
  .nav-open .nav-toggle .bar:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }

  /* With the menu open the panel owns the entire top of the screen: the bar
     itself becomes the panel's solid top strip (same color as the sheet below,
     so they read as one surface) and the logo steps aside — only the X stays.
     The logo fades rather than display:none so open/close stays smooth. */
  .logo { transition: opacity .2s ease; }
  .site-header.nav-open {
    background: rgba(253, 251, 251, 0.97);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
  }
  .site-header.nav-open .logo { opacity: 0; pointer-events: none; }

  /* The panel drops out from under the bar. It fades and slides in on
     transform/opacity (compositor-only) — the old 0fr→1fr grid-track
     animation relaid the panel out every frame behind two backdrop blurs,
     which is what made the menu feel juddery on phones. visibility waits
     out the .3s so the closing panel can't be tapped once dismissed. */
  .main-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    transform: translateY(-10px);
    opacity: 0;
    padding: 0;
    display: grid;
    grid-template-rows: 1fr;
    visibility: hidden;
    background: rgba(253, 251, 251, 0.97);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    box-shadow: 0 14px 26px rgba(15, 58, 82, 0.16);
    transition: opacity .3s ease, transform .3s ease, visibility .3s;
  }
  .nav-open .main-nav { opacity: 1; transform: none; visibility: visible; }

  .main-nav > ul {
    min-height: 0;
    overflow: hidden;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }
  .main-nav > ul > li + li,
  .dropdown li + li { border-top: 1px solid rgba(15, 58, 82, 0.09); }

  .main-nav a {
    display: block;
    padding: 1rem 1.25rem;
    font-size: 1.05rem;
  }
  .main-nav a.active { background: #eaf6fb; }

  /* no nested dropdown on mobile — the two extra pages become plain rows */
  .has-dropdown { position: static; }
  .more-btn { display: none; }
  .dropdown {
    display: flex;
    position: static;
    min-width: 0;
    padding: 0;
    background: none;
    border-radius: 0;
    box-shadow: none;
  }
  .dropdown a { padding: 1rem 1.25rem; font-size: 1.05rem; }
}

/* ============================================================
   Palette (sampled from live site):
   headings  #29536E   nav links #4F9EB2   body text #2D3748
   teal band #147C9C   timeline  #5F9FC1   accent red #C54E42
   ============================================================ */

/* ------------------------------------------------------------
   Page sheets — the whole site is one document. The home scene
   stays in the background; the router (site.js) docks the cloud
   veil over it and shows one of these full-width sheets. State
   classes live on <html> so deep links (#about) style correctly
   before first paint:
     html.ab-page            a sheet is open (home view hidden)
     html[data-route="..."]  which sheet
   The veil element itself is built and positioned by site.js
   (z-index 3); sheets sit above it, the header above both.
   ------------------------------------------------------------ */
#page-view {
  position: relative;
  z-index: 4;
  /* the hero is 72.168% of the page width tall; pulling the view up by
     42.17% starts the sheets 30% down — inside the cloud whites, just
     under the nav, matching the live site's content position */
  margin-top: -42.17%;
  display: none;
}
.page-sheet { display: none; }

html.ab-page #home-view { display: none; }
html.ab-page #page-view { display: block; }

/* Flight state (html.ab-flying, set by site.js during the home -> inner
   rise): the home sections stay visible beneath the climbing veil instead of
   blinking to white the moment navigation starts, and the incoming sheet is
   overlaid at exactly its landing offset (site.js measures the docked layout
   and sets the inline top) so the swap to the docked state moves nothing. */
html.ab-flying #page-view {
  display: block;
  position: absolute;
  left: 0;
  right: 0;
  margin-top: 0;
}
html[data-route="watch"] #page-watch,
html[data-route="gallery"] #page-gallery,
html[data-route="about"] #page-about,
html[data-route="contact"] #page-contact,
html[data-route="library-shows"] #page-library-shows,
html[data-route="birthday-parties"] #page-birthday-parties,
html[data-route="festivals-austin"] #page-festivals-austin { display: block; }

@keyframes ab-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}


.page-content {
  max-width: 1080px;
  margin: 0 auto;
  padding: 40px 32px 80px;
}

/* ------------------------------------------------------------
   HOME — intro section
   ------------------------------------------------------------ */
.intro {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 120px;
  max-width: 1400px;
  margin: 0 auto;
  padding: 130px 32px 90px;
}
.intro .circle-photo { flex: 0 0 500px; }
.intro .circle-photo img {
  width: 500px; height: 500px;
  object-fit: cover;
  border-radius: 50%;
}
.intro h2 {
  font-size: 46px;
  line-height: 1.35;
  max-width: 620px;
  text-align: center;
  color: #33526C;
}

/* ------------------------------------------------------------
   HOME — photo carousel
   ------------------------------------------------------------ */
.carousel {
  max-width: 1150px;
  margin: 0 auto;
  padding: 20px 32px 40px;
}
.carousel-stage {
  display: flex;
  align-items: center;
  gap: 10px;
}
.carousel-viewport {
  flex: 1 1 auto;
  overflow: hidden;
}
.carousel-slider {
  display: flex;
  gap: 24px;
  /* Frames carry their own square height. Without this, flex's default
     `stretch` would pull every frame to the tallest one's height, leaving a
     blank strip under any image that didn't fill it. */
  align-items: flex-start;
}
/* Square tiles. The width comes from JS; the 1:1 ratio lives on the FRAME (not
   the img) so the tile is square structurally — the image then simply fills it.
   Putting the ratio on the image alone meant any rule that touched the image's
   height could collapse the whole tile back to the photo's natural portrait
   shape, which is what produced the ragged, uneven row. */
.carousel-slider .photo-frame {
  flex: 0 0 auto;      /* exact width set in JS */
  aspect-ratio: 1 / 1;
}
.carousel-slider .photo-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;   /* fills the square edge-to-edge, no letterbox bars */
}
/* Slider nav arrows — shared by the photo carousel and the testimonial
   slider so both read identically: main-copy font, big chevrons, main-nav
   blue palette, same hover motion + timing. Color/position differ per
   backdrop (blue-on-white carousel, white-on-blue testimonial band). */
.carousel-arrow,
.t-arrow {
  font-family: inherit;          /* 'Chelsea Market', 'Quicksand', cursive */
  font-size: 3.4rem;
  line-height: 1;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0 .12em;
  user-select: none;
  transition: color .2s ease, transform .2s ease;
}
.carousel-arrow { flex: none; color: #0f3a52; }   /* nav default */
.carousel-arrow:active { color: #4F9EB2; }        /* nav active shade */
/* hover-guarded: on touch these stick after a tap (no un-hover exists) */
@media (hover: hover) {
  .carousel-arrow:hover { color: #1b7ea8; }       /* nav hover blue */
  .carousel-arrow.prev:hover { transform: translateX(-3px); }
  .carousel-arrow.next:hover { transform: translateX(3px); }
}

/* Shared hover-zoom frame — home carousel + gallery grid. The frame clips the
   scaled image and hosts a dark veil that fades in on hover. */
.photo-frame {
  position: relative;
  overflow: hidden;
  border-radius: 4px;
}
.photo-frame img {
  display: block;
  width: 100%;
  /* long, gentle ease-out so the zoom drifts in rather than snapping */
  transition: transform 1.2s cubic-bezier(.16,.55,.2,1);
}
.photo-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #000;
  opacity: 0;
  transition: opacity .6s ease;
  pointer-events: none;
}
@media (hover: hover) {
  .photo-frame { cursor: pointer; }
  .photo-frame:hover img { transform: scale(1.08); }
  .photo-frame:hover::after { opacity: .38; }
}
/* Touch devices can't hover, so the script at the foot of index.html pops the
   photo the instant a finger lands on it and releases it again on its own.
   No dark veil here: on desktop it reads as a hover state, but under a finger
   it just dims the photo you reached out to look at.
   The easing is a spring-like ease-out-back: the control point above 1
   (1.56) makes the scale overshoot 1.08 and settle back, so the photo bounces
   up to size rather than just drifting. Safe because every frame clips, so the
   slight overshoot is absorbed by the crop instead of nudging neighbours. */
@media (hover: none), (pointer: coarse) {
  .photo-frame img { transition: transform .85s cubic-bezier(.34,1.56,.64,1); }
  .photo-frame.is-zoomed img { transform: scale(1.08); }
  /* Gallery tiles release at the floating cloud header, same as the timeline:
     the script tags a frame .at-header once its top passes under the cloud
     bank, and the zoom lets go even if its timed pop hasn't finished. */
  .photo-frame.is-zoomed.at-header img { transform: none; }

  /* Gallery scroll-wave: the JS repaints each tile's scale every frame as a
     pure function of scroll position, so the transform must NOT be eased by a
     transition (the base rule's 1.2s drift would smear frame-perfect motion
     into lag). Opacity keeps its load fade-up; will-change holds each tile on
     its own compositing layer so the wave stays at frame rate. */
  .gallery-grid .photo-frame img {
    transition: opacity .6s ease;
    will-change: transform;
  }
}

/* Timeline photo hover-zoom — same easing and scale as the gallery, without
   the dark overlay. Applied everywhere the images live: single-photo entries,
   the section-7 poster, and each carousel's active slide. The transition sits
   on the img so a repaint isn't triggered on siblings, and hover targets are
   the wrappers that already clip (mobile) or the img directly (desktop, no
   wrapper — the image simply scales outward, safely within the 18px gap
   between siblings). Pointer devices only, so a tap doesn't leave a scaled
   photo stuck on mobile. */
.timeline-entry .t-photos > img,
.timeline-entry .t-photos.carousel .t-frame > img,
.timeline-entry .t-poster > img {
  transition: transform 1.2s cubic-bezier(.16,.55,.2,1);
}
@media (hover: hover) and (pointer: fine) {
  .timeline-entry .t-photos > img,
  .timeline-entry .t-poster,
  .timeline-entry .t-photos.carousel .t-frame { cursor: pointer; }
  .timeline-entry .t-photos > img:hover,
  .timeline-entry .t-poster:hover > img,
  .timeline-entry .t-photos.carousel .t-frame:hover > img.is-active {
    transform: scale(1.08);
  }
}
/* Touch equivalent. Here .is-zoomed lands on the whole .timeline-entry rather
   than on an individual photo — the trigger is the SECTION reaching the middle
   of the screen, caption and all, so the photo and its arrow light up together
   exactly as they do when the entry is hovered on desktop.
   The carousel's opacity fade is preserved alongside the transform so
   cross-fading between slides still works. */
@media (hover: none), (pointer: coarse) {
  .timeline-entry .t-photos > img,
  .timeline-entry .t-poster > img {
    transition: transform .85s cubic-bezier(.34,1.56,.64,1);
  }
  .timeline-entry .t-photos.carousel .t-frame > img {
    transition: opacity .55s ease, transform .85s cubic-bezier(.34,1.56,.64,1);
  }
  .timeline-entry.is-zoomed .t-photos > img,
  .timeline-entry.is-zoomed .t-poster > img,
  .timeline-entry.is-zoomed .t-photos.carousel .t-frame > img.is-active {
    transform: scale(1.08);
  }
  /* Individual release at the header: the script tags the photo block (or
     poster) with .at-header the moment its top edge slides under the floating
     cloud bar, and the zoom lets go there even while the section itself is
     still inside the middle band. Later in the block than the rules above, so
     it wins at equal-or-higher specificity. */
  .timeline-entry.is-zoomed .t-photos.at-header > img,
  .timeline-entry.is-zoomed .t-poster.at-header > img,
  .timeline-entry.is-zoomed .t-photos.carousel.at-header .t-frame > img.is-active {
    transform: none;
  }

  /* Same golden glow the desktop hover lifts behind the arrow. Every mobile
     arrow renders at 78px, so they all get the desktop rule's SMALL falloff —
     the wide-radius variant is tuned for the 165px desktop arrows and would
     balloon at this size. */
  .timeline-entry .gold-arrow {
    transition: filter .8s ease;
    /* Same WebKit quirk the desktop hover rules guard against: without a
       standing compositing layer the browser promotes the arrow only for the
       life of the filter transition, then demotes it and re-rasterises the
       glow into the element's ordinary paint bounds — which clips the halo
       into a hard invisible rectangle the moment the fade lands. */
    will-change: filter;
  }
  .timeline-entry.is-zoomed .gold-arrow {
    filter:
      drop-shadow(0 0 1px rgba(255, 236, 176, .95))
      drop-shadow(0 0 5px rgba(250, 212, 88, .60))
      drop-shadow(0 0 11px rgba(245, 197, 48, .32))
      drop-shadow(0 0 24px rgba(240, 186, 30, .16));
  }
  /* The arrow's own header release — same mechanism as the photos above. */
  .timeline-entry.is-zoomed .gold-arrow.at-header { filter: none; }
}
.carousel-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 18px;
}
.carousel-dots button {
  width: 9px; height: 9px;
  border-radius: 50%;
  border: none;
  background: #C9D6DE;
  cursor: pointer;
  padding: 0;
}
.carousel-dots button.active { background: #4F9EB2; }

/* ------------------------------------------------------------
   HOME — Concerts / Classes
   ------------------------------------------------------------ */
.programs {
  max-width: 1160px;   /* content width matches the live site's 1095px at 1280 */
  margin: 0 auto;
  padding: 30px 32px 60px;
}
.programs h2 {
  font-size: 34px;
  margin: 40px 0 14px;
}
/* Concerts row: the mirror of Classes — heading centred over the blob on the
   left, one photo on the right. The blob is capped at its live rendered width
   so it never balloons, and the photo column is centred against it. */
.row-concerts .program-row {
  align-items: center;
  gap: 4%;
}
.row-concerts .blob-col { flex: 0 0 44%; }
.row-concerts .blob-col h2 {
  color: #33526C;
  font-size: 48px;
  text-align: center;
  margin: 0 0 24px;
}
.row-concerts .blob-col .blob {
  width: 100%;
  max-width: 518px;
  margin: 0 auto;
}
.row-concerts .photos { flex: 0 0 52%; }
.row-concerts .photos img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: right center;
}

/* Classes row: two photos stepping down the left, heading centred over the
   blob on the right. Proportions taken from the live site at 1280px, where
   the photos are a matched 450x338 crop offset 37px from each other. */
.row-classes .program-row {
  align-items: flex-start;
  gap: 8.2%;
}
.row-classes .photos {
  flex: 0 0 44.4%;
  gap: 16px;
}
.row-classes .photos img {
  width: 92.4%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
.row-classes .photos img + img { margin-left: 7.6%; }

.row-classes .blob-col { flex: 0 0 47.3%; }
.row-classes .blob-col h2 {
  color: #941E13;
  font-size: 48px;
  text-align: center;
  margin: 0 0 18px;
}
.row-classes .blob-col .blob { width: 100%; }

.program-row {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 40px;
  margin-bottom: 30px;
}
.program-row .blob { flex: 0 0 44%; width: 44%; min-width: 0; }
.program-row .photos {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 26px;
}
.program-row .photos img { border-radius: 15px; }

/* The blob artwork is painted on an opaque near-white (254,254,254) rather
   than transparency, which drew a faint box against the page's #FDFBFB.
   `darken` keeps the per-channel minimum, so that 254 loses to the page's
   253/251/251 and disappears *exactly*, while every colour in the blob is
   darker than the page and so passes through untouched. (Multiply also hides
   it, but leaves a 1-per-channel residue.)
   Block display so the auto margins can actually centre it — it is an inline
   image by default, which was pinning it to the left edge. */
.blob {
  display: block;
  margin-inline: auto;
  mix-blend-mode: darken;
}

/* 1-inch desktop gap between Concerts and Classes (mobile stacks them so no gap needed) */
@media (min-width: 861px) {
  .row-classes { margin-top: 96px; }
}

/* ------------------------------------------------------------
   HOME — testimonials band
   ------------------------------------------------------------ */
.testimonials {
  background: #147C9C;
  color: #fff;
  padding: 60px 70px;
  position: relative;
  text-align: center;
}
.testimonials .quote-mark {
  font-size: 64px;
  line-height: 1;
  opacity: .85;
  font-family: Georgia, serif;
}
.testimonial blockquote {
  font-size: 24px;
  line-height: 1.6;
  font-style: italic;
  max-width: 760px;
  margin: 16px auto 22px;
}
.testimonial cite {
  font-style: normal;
  font-size: 17px;
  letter-spacing: .5px;
}
.testimonial { display: none; }
/* Fade in at the same duration/easing as the carousel glide, so the two
   sliders feel equally paced. */
.testimonial.active {
  display: block;
  animation: ab-fade-in .8s cubic-bezier(.22, .61, .36, 1);
}

.t-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  color: #fff;                 /* white on the blue band */
}
.t-arrow:active { color: #9fd4e4; }
.t-arrow.prev { left: 14px; }
.t-arrow.next { right: 14px; }
/* hover-guarded: taps on touch left the chevron stuck displaced */
@media (hover: hover) {
  .t-arrow:hover { color: #bfe3ee; } /* lightens on hover, echoing the nav shift */
  .t-arrow.prev:hover { transform: translateY(-50%) translateX(-3px); }
  .t-arrow.next:hover { transform: translateY(-50%) translateX(3px); }
}

.t-dots { display: flex; justify-content: center; gap: 8px; margin-top: 26px; }
.t-dots button {
  width: 9px; height: 9px; border-radius: 50%;
  border: 1px solid #fff; background: transparent; cursor: pointer; padding: 0;
}
.t-dots button.active { background: #fff; }

/* ------------------------------------------------------------
   Contact blob + footer
   ------------------------------------------------------------ */
.contact-cta {
  display: flex;
  justify-content: center;
  padding: 55px 20px 30px;
}
.contact-cta a {
  position: relative;
  display: inline-block;
  text-decoration: none;
}
.contact-cta img { width: 300px; display: block; }
.contact-cta .cta-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-family: 'Chelsea Market', 'Quicksand', cursive;
  font-size: 26px;
  letter-spacing: 1px;
  text-transform: uppercase;
  pointer-events: none;
}
/* The whole link scales — art and label together, so the text stays in
   register with the blob instead of drifting off it. Hover-guarded: on touch
   a tap would leave the link stuck at 1.04 with no way to un-hover. */
@media (hover: hover) {
  .contact-cta a { transition: transform 0.2s ease; }
  .contact-cta a:hover { transform: scale(1.04); }
}

.site-footer {
  display: grid;
  grid-template-columns: 1fr auto;   /* copyright left, socials right */
  align-items: center;
  max-width: 1150px;
  margin: 0 auto;
  padding: 30px 40px 40px;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 13px;
  color: #2D3748;
}
.site-footer > span { justify-self: start; }
.site-footer .socials { justify-self: end; }

/* Newsletter signup: its own full-width row above the copyright line. */
.footer-subscribe {
  grid-column: 1 / -1;
  justify-self: start;
  max-width: 420px;
  width: 100%;
  margin-bottom: 22px;
  padding-bottom: 22px;
  border-bottom: 1px solid rgba(45, 55, 72, .14);
}
.footer-subscribe label {
  display: block;
  font-family: 'Quicksand', Arial, sans-serif;
  font-size: 14px;
  font-weight: 600;
  color: #2D3748;
  margin-bottom: 8px;
}
.subscribe-row { display: flex; gap: 8px; }
.footer-subscribe input[type="email"] {
  flex: 1;
  min-width: 0;                       /* lets the field shrink inside the flex row */
  padding: 9px 12px;
  border: 1px solid #CBD5E0;
  border-radius: 4px;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 14px;
  background: #fff;
}
.footer-subscribe button {
  background: #147C9C;
  color: #fff;
  font-family: 'Chelsea Market', cursive;
  font-size: 14px;
  border: none;
  border-radius: 4px;
  padding: 9px 20px;
  cursor: pointer;
  white-space: nowrap;
}
.footer-subscribe button:hover { background: #29536E; }

.socials { display: flex; gap: 10px; }
.socials a {
  width: 30px; height: 30px;
  border-radius: 50%;
  background: #111;
  display: flex;
  align-items: center;
  justify-content: center;
}
.socials svg { width: 15px; height: 15px; fill: #fff; }

/* ------------------------------------------------------------
   WATCH page
   ------------------------------------------------------------ */
.videos-list {
  display: flex;
  flex-direction: column;
  gap: 50px;
  align-items: center;
}
.video-embed {
  width: 100%;
  max-width: 760px;
  aspect-ratio: 16 / 9;
}
.video-embed iframe { width: 100%; height: 100%; border: 0; }
.channel-link {
  display: flex;
  align-items: center;
  gap: 10px;
  color: #4F9EB2;
  font-size: 19px;
  margin-top: 10px;
}
.channel-link svg { width: 30px; height: 30px; fill: #c00; }

/* ------------------------------------------------------------
   GALLERY page
   ------------------------------------------------------------ */
/* Three masonry columns; site.js deals the frames into .gallery-col divs
   round-robin (1-4-7…, 2-5-8…, 3-6-9…) to match the live photos page. */
.gallery-grid {
  display: flex;
  gap: 28px;
  align-items: flex-start;
}
.gallery-col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 28px;
}
.gallery-grid .photo-frame {
  width: 100%;
  border-radius: 12px;
  /* A soft wash of the timeline blue behind each photo. While an image is
     still arriving its frame reads as a quiet tinted card instead of a bare
     white hole in the page — the "white flash" during the sheet reveal. */
  background: rgba(95, 159, 193, .12);
}
/* Images fade up once decoded (the JS tags still-loading images with
   .img-loading and lifts it on load). transform is restated so this
   transition doesn't clobber the shared hover-zoom drift. */
.gallery-grid .photo-frame img {
  transition: opacity .6s ease, transform 1.2s cubic-bezier(.16,.55,.2,1);
}
.gallery-grid .photo-frame img.img-loading { opacity: 0; }
/* Coarse pointers: the scroll-wave JS repaints each tile's transform every
   frame, so transform must stay un-eased. The identical selector above
   (same specificity, later in file than the block at ~632) had been silently
   re-adding the 1.2s drift and smearing the wave into lag — this re-assertion
   must stay BELOW the rule above to win the cascade. */
@media (hover: none), (pointer: coarse) {
  .gallery-grid .photo-frame img { transition: opacity .6s ease; }
}

/* ------------------------------------------------------------
   ABOUT page
   ------------------------------------------------------------ */
/* Intro matches the live about page: heading left, then light Open Sans
   paragraphs in the sky blue (#26a2c7), no photo. */
.about-intro {
  margin: 0 auto;
  text-align: left;
}
.about-intro h1 { font-size: 40px; margin-bottom: 34px; }
.about-intro p {
  font-family: 'Open Sans', Arial, Helvetica, sans-serif;
  font-weight: 300;
  font-size: 18px;
  line-height: 2;
  letter-spacing: 0.1px;
  color: #26a2c7;
  margin-bottom: 30px;
}
.down-arrow {
  width: 46px;
  margin: 10px auto 26px;
}

.timeline {
  background: #5F9FC1;
  color: #fff;
  padding: 60px 0 70px;
  margin-top: 50px;
}
.timeline h2 {
  color: #29536E;                /* dark navy, matching the mock */
  text-align: left;
  max-width: 1080px;             /* matches old .about-full width so heading placement is unchanged */
  margin: 0 auto 44px;
  padding: 0 32px 0 16%;
}
.timeline-entry {
  display: flex;
  align-items: center;
  gap: 44px;
  max-width: 1080px;             /* matches old .about-full width so entry placement is unchanged */
  margin: 0 auto 144px;
  padding: 0 32px;
}
/* photos and captions swap sides every other entry */
.timeline .timeline-entry:nth-of-type(even) { flex-direction: row-reverse; }
.timeline-entry .t-caption {
  flex: 0 0 30%;
  font-size: 19px;
  line-height: 1.6;
}
/* Hovering anywhere in a timeline entry — its caption, its arrow or its
   photos — lifts a sheer golden glow behind that entry's arrow. Stacked
   drop-shadows are used rather than a box-shadow so the light follows the
   drawn line's own silhouette instead of haloing a rectangle: a tight warm
   core, then two progressively wider, fainter blooms. Pointer devices only,
   so it can't stick on after a tap. */
@media (hover: hover) and (pointer: fine) {
  .timeline-entry .gold-arrow {
    transition: filter .45s ease;
    /* Hold the arrow on its own compositing layer permanently. Otherwise the
       browser promotes it only for the life of the filter transition and then
       demotes it, re-rasterising the glow into the element's ordinary paint
       bounds — so the halo renders correctly while the fade runs and snaps
       into a hard rectangle the moment it lands (~.45s in, exactly when it
       "boxes in"). Declaring the intent up front keeps one stable
       rasterisation either side of the transition. WebKit needs this; Chrome
       does not demote and renders identically with or without it. */
    will-change: filter;
    transform: translateZ(0);
  }
  /* the mirrored arrows must keep their flip alongside the promotion */
  .timeline-entry .gold-arrow.flip,
  .timeline-entry .gold-arrow.sm.flip {
    transform: scaleX(-1) translateZ(0);
  }
  /* Four stops, each roughly doubling in radius while its opacity tapers to
     almost nothing. The previous outermost bloom sat at .32 opacity, which
     against the flat blue read as a defined boundary — the glow looked like it
     stopped rather than faded. Ending on .10 removes that edge.
     Deliberately kept to four: every drop-shadow is another full blur pass,
     and WebKit rasterises heavy filter stacks at reduced resolution, which
     shows up as banding and hard edges on iOS even where Chrome is smooth. */
  .timeline-entry:hover .gold-arrow {
    filter:
      drop-shadow(0 0 2px rgba(255, 236, 176, .95))
      drop-shadow(0 0 8px rgba(250, 212, 88, .60))
      drop-shadow(0 0 20px rgba(245, 197, 48, .32))
      drop-shadow(0 0 46px rgba(240, 186, 30, .16));
  }
  /* The arrows render anywhere from 65px to 217px wide, so one fixed radius is
     a whisper on the big ones and a balloon on the small ones. The little
     arrows get the same falloff at roughly half scale. */
  .timeline-entry:hover .gold-arrow.sm,
  .timeline-entry.tuck:hover .gold-arrow {
    filter:
      drop-shadow(0 0 1px rgba(255, 236, 176, .95))
      drop-shadow(0 0 5px rgba(250, 212, 88, .60))
      drop-shadow(0 0 11px rgba(245, 197, 48, .32))
      drop-shadow(0 0 24px rgba(240, 186, 30, .16));
  }
}
@media (prefers-reduced-motion: reduce) {
  .timeline-entry .gold-arrow { transition: none; }
}

/* hand-drawn golden arrows below each caption, pointing at the photos */
.t-caption .gold-arrow {
  display: block;
  width: 110px;
  max-width: none;
  margin: 30px auto 0;
}
/* small arrow, pushed toward the photos so its tip meets the first one */
.gold-arrow.sm { width: 42%; max-width: 150px; margin-right: 0; margin-left: auto; }
.gold-arrow.flip { transform: scaleX(-1); }
.gold-arrow.sm.flip { transform: scaleX(-1); }

/* "tuck" entry: the caption runs wide across the top; below it the arrow
   sits on the left and the photo row starts under the text's span. */
.timeline-entry.tuck {
  display: grid;
  /* left column hugs the arrow so the photos start right beside it */
  grid-template-columns: auto 1fr;
  column-gap: 34px;
  align-items: start;
}
.timeline-entry.tuck .t-caption {
  grid-column: 1 / -1;
  flex-basis: auto;
  max-width: 720px;
  margin-bottom: 6px;
}
.timeline-entry.tuck .gold-arrow.sm {
  grid-column: 1;
  justify-self: start;
  width: 110px;
  margin: 30px 0 0 44px;
}
.timeline-entry.tuck .t-photos { grid-column: 2; margin-top: 22px; }

/* Section 6: the tuck layout mirrored — caption runs across the top aligned
   right, the arrow sits lower-right, and the photo row hugs the right beneath
   the text. Columns are flipped (photos | arrow) from the standard tuck. */
.timeline-entry.tuck-right {
  grid-template-columns: 1fr auto;
  row-gap: 16px;               /* tight gap between caption and photo row */
}
.timeline-entry.tuck-right .t-caption {
  justify-self: end;
  text-align: right;
}
.timeline-entry.tuck-right .gold-arrow.sm {
  grid-column: 2;
  grid-row: 2;
  justify-self: end;
  margin: 0 44px 0 0;          /* no top margin — photos and arrow share the row */
}
.timeline-entry.tuck-right .t-photos {
  grid-column: 1;
  grid-row: 2;                 /* share the row with the arrow, not fall into row 3 */
  justify-content: flex-end;   /* photos cluster toward the caption/arrow side */
  margin-top: 0;               /* sit right beneath the caption */
  align-self: start;           /* don't stretch to match the arrow row height */
}
/* Both section-6 photos share one aspect ratio so they render at identical
   heights, keeping the row visually level. `cover` fills the frame without
   distortion. */
.timeline-entry.tuck-right .t-photos img {
  aspect-ratio: 4 / 3;
  height: auto;
  object-fit: cover;
}
/* Nudge the arrow up too so it aligns with the raised photo row. */
.timeline-entry.tuck-right .gold-arrow.sm {
  margin-top: 0;
}

/* entry 1: caption sits mid-left with a large loop arrow below it; one big
   photo rides high on the right */
.timeline-entry.t1 {
  display: grid;
  grid-template-columns: 1fr 42%;
  column-gap: 56px;
  align-items: start;
}
.timeline-entry.t1 .t-caption {
  flex-basis: auto;
  margin-top: 74px;      /* caption + arrow stay within the photo's vertical span */
  padding-left: 18%;
}
.timeline-entry.t1 .gold-arrow {
  width: 110px;
  max-width: none;
  margin: 32px auto 0;
}
.timeline-entry.t1 .t-photos img {
  width: 100%;
  max-height: none;
}

/* entry 7 (Amber Nick Radio Hour): the tall poster stands alone on the left,
   and the two landscape photos stack on the right. Desktop only — mobile is
   handled by the JS poster carousel. */
@media (min-width: 1001px) {
  .timeline-entry.t7 .t-photos {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-rows: auto auto;
    gap: 18px;
    align-items: start;
  }
  .timeline-entry.t7 .t-photos img {
    flex: none;
    max-width: none;
    width: auto;
  }
  .timeline-entry.t7 .t-photos img:nth-child(1) {
    grid-column: 1;
    grid-row: 1 / 3;             /* poster spans both stacked rows */
    height: 100%;
    width: auto;
    max-height: 420px;
  }
  .timeline-entry.t7 .t-photos img:nth-child(2),
  .timeline-entry.t7 .t-photos img:nth-child(3) {
    grid-column: 2;
    width: 100%;
    max-width: 320px;
    height: auto;
  }
  .timeline-entry.t7 .t-photos img:nth-child(2) { grid-row: 1; }
  .timeline-entry.t7 .t-photos img:nth-child(3) { grid-row: 2; }
}

/* entry 3: single portrait photo on the right; caption + arrow on the left.
   Equal columns give a balanced look despite the one-photo layout. */
.timeline-entry.t3 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 56px;
  align-items: center;
}
.timeline-entry.t3 .t-caption {
  flex-basis: auto;
  padding-left: 0;
  text-align: center;
  max-width: 340px;
  justify-self: end;   /* pull caption block toward the photo side */
}
.timeline-entry.t3 .gold-arrow {
  margin: 30px auto 0;   /* re-center under the caption */
}
.timeline-entry.t3 .t-photos img {
  width: 100%;
  max-height: none;
}

/* entry 2: two diagonally staggered photos on the left; caption and a small
   arrow sit at mid-height on the right */
.timeline-entry.t2 { align-items: flex-start; gap: 110px; }
.timeline-entry.t2 .t-caption { margin-top: 120px; }
.timeline-entry.t2 .gold-arrow { width: 110px; margin: 30px 0 0 6px; }
.timeline-entry.t2 .t-photos img:nth-child(2) { margin-top: 150px; }
/* Sections 4 and 8: left-align arrow so it sits directly under the text
   (section 3 handled by the .t3 rule above, which centers the arrow) */
.timeline-entry:nth-of-type(4) .gold-arrow,
.timeline-entry:nth-of-type(8) .gold-arrow {
  margin: 30px 0 0 0;
}
/* Sections 8 and 10: pin both photos to a shared aspect ratio so their
   bottoms level with their tops */
.timeline-entry:nth-of-type(8) .t-photos img,
.timeline-entry:nth-of-type(10) .t-photos img {
  aspect-ratio: 3 / 2;
  height: auto;
  object-fit: cover;
}
/* Section 10: use the small (110px) arrow, matching section 6 */
.timeline-entry:nth-of-type(10) .gold-arrow {
  width: 110px;
  max-width: none;
}
/* Sections 5 and 9: match section 6's tight caption-to-photos spacing (22px) */
.timeline-entry:nth-of-type(5),
.timeline-entry:nth-of-type(9) { row-gap: 16px; }
.timeline-entry:nth-of-type(5).tuck .gold-arrow.sm,
.timeline-entry:nth-of-type(9).tuck .gold-arrow.sm { margin-top: 0; }
.timeline-entry:nth-of-type(5).tuck .t-photos,
.timeline-entry:nth-of-type(9).tuck .t-photos { margin-top: 0; }

/* Last timeline entry: caption + arrow centered above the photo */
.timeline .timeline-entry.t-final {
  flex-direction: column;
  align-items: center;
}
.timeline-entry.t-final .t-caption {
  flex: none;
  text-align: center;
  max-width: 420px;
}
.timeline-entry.t-final .t-photos {
  flex: none;
  justify-content: center;
}
/* Desktop: the arrow is rotated 90deg, so its drawn tip hangs below the box
   the layout reserves for it. Drop the photo to clear that overhang — without
   this the photo's top edge rides up over the arrowhead as it scales up. */
@media (min-width: 861px) {
  .timeline-entry.t-final .t-photos { margin-top: 34px; }
}
.timeline-entry.t-final .t-photos img {
  max-width: 480px;
  width: 100%;
}
/* Rotate the last entry's arrow to point downward toward the photo.
   The arrow renders 110px wide × ~34px tall, so a 90deg rotation makes it
   visually ~110px tall but its layout box remains 34px tall. That overflow
   (~38px above and below the layout box) would bleed into the caption text
   above and crowd the photo below — so extra top/bottom margin reserves the
   room the rotated shape actually occupies. */
.timeline-entry.t-final .gold-arrow {
  transform: rotate(90deg) translateZ(0);
  margin: 60px auto 20px;
}
@media (hover: hover) and (pointer: fine) {
  .timeline-entry.t-final .gold-arrow {
    transform: rotate(90deg) translateZ(0);
  }
  .timeline-entry.t-final:hover .gold-arrow {
    transform: rotate(90deg) translateZ(0);
  }
}
.timeline-entry .t-photos {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  justify-content: flex-start;   /* tuck photos toward the caption/arrow side */
  align-items: flex-start;       /* keep natural heights; don't stretch to the tallest */
}
.timeline-entry .t-photos img {
  border-radius: 14px;
  /* equal widths sharing one row; min-width:0 stops the large intrinsic widths
     from forcing a wrap, max-width keeps a lone photo from ballooning */
  flex: 1 1 0;
  min-width: 0;
  max-width: 340px;
  height: auto;
}

/* Timeline wrapper escapes .page-content's padding, then extends another
   48px (0.5 inch) on each side so the blue band reads wider without going
   full-bleed. Entries inside are max-width capped + auto-margin centered, so
   their placement is unchanged — only the background grows. */
.about-full { margin: 0 -80px; }

/* ------------------------------------------------------------
   Timeline scroll feel — per-section snap
   Scoped to the About route (site.js stamps data-route on <html>).
   `mandatory` so a scroll ALWAYS settles on a section — proximity left
   readers stranded between sections with the caption nowhere near the top.
   Every legal resting place is declared explicitly so mandatory can't trap
   anyone: the hero keeps the document top legal (the router lands routes at
   scrollY 0), the intro is its own stop (taller than the viewport on phones,
   and snap areas taller than the snapport stay freely scrollable per spec),
   each timeline entry aligns its caption below the header, and the footer
   keeps the very bottom reachable. Strong flings still sail past several
   sections (no scroll-snap-stop) — momentum decides how far you travel.
   ------------------------------------------------------------ */
html[data-route="about"] { scroll-snap-type: y mandatory; }
.hero { scroll-snap-align: start; }              /* document top stays legal */
.about-intro {
  scroll-snap-align: start;
  scroll-margin-top: 48px;
}
#page-view .site-footer { scroll-snap-align: end; }  /* page bottom stays legal */
/* The band's own title needs a stop of its own, or the scroll that leaves the
   intro lands on entry 1 and carries "Time-Line History" up out of sight
   (behind the cloud header on a phone). */
.timeline h2 {
  scroll-snap-align: start;
  scroll-margin-top: 48px;
}
.timeline-entry {
  scroll-snap-align: start;
  /* where a settled section's caption lands from the top of the viewport;
     the mobile block deepens this to clear the floating cloud header */
  scroll-margin-top: 48px;
}
/* A section taller than the screen needs more than one resting place —
   otherwise the next snap jumps clean past whatever sat below the fold.
   Section 7 ("Amber Nick Radio Hour") stacks a standalone poster above its
   carousel, so it gets a second stop on the carousel itself: one scroll to
   read the caption and poster, the next to reach the photos. */
.timeline-entry .t-photos.carousel-landscape {
  scroll-snap-align: start;
  scroll-margin-top: 48px;
}
/* The intro runs several screens on a phone. Each block becomes a stop, and
   the zero-height .snap-tail sentinel makes the intro's bottom edge legal so
   the closing lines are always reachable. */
.about-intro > h1,
.about-intro > p {
  scroll-snap-align: start;
  scroll-margin-top: 48px;
}
.about-intro .snap-tail { scroll-snap-align: end; }
@media (prefers-reduced-motion: reduce) {
  html[data-route="about"] { scroll-snap-type: none; }
}

/* ------------------------------------------------------------
   CONTACT page
   ------------------------------------------------------------ */
.contact-wrap {
  display: flex;
  gap: 60px;
  align-items: flex-start;
  max-width: 980px;
  margin: 0 auto;
}
.contact-left { flex: 0 0 40%; text-align: center; }
.contact-left h1 { font-size: 34px; margin-bottom: 26px; }
.squiggle { position: relative; width: 320px; margin: 0 auto; }  /* centered under the heading */
.squiggle img { width: 100%; display: block; }
.squiggle .email {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 15px;
  font-family: Arial, Helvetica, sans-serif;
  transform: translateY(-7%);   /* sit in the fuller upper-middle of the cloud */
}

.contact-form { flex: 1; }
.form-row { display: flex; gap: 16px; }
.form-row > div { flex: 1; }
.contact-form label {
  display: block;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 14px;
  color: #2D3748;
  margin: 16px 0 6px;
}
.contact-form input,
.contact-form textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid #CBD5E0;
  border-radius: 4px;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 15px;
  background: #fff;
  box-sizing: border-box;
}
.contact-form textarea { min-height: 140px; resize: vertical; }
.contact-form button {
  margin-top: 22px;
  background: #147C9C;
  color: #fff;
  font-family: 'Chelsea Market', cursive;
  font-size: 18px;
  border: none;
  border-radius: 6px;
  padding: 10px 34px;
  cursor: pointer;
}
.contact-form button:hover { background: #29536E; }

/* Newsletter opt-in — a checkbox, so it sits inline rather than stacking
   like the other labels. */
.contact-form .opt-in {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 18px 0 0;
  cursor: pointer;
}
.contact-form .opt-in input {
  width: auto;
  margin: 0;
  accent-color: #147C9C;
  flex: 0 0 auto;
}
.contact-form .opt-in span { font-size: 14px; }

/* Brevo's spam trap. Off-screen rather than display:none — some bots skip
   fields they can tell are hidden. */
.hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px !important;
  height: 1px !important;
  opacity: 0 !important;
}

/* Submission feedback for both forms. */
.form-status {
  margin: 12px 0 0;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 14px;
  line-height: 1.45;
  min-height: 1em;
}
.form-status.ok  { color: #1B6B4A; }
.form-status.err { color: #9B2C2C; }

/* ------------------------------------------------------------
   Library shows / Birthday parties / Festivals & events
   ------------------------------------------------------------ */
/* A big rounded photo (or photo stack) beside a headline and body copy,
   top-aligned. These run wider than the standard sheet so the photo reads
   at full size. */
#page-library-shows .page-content,
#page-birthday-parties .page-content,
#page-festivals-austin .page-content { max-width: 1260px; }

.simple-page {
  display: flex;
  align-items: flex-start;
  gap: 70px;
  max-width: 1180px;
  margin: 0 auto;
}
.simple-page img {
  flex: 0 0 45%;
  /* without this, flexbox's automatic min-width (the photo's 1600px intrinsic
     width) floors the image at full container width and crushes the text */
  min-width: 0;
  border-radius: 16px;
  align-self: center;
}
/* Library shows only: three photos (one full-width, two side by side below
   it) instead of the single centered photo the birthday page still uses.
   Top-aligned so the stack lines up with the headline instead of centering
   against the taller text column. */
.simple-page-photos {
  flex: 0 0 45%;
  min-width: 0;
  align-self: flex-start;
}
.simple-page-photos > img {
  width: 100%;
  border-radius: 16px;
  margin-bottom: 20px;
}
.simple-page-photos-row {
  display: flex;
  gap: 20px;
}
.simple-page-photos-row img {
  flex: 1;
  min-width: 0;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: 16px;
}
.simple-page-text {
  flex: 1;
  min-width: 0;
  text-align: left;
}
/* Matches the about page's intro: Chelsea Market headline, light Open Sans
   body copy in sky blue. */
.simple-page-text h1 { font-size: 40px; margin-bottom: 34px; }
.simple-page-text p,
.simple-page-text li {
  font-family: 'Open Sans', Arial, Helvetica, sans-serif;
  font-weight: 300;
  font-size: 18px;
  line-height: 2;
  letter-spacing: 0.1px;
  color: #26a2c7;
}
.simple-page-text p { margin-bottom: 30px; }
.simple-page-text h2 {
  font-size: 26px;
  margin: 10px 0 14px;
}
.simple-page-text ul {
  list-style: none;
  margin: 0 0 30px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.simple-page-text li strong { color: #29536E; }

/* ------------------------------------------------------------
   Responsive — inner pages & home sections
   ------------------------------------------------------------ */
@media (max-width: 860px) {
  .intro { flex-direction: column; gap: 34px; padding-top: 60px; }
  .intro h2 { font-size: 25px; }
  .intro .circle-photo { flex: 0 0 auto; width: min(70vw, 340px); }
  .intro .circle-photo img { width: 100%; height: auto; aspect-ratio: 1 / 1; }
  h2 { font-size: 30px; }

  .carousel-arrow, .t-arrow { font-size: 2.6rem; }

  /* Home photo carousel on a phone (matches the JS one-photo breakpoint of
     700px): the chevrons and their gaps ate ~70px of a 375px screen, leaving
     the photo cramped. Drop them, tighten the side padding, and let the photo
     take the width back — the dots stay as the visible affordance and a
     horizontal swipe steps the strip (see the swipe handler in index.html). */
  @media (max-width: 700px) {
    .carousel-stage .carousel-arrow { display: none; }
    .carousel-stage { gap: 0; }
    .carousel { padding-left: 16px; padding-right: 16px; }
    /* the browser keeps vertical panning; only horizontal intent reaches us,
       so page scrolling is never blocked by the swipe handler */
    .carousel-viewport { touch-action: pan-y; }
  }

  /* gallery flat-stacks on mobile (site.js removes the columns) */
  .gallery-grid { display: block; }
  .gallery-grid .photo-frame { margin-bottom: 18px; }

  /* Concerts & Classes stack the same way on mobile: one edge-to-edge photo
     on top, then the heading, then the blob. The section's own side padding is
     dropped so the photo can bleed to both screen edges; the heading and blob
     set their own widths, so they stay comfortably inset. */
  .programs { padding-left: 0; padding-right: 0; }

  .program-row { flex-direction: column; align-items: center; gap: 0; }
  .program-row .blob { flex: none; width: 82%; }
  .program-row .photos { gap: 0; }
  /* full-bleed, uncropped, square-cornered — the photo is the section's lid */
  .program-row .photos img {
    width: 100%;
    height: auto;
    aspect-ratio: auto;
    border-radius: 0;
  }
  .program-row .blob-col,
  .program-row .photos { flex: none; width: 100%; }
  .program-row .blob-col h2 { font-size: 32px; margin: 26px 0 14px; }
  .program-row .blob-col .blob { width: 82%; margin: 0 auto; }

  /* Concerts' markup puts the blob first (heading left of the photo on
     desktop), so flip the stack to lead with the photo like Classes does. */
  .row-concerts .program-row { flex-direction: column-reverse; }

  /* Classes carries a second photo for the desktop stagger — one lid is enough
     on mobile, matching Concerts. */
  .row-classes .photos img + img { display: none; }

  .testimonials { padding: 50px 34px; }
  .testimonial blockquote { font-size: 19px; }

  .page-content { padding: 30px 20px 60px; }
  /* Cancel the desktop -32px bleed margin to match the 20px mobile padding */
  .about-full { margin-left: -20px; margin-right: -20px; }
  .contact-wrap { flex-direction: column; }
  .simple-page { flex-direction: column; gap: 34px; }
  .simple-page img { flex: none; width: 100%; }
  .simple-page-photos { flex: none; width: 100%; }
  .timeline-entry,
  .timeline .timeline-entry:nth-of-type(even) {
    /* Both selectors listed so the desktop alternating row-reverse rule (which
       has higher specificity than the plain `.timeline-entry` selector) is
       actually overridden on mobile. Without this every second entry stayed in
       a horizontal row, cropping the photo column. */
    flex-direction: column;
    align-items: stretch;
    gap: 20px;
    margin-bottom: 96px;
    /* a settled section's caption lands just under the floating cloud bank
       (the header art reaches ~136px down; see the at-header release JS) */
    scroll-margin-top: 148px;
  }
  /* every snap stop on this route clears the cloud bank the same way */
  .about-intro,
  .about-intro > h1,
  .about-intro > p,
  .timeline h2,
  .timeline-entry .t-photos.carousel-landscape { scroll-margin-top: 148px; }
  .timeline-entry.tuck, .timeline-entry.t1, .timeline-entry.t3 { display: flex; }
  .timeline-entry.t1 .t-caption { margin-top: 0; padding-left: 0; }
  .timeline-entry.t3 .t-caption { padding-left: 0; }
  /* Section 1: stacked on mobile the photo sits below the caption, so the
     arrow is turned a quarter-turn to point down into it rather than off to
     the right, and shrunk to suit. Rotating the existing artwork keeps the
     desktop layout untouched. Because a rotated element still occupies its
     unrotated box, the vertical margins reserve the room the turned arrow
     actually needs, and `margin-inline: auto` centres that box in its
     column. */
  .timeline-entry .gold-arrow {
    /* Every timeline arrow gets the same mobile treatment: small (64px),
       centred under the caption, rotated so it points down into the photo
       below. `!important` is needed across the board because the desktop
       per-entry rules (`.timeline-entry.t1 .gold-arrow`,
       `.timeline-entry.t2 .gold-arrow`, `.timeline-entry.tuck .gold-arrow`)
       have higher specificity than this generic selector, so they'd win
       the width/margin/transform cascade otherwise. */
    width: 64px !important;
    max-width: none !important;
    display: block;
    margin: 36px auto !important;
    transform: rotate(88deg) !important;
  }
  /* Sections 5, 6 and 9 get an additional horizontal mirror layered on top of
     the down-rotation, so their arrows curve the opposite way from the
     others. The arrow still points at the photo below; only the curl of the
     stroke flips left-to-right. (These sections carry the `flip` class for
     desktop, but the rotation above is !important and replaces it, so the
     mirror has to be restated here.) */
  .timeline-entry:nth-of-type(5) .gold-arrow,
  .timeline-entry:nth-of-type(6) .gold-arrow,
  .timeline-entry:nth-of-type(9) .gold-arrow {
    transform: rotate(88deg) scaleX(-1) !important;
  }
  /* Timeline captions read as centred blocks on the phone — the arrow already
     centres visually, the text follows suit. Desktop keeps its natural
     left-align (this rule is inside the mobile media query). */
  .timeline-entry .t-caption { text-align: center; }
  /* Section 6 carries the desktop tuck-right rule (text-align:right,
     justify-self:end) which outranks the generic centre above; restate the
     centring here so its caption matches every other entry on mobile. */
  .timeline-entry.tuck-right .t-caption { text-align: center; justify-self: center; }
  .timeline-entry.t2 { align-items: stretch; gap: 0; }
  .timeline-entry.t2 .t-caption { margin-top: 0; }
  .timeline-entry.t2 .t-photos img:nth-child(2) { margin-top: 0; }
  /* The base .t-photos rule sets `flex: 1` for the desktop side-by-side row.
     In the mobile column layout that leaves the row at its content min-width
     — narrow enough to squash the images down to nothing — so pin it to the
     full column width here. */
  .timeline-entry .t-photos { width: 100%; justify-content: center; }

  /* Multi-photo timeline entries — the two-photo rows in particular — laid out
     side-by-side ran off the right edge of the phone: the second photo was cut
     out of view. Here the .t-photos container becomes a carousel viewport
     (JS adds the `carousel` class after wrapping the images in a track). Photos
     are stacked, and the active slide fades in over the previous one — one
     photo appears at a time rather than sliding across. Desktop is untouched
     — the class is only added under this media query. */
  .timeline-entry .t-photos.carousel {
    display: block;
    width: 100%;
    position: relative;
    /* Reserve the row height by leaning on the natural size of the first
       image (which sits inline via .t-track > img:first-child). The stacked
       siblings are absolutely positioned on top of it.
       Horizontal padding reserves the strip either side of the photo where
       the prev/next chevrons live, so the arrows sit OUTSIDE the frame
       rather than on top of it. */
    padding: 0 34px;
    box-sizing: border-box;
  }
  .timeline-entry .t-photos.carousel .t-track {
    position: relative;
  }
  /* .t-frame is the clipping window. Because it's the first-and-only in-flow
     child of .t-track (aside from absolutely-positioned toggle buttons),
     .t-track's height equals .t-frame's height equals the photo's height. */
  .timeline-entry .t-photos.carousel .t-frame {
    position: relative;
    overflow: hidden;
    border-radius: 14px;
  }
  .timeline-entry .t-photos.carousel .t-frame > img {
    width: 100%;
    height: auto;
    margin: 0;
    display: block;
  }
  /* First image stays in flow so the frame has a height; subsequent images
     stack on top and take their opacity from JS. */
  .timeline-entry .t-photos.carousel .t-frame > img ~ img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .timeline-entry .t-photos.carousel .t-frame > img {
    opacity: 0;
    /* Bouncy spring on the zoom (see the touch block above). This mobile-width
       rule sits later in the file and outranks it, so the curve has to be
       restated here or the flat ease-out would win. */
    transition: opacity .55s ease, transform .85s cubic-bezier(.34,1.56,.64,1);
  }
  .timeline-entry .t-photos.carousel .t-frame > img.is-active {
    opacity: 1;
  }
  .timeline-entry .t-photos.carousel .t-dots {
    display: flex;
    justify-content: center;
    gap: 8px;
    margin-top: 12px;
  }
  .timeline-entry .t-photos.carousel .t-dots > button {
    width: 8px; height: 8px; padding: 0;
    border: 0; border-radius: 50%;
    background: rgba(255, 255, 255, .5);
    cursor: pointer;
    transition: background .25s ease;
  }
  .timeline-entry .t-photos.carousel .t-dots > button[aria-current="true"] {
    background: #ffffff;
  }
  /* Side toggles — bare chevrons in the site's shared arrow style, matching
     the home-page photo carousel and the testimonials slider (same font, same
     line-height, same nudge-on-press motion). Photos vary in tone, so the
     chevron is white with a soft shadow to stay legible on light frames. */
  .timeline-entry .t-photos.carousel .t-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    padding: 0 .12em;
    background: none;
    border: 0;
    cursor: pointer;
    user-select: none;
    font-family: inherit;
    font-size: 2.6rem;
    line-height: 1;
    color: #fff;
    text-shadow: 0 1px 6px rgba(15, 58, 82, .55);
    transition: color .2s ease, transform .2s ease;
  }
  .timeline-entry .t-photos.carousel .t-nav:active { color: #9fd4e4; }
  /* Negative offsets pull each chevron out of the .t-track (its containing
     block) and into the padding strip the parent .t-photos.carousel reserved
     on either side — putting them just clear of the photo edges. */
  .timeline-entry .t-photos.carousel .t-nav.prev { left: -34px; }
  .timeline-entry .t-photos.carousel .t-nav.next { right: -34px; }
  /* hover-guarded: these carousels exist on touch layouts, where a tapped
     chevron would otherwise hold its :hover displacement forever */
  @media (hover: hover) {
    .timeline-entry .t-photos.carousel .t-nav:hover { color: #bfe3ee; }
    .timeline-entry .t-photos.carousel .t-nav.prev:hover {
      transform: translateY(-50%) translateX(-3px);
    }
    .timeline-entry .t-photos.carousel .t-nav.next:hover {
      transform: translateY(-50%) translateX(3px);
    }
  }
  /* section 7 (poster mode): standalone poster above the carousel, and the
     carousel below sized for landscape photos so more of each shows through */
  .timeline-entry .t-poster {
    display: block;
    width: 62%;
    max-width: 260px;
    margin: 0 auto 20px;
    overflow: hidden;                  /* clip the hover-zoom */
    border-radius: 14px;
  }
  .timeline-entry .t-poster > img {
    display: block;
    width: 100%;
    height: auto;
    transition: transform .85s cubic-bezier(.34,1.56,.64,1);
  }
  .timeline-entry .t-photos.carousel-landscape {
    /* landscape photos read best in a 3:2 frame — matches their natural aspect
       so both slides render without heavy cropping */
    aspect-ratio: 3 / 2;
  }
  .timeline-entry .t-photos.carousel-landscape .t-frame,
  .timeline-entry .t-photos.carousel-landscape .t-frame > img:first-child {
    height: 100%;
  }
  .timeline-entry .t-photos.carousel-landscape .t-frame > img:first-child {
    width: 100%;
    object-fit: cover;
  }
  /* Single-photo timeline entries: no carousel is built, but they still zoom
     on hover. Clip on the .t-photos wrapper so the img has somewhere to grow
     into. */
  .timeline-entry .t-photos:not(.carousel) {
    overflow: hidden;
    border-radius: 14px;
  }
  .timeline-entry .t-photos:not(.carousel) > img {
    transition: transform .85s cubic-bezier(.34,1.56,.64,1);
  }
  .site-footer { grid-template-columns: 1fr; justify-items: center; gap: 14px; }
  .site-footer > span, .site-footer .socials { justify-self: center; }
}

/* ============================================================
   Load + motion hygiene
   ============================================================ */
/* Hero layers fade up as each decodes instead of popping in one by one on a
   cold load. The JS at the foot of index.html tags still-loading images with
   .img-loading and lifts it on load — same contract as the gallery grid.
   The containers carry aspect-ratio, so this is purely cosmetic: nothing
   shifts, the art just arrives gently. */
.hero-art img { transition: opacity .5s ease; }
.hero-art img.img-loading { opacity: 0; }

/* While a timeline carousel is being BUILT (viewport shrank into the mobile
   band with About open), the slides were already painted at full opacity —
   letting the .55s opacity transition run at that moment visibly faded the
   stacked photos out. The builder holds this class for one frame. */
.t-photos.no-anim,
.t-photos.no-anim * { transition: none !important; }

/* Reduced motion: the router and scroll effects already bail in JS; this
   covers the CSS-driven movement they can't reach. Opacity-only fades are
   left alone (they're not vestibular triggers) — what's disabled is motion:
   slides, scales, nudges, and the testimonial entrance. */
@media (prefers-reduced-motion: reduce) {
  .testimonial.active { animation: none; }
  .main-nav,
  .nav-toggle .bar,
  .chev,
  .contact-cta a,
  .carousel-arrow,
  .t-arrow,
  .t-nav,
  .photo-frame img,
  .timeline-entry .t-photos > img,
  .timeline-entry .t-poster > img,
  .t-photos.carousel .t-frame > img { transition: none !important; }
}

