/* ==========================================================================
   Kaafila 2026 — Taana Baana
   The site's visual language is woven: warp (--tana, vertical, structural)
   and weft (--bana, horizontal, connective) threads recur as dividers,
   underlines and the hero signature.

   Palette balance: navy, white, and espresso brown are the three dominant
   colors — each gets real screen space via section "bands" (data-band on
   each <section>) that cycle navy → white → brown down the page, like
   woven stripes, rather than one color drowning out the other two. Brick
   red and amber stay accents. Sage green and slate blue-grey round out the
   five category colors (the brand palette only supplies 5 hues, and the
   site needs 5 distinguishable ones).

   Token architecture:
   - --ink / --ink-soft / --thread-cream / --thread-cream-dim / --line /
     --line-strong: FIXED navy-band values. Used only by the header, footer,
     skip-link and other chrome that never re-bands as you scroll — that
     chrome should look identical everywhere for wayfinding.
   - --band-*: the same roles, but re-scoped per section via [data-band].
     Everything inside <main> sections reads these instead, so a card,
     button or heading automatically re-themes for whichever band it's in.
   - --thread-*, --charcoal: category accent colors and their paired dark
     text. Global and fixed on purpose — a "Dance" tag should always read
     as amber, whatever band it happens to sit on.
   ========================================================================== */

:root {
  /* Taana Baana motif palette — derived from the emblem
     Navy/dark blue (bird wing), Cream/beige (nest), Gold (feathers),
     Rust/burgundy (threads), Navy (background) */
  --ink: #2c3e50;
  --ink-soft: #34495e;
  --thread-cream: #f5f1e8;
  --thread-cream-dim: #a89968;
  --charcoal: #1a1a1a;
  --brown: #6b5344;

  --line: rgba(44, 62, 80, 0.14);
  --line-strong: rgba(44, 62, 80, 0.28);

  /* Band-relative tokens — default to the navy band; overridden below by
     [data-band="white"] and [data-band="brown"] */
  --band-bg: var(--ink);
  --band-surface: var(--ink-soft);
  --band-fg: var(--thread-cream);
  --band-fg-dim: var(--thread-cream-dim);
  --band-line: var(--line);
  --band-line-strong: var(--line-strong);
  --band-heading: var(--brochure-yellow);   /* 7.1:1 on navy */

  /* Brochure yellow — sampled off the event posters' own titles (#fcb454,
     hue 34). The Kaafila wordmark's lime was tried first and read as green,
     which it is: that sits at hue 61. Headings and nav use this on every band,
     but it only clears contrast on the dark ones — 1.4:1 on the beige — so the
     beige band takes the same hue darkened, each variant picked as the nearest
     step clearing 4.5:1. */
  --brochure-yellow: #fcb454;

  /* Category threads — Taana Baana palette: gold, rust, burgundy accents */
  --thread-music: #a86d3c;    /* rust/burgundy from threads */
  --thread-dance: #d4a574;    /* gold from bird feathers */
  --thread-visual: #8b7355;   /* warm brown from nest */
  --thread-theatre: #6b5344;  /* deep brown from threads */

  /* Type — Poppins (main font, body + display), Source Serif 4 (headings +
     subtitles, serif contrast), Montserrat (nav/labels/UI chrome).
     The hero's "Taana Baana" was once set in a script face; it now takes the
     heading serif like every other heading, so no script font is loaded. */
  --font-heading: "Source Serif 4", Georgia, serif;
  --font-body: "Poppins", "Helvetica Neue", Arial, sans-serif;
  --font-ui: "Montserrat", "Helvetica Neue", Arial, sans-serif;

  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.75rem;
  --space-4: 3rem;
  --space-5: 5rem;
  --space-6: 8rem;

  --container: 1180px;
  --radius: 2px;
}

/* Band overrides — applied to <section data-band="..."> */
/* Beige band — matched to the paper tone of the hero artwork (#f0e2d2) so the
   banner image blends into the page with no visible edge. */
[data-band="white"] {
  --band-bg: #f0e2d2;
  --band-surface: #e6d5c1;
  --band-fg: #2c3e50;
  --band-fg-dim: #6b5344;
  --band-line: rgba(44, 62, 80, 0.12);
  --band-line-strong: rgba(44, 62, 80, 0.24);
  --band-heading: #8c5303;                  /* same hue, darkened — 4.9:1 on beige */
}
[data-band="brown"] {
  --band-bg: #6b5344;
  --band-surface: #7d6450;
  --band-fg: #f5f1e8;
  --band-fg-dim: #d4a574;
  --band-line: rgba(245, 241, 232, 0.14);
  --band-line-strong: rgba(245, 241, 232, 0.28);
  --band-heading: #fdc67d;                  /* same hue, lightened — 4.6:1 on brown */
}
[data-band="navy"] {
  --band-bg: var(--ink);
  --band-surface: var(--ink-soft);
  --band-fg: var(--thread-cream);
  --band-fg-dim: var(--thread-cream-dim);
  --band-line: var(--line);
  --band-line-strong: var(--line-strong);
  --band-heading: var(--brochure-yellow);
}

/* -------------------------------------------------------------------------
   Reset
   ------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

body {
  margin: 0;
  background: var(--band-bg);
  color: var(--band-fg);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

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

/* The browser hides [hidden] with a `display: none` from its own stylesheet,
   which any author rule setting `display` outranks. That is why a hidden
   .btn — like the booking link before the gate is satisfied — stayed on
   screen. Restated here so the attribute wins wherever it is used. */
[hidden] { display: none !important; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; }
ul { list-style: none; margin: 0; padding: 0; }
h1, h2, h3, h4 { margin: 0; font-weight: 400; }

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

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-3);
}

.eyebrow {
  font-family: var(--font-ui);
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--band-fg-dim);
}

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

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--thread-cream);
  color: var(--ink);
  padding: 0.75rem 1.25rem;
  font-family: var(--font-ui);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
}
.skip-link:focus {
  left: var(--space-2);
  top: var(--space-2);
}

/* -------------------------------------------------------------------------
   Weft divider — the recurring horizontal-thread motif. Fixed colors on
   purpose: as a section-to-section seam marker, it should read the same
   whichever two bands it sits between.
   ------------------------------------------------------------------------- */
.weft {
  height: 1px;
  background: linear-gradient(
    90deg,
    var(--thread-music) 0 25%,
    var(--thread-dance) 25% 50%,
    var(--thread-visual) 50% 75%,
    var(--thread-theatre) 75% 100%
  );
  opacity: 0.55;
  border: 0;
  margin: 0;
}

/* -------------------------------------------------------------------------
   Header / nav — fixed navy chrome, never re-bands, for consistent
   wayfinding regardless of which section is scrolled behind it.
   ------------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(31, 53, 76, 0.92);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}

.nav-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: 0.4rem;
}

/* Header lockup — one supplied image carrying both marks: the school's lotus
   and name beside the Kaafila mark. It replaces the three separate pieces this
   header used to assemble (lotus, hairline rule, brochure mark + school name),
   which duplicated what the artwork already contains.

   The file is white line-work with a soft glow, which disappears into this navy
   chrome and reads cleanly against it.

   img/kaafila-lockup.png is a 1536x1024 canvas whose artwork occupies only the
   band from y=264 to y=740 — well under half the height. Sized the plain way
   (height on the <img>), most of the header's logo box is therefore empty
   margin and the mark renders at under half the height it was given, far too
   small to read. Shipping a pre-cropped file did not survive: img/ gets
   restored from the supplied assets and the trimmed copy disappears with it.

   So the crop is done here instead, and nothing outside this rule has to hold.
   .wordmark is a window of the band's proportions; the <img> is blown up to
   2.15x that height and pulled up and left so the artwork alone fills it.
   --lockup-h is the height the mark actually renders at, and the only number
   worth touching — the multipliers are just the band's geometry:

     img height   1024/476 = 2.1513      window width  1497/476 = 3.1450
     pull up       264/476 = 0.5546      pull left       24/476 = 0.0504

   The band is measured a few pixels loose on every side, because the supplied
   PNG has been re-encoded before with the artwork landing a pixel or two
   over. If the file is ever replaced with one already cropped, delete this
   window and go back to a plain height on .wordmark-logo. */
.wordmark {
  --lockup-h: 96px;
  display: block;
  overflow: hidden;
  flex: none;
  width: calc(var(--lockup-h) * 3.145);
  height: var(--lockup-h);
  line-height: 1;
}
.wordmark-logo {
  display: block;
  height: calc(var(--lockup-h) * 2.1513);
  width: auto;
  max-width: none;
  margin-top: calc(var(--lockup-h) * -0.5546);
  margin-left: calc(var(--lockup-h) * -0.0504);
}
/* The mark is 3.1x as wide as it is tall, so its height is what decides whether
   it still clears the seven nav links. 80px puts it at ~250px wide, the most
   that fits beside them down to the 780px breakpoint where the nav folds into
   the toggle and the row belongs to the logo again. */
@media (max-width: 1000px) {
  .wordmark { --lockup-h: 80px; }
}
@media (max-width: 620px) {
  .wordmark { --lockup-h: 64px; }
}
@media (max-width: 420px) {
  .wordmark { --lockup-h: 52px; }
}

.nav-links {
  display: flex;
  gap: var(--space-3);
  font-family: var(--font-ui);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
}

/* Nav carries the brochure yellow like the headings do. It clears 6.9:1 on
   the header bar and 5.1:1 on the mobile drop-down, both better than the dim
   cream it replaces. Hover and the current page go a step lighter — same hue,
   so there is still a visible state change without a second colour. */
.nav-links a {
  position: relative;
  padding-block: 0.3rem;
  color: var(--brochure-yellow);
  transition: color 0.2s ease;
}
.nav-links a:hover,
.nav-links a:focus-visible { color: #fdd39b; }
.nav-links a[aria-current="page"] { color: #fdd39b; }
.nav-links a[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--brochure-yellow);
}

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--line-strong);
  color: var(--thread-cream);
  width: 44px;
  height: 44px;
  border-radius: var(--radius);
}
.nav-toggle svg { pointer-events: none; }

@media (max-width: 780px) {
  .nav-toggle { display: inline-flex; align-items: center; justify-content: center; }
  .nav-links {
    position: absolute;
    top: 100%;
    left: 0; right: 0;
    flex-direction: column;
    gap: 0;
    background: var(--ink-soft);
    border-bottom: 1px solid var(--line);
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
  }
    .nav-links.is-open { max-height: 70vh; overflow-y: auto; }
  .nav-links a {
    padding: var(--space-2) var(--space-3);
    display: block;
    border-top: 1px solid var(--line);
  }
}

/* -------------------------------------------------------------------------
   Hero + Taana Baana bird/nest banner
   Integrated design: banner, title, and theme text together as one visual unit
   ------------------------------------------------------------------------- */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-4) var(--space-3) var(--space-2);
  background: var(--band-bg);
  color: var(--band-fg);
  overflow: visible;
}

/* -------------------------------------------------------------------------
   Hero scroll scene — see js/hero-scene.js.

   Two states share this markup. Without the script (or with reduced motion)
   .hero-stage is an ordinary centred block and .hero-scene holds the two flat
   images, exactly the hero as it was. With it, <html> gains .hero-animating,
   the stage becomes a full-height panel that ScrollTrigger pins, and the scene
   is one <svg> that redraws itself against scroll position.

   --header-h is measured by the script: the site header is sticky, so the
   stage has to hold its content clear of it rather than centre in the window.
   ------------------------------------------------------------------------- */
:root { --header-h: 0px; }

.hero-stage {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.hero-scene {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.hero-animating .hero-stage {
  /* svh keeps iOS Safari from re-laying the pinned panel out as its toolbars
     collapse; the vh line is the fallback for anything that lacks it. */
  height: 100vh;
  height: 100svh;
  padding-top: var(--header-h);
  padding-bottom: var(--space-2);
  justify-content: space-between;
  gap: 0;
  /* The scene deliberately draws beyond its box while the bird is flying and
     while the thread is running off both edges. Clipping here — rather than on
     the section — is what keeps that from ever widening the page. */
  overflow: hidden;
}

.hero-animating .hero-scene {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  justify-content: center;
}

.hero-svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
/* The mark is the brochure's own two-colour artwork: lime letterforms, navy
   figures. Set here rather than baked into the generated paths so the mark
   stays themeable alongside the rest of the palette. */
.hero-svg .mark-letters { fill: #c5c728; }
.hero-svg .mark-figures { fill: #264a88; }
.hero-svg .scene-bird,
.hero-svg .scene-nest,
.hero-svg .scene-mark,
.hero-svg .scene-warp,
.hero-svg .scene-lines,
.hero-svg .scene-shards,
.hero-svg .scene-threads { will-change: opacity, transform; }

/* The eyebrow holds still at the top of the panel; the theme and tagline are
   faded in by the script once the mark has finished forming, so the panel is
   never carrying more than one idea at a time. */
.hero-animating .hero-eyebrow { margin-bottom: 0; flex: none; }
.hero-animating .hero-theme { margin-top: 0; }
.hero-animating [data-hero-reveal] {
  opacity: 0;
  will-change: opacity, transform;
  /* The load-in "rise" keyframes fill forwards with opacity:1, and a running
     animation outranks both this rule and the inline opacity the script sets —
     so the script could never hold these hidden until the mark had formed. */
  animation: none;
}

@media (max-width: 640px) {
  .hero-animating .hero-stage { padding-inline: var(--space-2); }
  .hero-animating .hero-theme { font-size: clamp(2rem, 9vw, 2.8rem); }
}
/* A phone held sideways leaves barely 300px between the header and the fold.
   The words give their space back to the scene there — nothing is lost, since
   the visually-hidden <h1> carries the same line for assistive tech and the
   theme is set out in full in the section below. */
@media (max-height: 620px) {
  .hero-animating .hero-tagline { display: none; }
  .hero-animating .hero-kicker { display: none; }
  .hero-animating .hero-eyebrow { font-size: 0.6rem; letter-spacing: 0.2em; }
  .hero-animating .hero-theme {
    font-size: clamp(1.4rem, 5.5vh, 2.1rem);
    line-height: 1;
  }
  .hero-animating .hero-stage { padding-bottom: 0; }
}

/* The artwork carries wide margins of its own, so it is held well below the
   text column's width — at 900px the hero ran past 1100px tall and pushed the
   categories entirely below the fold. */
.hero-banner {
  width: 100%;
  max-width: 720px;
  height: auto;
  margin: var(--space-2) 0 0;
  object-fit: cover;
  object-position: center;
  pointer-events: none;
  opacity: 1;
}
@keyframes banner-fade-in {
  to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-banner { opacity: 1; animation: none; }
}

.hero-overlay {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background: transparent;
  pointer-events: none;
  z-index: 1;
}

.hero-content {
  position: relative;
  z-index: 2;
  padding: var(--space-4) var(--space-3) var(--space-2);
  max-width: 900px;
  flex: 0 0 auto;
  animation: rise 1s ease 1.9s both;
}
@media (prefers-reduced-motion: reduce) {
  .hero-content { animation: none; }
}
@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}

.hero-eyebrow {
  font-family: var(--font-ui);
  letter-spacing: 0.3em;
  font-size: 0.72rem;
  color: var(--band-fg-dim);
  text-transform: uppercase;
  margin-bottom: var(--space-2);
  animation: rise 0.7s ease 0.1s both;
}

.hero-title {
  margin-bottom: var(--space-2);
  height: auto;
  max-width: min(500px, 95vw);
  width: auto;
  display: block;
  animation: rise 0.7s ease 0.25s both;
}

/* Label above the theme word, set in the same small-heading style .eyebrow uses
   everywhere else on the site (UI face, uppercase, wide tracking) so the hero
   introduces its theme the way the sections introduce their titles. It rides
   with the theme and tagline in the reveal group rather than with the eyebrow,
   since it belongs to the line it labels. */
.hero-kicker {
  font-family: var(--font-ui);
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--band-fg-dim);
  margin: 0 0 0.5rem;
  animation: rise 0.7s ease 0.34s both;
}

/* Theme word — set in the heading serif, like every other heading on the site,
   rather than a script face. It keeps the deeper rust: the accent
   --thread-music only reaches 3.4:1 on the beige band. */
.hero-theme {
  font-family: var(--font-heading);
  font-style: normal;
  font-weight: 600;
  font-size: clamp(2rem, 5.2vw, 3.4rem);
  letter-spacing: 0.01em;
  line-height: 1.15;
  color: #8a5730;
  margin: 0 0 var(--space-1);
  animation: rise 0.7s ease 0.4s both;
}

.hero-tagline {
  font-family: var(--font-body);
  font-size: clamp(0.95rem, 1.8vw, 1.2rem);
  font-weight: 500;
  color: var(--band-fg-dim);
  max-width: 36ch;
  margin: 0 auto;
  animation: rise 0.7s ease 0.55s both;
}

/* -------------------------------------------------------------------------
   Buttons
   ------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-ui);
  font-size: 0.82rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.95rem 1.9rem;
  border: 1px solid var(--band-fg);
  color: var(--band-fg);
  background: transparent;
  border-radius: var(--radius);
  transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}
.btn:hover, .btn:focus-visible {
  background: var(--band-fg);
  color: var(--band-bg);
}
.btn-solid {
  background: var(--thread-dance);
  border-color: var(--thread-dance);
  color: var(--charcoal);
}
.btn-solid:hover, .btn-solid:focus-visible {
  background: var(--band-fg);
  border-color: var(--band-fg);
  color: var(--band-bg);
}

/* -------------------------------------------------------------------------
   Sections
   ------------------------------------------------------------------------- */
.section {
  padding-block: var(--space-5);
  background: var(--band-bg);
  color: var(--band-fg);
}
.section-head {
  margin-bottom: var(--space-4);
}
.section-title {
  color: var(--band-heading);
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 1.6rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin-top: var(--space-1);
}

/* Category cards ---------------------------------------------------------*/
.categories-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--band-line);
  border: 1px solid var(--band-line);
}
@media (max-width: 900px) {
  .categories-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
  .categories-grid { grid-template-columns: 1fr; }
}

.category-card {
  background: var(--band-bg);
  padding: var(--space-4) var(--space-2);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2);
  position: relative;
  transition: background 0.25s ease;
}
.category-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--thread);
}
/* The thread colour washes up from the base on hover — the card taking on its
   own strand rather than a generic highlight. */
.category-card::after {
  content: "";
  position: absolute;
  inset: 3px 0 0;
  background: linear-gradient(to top, var(--thread), transparent 70%);
  opacity: 0;
  transition: opacity 0.28s ease;
  pointer-events: none;
}
.category-card:hover { background: var(--band-surface); }
.category-card:hover::after,
.category-card:focus-visible::after { opacity: 0.1; }
.category-card .icon {
  width: 40px;
  height: 40px;
  color: var(--thread);
  transition: transform 0.28s ease;
}
.category-card:hover .icon,
.category-card:focus-visible .icon { transform: translateY(-3px); }
.category-card > * { position: relative; z-index: 1; }
@media (prefers-reduced-motion: reduce) {
  .category-card::after, .category-card .icon { transition: none; }
  .category-card:hover .icon { transform: none; }
}
.category-card .label {
  font-family: var(--font-ui);
  font-size: 0.92rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.category-card[data-cat="music"]    { --thread: var(--thread-music); }
.category-card[data-cat="dance"]    { --thread: var(--thread-dance); }
.category-card[data-cat="visual"]   { --thread: var(--thread-visual); }
.category-card[data-cat="theatre"]  { --thread: var(--thread-theatre); }
/* Updates board ----------------------------------------------------------- */
/* Same construction as the category grid above — cells separated by a 1px
   line that is really the grid's own background showing through the gaps, so
   the rules between boxes are woven rather than drawn four times over. The
   countdown reuses it too, which is what keeps a ticking clock from looking
   like a different piece of furniture. */
.countdown-label {
  margin: 0 0 var(--space-2);
  font-family: var(--font-ui);
  font-size: 0.82rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--band-fg-dim);
}
.countdown-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--band-line);
  border: 1px solid var(--band-line);
  max-width: 620px;
  margin-bottom: var(--space-4);
}
.countdown-cell {
  background: var(--band-bg);
  padding: var(--space-2) var(--space-1);
  text-align: center;
}
.countdown-num {
  display: block;
  font-family: var(--font-heading);
  font-size: 2.1rem;
  line-height: 1.1;
  color: var(--band-heading);
  /* Seconds change width on a proportional numeral and shuffle the whole row;
     tabular figures hold the cells still. */
  font-variant-numeric: tabular-nums;
}
.countdown-unit {
  display: block;
  margin-top: 0.35rem;
  font-family: var(--font-ui);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--band-fg-dim);
}

/* Three across rather than four: update cards carry a sentence, not a word,
   and an odd count leaves a tidier last row here than it does at four. */
.updates-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--band-line);
  border: 1px solid var(--band-line);
}
@media (max-width: 900px) {
  .updates-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
  .updates-grid { grid-template-columns: 1fr; }
}
.update-card {
  display: block;
  position: relative;
  background: var(--band-bg);
  padding: var(--space-3) var(--space-2);
  transition: background 0.25s ease;
}
/* The thread bar is the card's category showing at the top edge, exactly as
   on the category cards; an announcement with no event behind it takes the
   heading colour instead. */
.update-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--thread, var(--band-heading));
}
a.update-card:hover,
a.update-card:focus-visible { background: var(--band-surface); }
.update-card-label {
  display: block;
  font-family: var(--font-ui);
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--band-fg-dim);
}
.update-card-title {
  display: block;
  margin-top: var(--space-1);
  font-family: var(--font-heading);
  font-size: 1.15rem;
  line-height: 1.35;
  color: var(--band-heading);
}
.update-card-note {
  display: block;
  margin-top: 0.4rem;
  color: var(--band-fg-dim);
  font-size: 0.92rem;
  line-height: 1.7;
}
}
.update-card-go {
  display: block;
  margin-top: var(--space-2);
  color: var(--band-fg-dim);
  transition: transform 0.25s ease, color 0.25s ease;
}
a.update-card:hover .update-card-go,
a.update-card:focus-visible .update-card-go {
  color: var(--band-heading);
  transform: translateX(4px);
}
@media (prefers-reduced-motion: reduce) {
  .update-card, .update-card-go { transition: none; }
  a.update-card:hover .update-card-go { transform: none; }
}

.updates-more { margin: var(--space-3) 0 0; }

@media (max-width: 520px) {
  .countdown-num { font-size: 1.6rem; }
  .countdown-unit { font-size: 0.6rem; letter-spacing: 0.1em; }
}

/* Theme / about copy blocks ----------------------------------------------*/
.prose {
  max-width: 62ch;
}
.prose p { margin: 0 0 var(--space-2); color: var(--band-fg-dim); }
.prose p:last-child { margin-bottom: 0; }

/* The heading column holds only an eyebrow and a title, so it is kept narrow
   — at 1fr against 1.4fr it left a tall empty gutter beside the prose. */
.theme-block {
  display: grid;
  grid-template-columns: minmax(180px, 0.55fr) 1.6fr;
  gap: var(--space-4);
  align-items: start;
}
/* A short woven rule under the heading, in the four category threads: the
   "four threads, one weave" idea stated once, quietly. */
.theme-block > :first-child::after {
  content: "";
  display: block;
  width: 62px;
  height: 3px;
  margin-top: var(--space-2);
  background: linear-gradient(
    90deg,
    var(--thread-music) 0 25%,
    var(--thread-dance) 25% 50%,
    var(--thread-visual) 50% 75%,
    var(--thread-theatre) 75% 100%
  );
}
@media (max-width: 780px) {
  .theme-block { grid-template-columns: 1fr; gap: var(--space-3); }
}

/* Sponsors ---------------------------------------------------------------- */
/* The heading column of the About block is otherwise empty below its title, so
   the sponsor cards go there. The woven rule above is a ::after on that column
   and therefore always its last box; laying the column out as a flex stack lets
   the rule be ordered back under the title where it belongs, with the sponsors
   below it. Scoped to --sponsors so the two .theme-blocks on about.html keep
   the plain behaviour. */
.theme-block--sponsors > :first-child {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.theme-block--sponsors > :first-child::after { order: 1; }
.sponsor-strip {
  order: 2;
  width: 100%;
  max-width: 340px;
  margin-top: var(--space-3);
}
.sponsor-strip-label { margin-bottom: var(--space-2); }

/* Stacked on a phone, the sponsor cards would wedge themselves between the
   title and the text it introduces. Dissolving the heading column with
   display:contents lifts its parts into the same stack as the prose, so the
   strip can be ordered last while the title stays on top. The stack drops its
   gap in favour of the margins the parts already carry — one gap can't serve
   both the tight title/rule spacing and the looser break before the prose.

   This has to sit *after* the desktop sponsor rules above, not up with the
   other 780px overrides: the selectors are the same weight, so whichever is
   written last wins, and `display: flex` on the heading column would otherwise
   overrule the `display: contents` that makes the reordering possible. */
@media (max-width: 780px) {
  .theme-block--sponsors { display: flex; flex-direction: column; gap: 0; }
  .theme-block--sponsors > :first-child { display: contents; }
  .theme-block--sponsors > :last-child { order: 2; margin-top: var(--space-3); }
  .theme-block--sponsors .sponsor-strip { order: 3; }
}
.sponsor-list {
  display: grid;
  gap: var(--space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Every mark is drawn for white, so each gets its own white card rather than
   being laid straight on the brown. The card is a fixed height so three logos
   of very different proportions still line up as one column. */
.sponsor-card {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100px;
  padding: 0.7rem 1.1rem;
  background: #fff;
  border-radius: var(--radius);
  overflow: hidden;
}
.sponsor-card img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  object-fit: contain;
  transform: scale(var(--logo-scale, 1));
}
/* County's PNG is a 5760x3240 canvas carrying a centred mark that is half its
   width and under a third of its height — contained plainly it comes out a
   third the size of the other two. The artwork is centred on the canvas, so
   scaling it up in place is enough; the card clips what spills. */
.sponsor-card--county img { --logo-scale: 2.8; }

/* Credits ----------------------------------------------------------------- */
/* Roles vary from one name to four, so the cards are a masonry-ish auto-fill
   grid rather than fixed rows: a card is only as tall as the names it holds,
   and the tiers stay scannable at any width. --team-accent is set per tier on
   the group so the edge does the sorting; it must be a colour that separates
   from --band-surface on whichever band the section sits, which on brown rules
   out half the thread palette. */
.team-group + .team-group { margin-top: var(--space-4); }
.team-group-title {
  font-family: var(--font-ui);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--band-fg-dim);
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--band-line);
}
.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
  gap: var(--space-2);
  align-items: start;
  list-style: none;
  margin: 0;
  padding: 0;
}
.team-card {
  padding: var(--space-3);
  background: var(--band-surface);
  border: 1px solid var(--band-line);
  border-left: 3px solid var(--team-accent, var(--band-line-strong));
  border-radius: var(--radius);
}
.team-role {
  margin: 0 0 0.6rem;
  font-family: var(--font-ui);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--band-fg-dim);
}
.team-names {
  display: grid;
  gap: 0.3rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.team-names li {
  font-family: var(--font-heading);
  font-size: 1.02rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: 0.01em;
  color: var(--band-heading);
}

/* Purpose / list blocks ----------------------------------------------------*/
.thread-list { display: grid; gap: var(--space-2); }
.thread-list li {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--band-line);
  color: var(--band-fg-dim);
}
.thread-list li:last-child { border-bottom: 0; }
.thread-list .mark {
  width: 10px; height: 10px;
  border-radius: 50%;
  flex: none;
  background: var(--thread-dance);
}

.artforms-list {
  display: grid;
  gap: 0;
}
.artforms-list li {
  display: grid;
  grid-template-columns: 4px 1fr;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--band-line);
}
.artforms-list li:first-child { border-top: 1px solid var(--band-line); }
.artforms-list .swatch { background: var(--c); border-radius: 1px; }
.artforms-list .name {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.95rem;
}
.artforms-list li:nth-child(1) .swatch { --c: var(--thread-music); }
.artforms-list li:nth-child(2) .swatch { --c: var(--thread-dance); }
.artforms-list li:nth-child(3) .swatch { --c: var(--thread-visual); }
.artforms-list li:nth-child(4) .swatch { --c: var(--thread-theatre); }

/* -------------------------------------------------------------------------
   Filters (gallery)
   ------------------------------------------------------------------------- */
/* Schedule page — day-grouped listing, .tag/.event-row/.event-btn are
   shared with other components. ------------------------------------------*/
.schedule-day {
  margin-bottom: var(--space-4);
}
.schedule-day-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  font-family: var(--font-ui);
  font-style: normal;
  font-weight: 500;
  font-size: 1.6rem;
  color: var(--thread-dance);
}
.schedule-day-head .weft { flex: 1; }

.event-row {
  display: grid;
  grid-template-columns: minmax(120px, auto) 1fr auto;
  gap: var(--space-2);
  align-items: start;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--band-line);
}
.event-row:first-of-type { border-top: 1px solid var(--band-line); }

/* On a phone the three columns cannot all hold their minimums — a 120px time,
   an event title that will not wrap below its longest word, and a category tag
   — so the row grew past the screen and the page scrolled sideways. The time
   moves to its own line there and the title is allowed to shrink. */
@media (max-width: 640px) {
  .event-row {
    grid-template-columns: 1fr auto;
    gap: var(--space-1) var(--space-2);
  }
  .event-time { grid-column: 1 / -1; padding-top: 0; }
  .event-main { min-width: 0; }
  .event-btn { overflow-wrap: anywhere; }
}

.event-time {
  font-family: var(--font-ui);
  font-size: 0.78rem;
  color: var(--band-fg-dim);
  padding-top: 0.35rem;
  white-space: nowrap;
}

.event-main {
  display: flex;
  flex-direction: column;
}

.event-venue {
  font-family: var(--font-ui);
  font-size: 0.78rem;
  color: var(--band-fg-dim);
}

/* For rows whose single time span hides something — a break mid-session, a
   split into phases — so a reader doesn't turn up expecting it to run through. */
.event-note {
  font-family: var(--font-ui);
  font-size: 0.74rem;
  font-style: italic;
  color: var(--band-fg-dim);
  margin-top: 0.15rem;
}

.event-btn {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  background: none;
  border: none;
  text-align: left;
  color: var(--band-fg);
  font-family: var(--font-body);
  font-size: 1.05rem;
  padding: 0.2rem 0;
  border-bottom: 1px dashed transparent;
}
.event-btn:hover, .event-btn:focus-visible {
  border-bottom-color: var(--band-fg-dim);
}
.event-btn-plain {
  min-height: auto;
  color: var(--band-fg-dim);
}

.tag {
  font-family: var(--font-ui);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.3rem 0.6rem;
  border-radius: var(--radius);
  white-space: nowrap;
  color: var(--charcoal);
  background: var(--thread);
}
.tag[data-cat="music"]   { --thread: var(--thread-music); color: var(--thread-cream); }
.tag[data-cat="dance"]   { --thread: var(--thread-dance); }
.tag[data-cat="visual"]  { --thread: var(--thread-visual); color: var(--thread-cream); }
.tag[data-cat="theatre"] { --thread: var(--thread-theatre); color: var(--thread-cream); }

.schedule-note {
  font-family: var(--font-ui);
  font-size: 0.82rem;
  color: var(--band-fg-dim);
  padding-top: var(--space-3);
}

/* -------------------------------------------------------------------------
   Category page: poster grid
   ------------------------------------------------------------------------- */
.category-hero {
  padding-top: 4rem;
  padding-bottom: 1rem;
}
.category-hero .section-title {
  font-size: clamp(2rem, 5vw, 2.8rem);
  /* Headings are the brochure yellow everywhere, so this no longer takes the
     category's own thread colour — the thread still marks the page through the
     tags, rules and poster accents. */
}
.category-hero .tagline {
  color: var(--band-fg-dim);
  margin-top: 0.5rem;
}

.poster-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}
@media (max-width: 640px) {
  .poster-grid { grid-template-columns: 1fr; }
}

.poster-card {
  display: block;
  border: 1px solid var(--band-line);
  background: var(--band-surface);
  transition: border-color 0.2s ease, transform 0.2s ease;
}
.poster-card:hover,
.poster-card:focus-visible {
  border-color: var(--thread-current, var(--band-fg-dim));
  transform: translateY(-3px);
  box-shadow: 0 8px 22px rgba(44, 62, 80, 0.13);
}
@media (prefers-reduced-motion: reduce) {
  .poster-card:hover, .poster-card:focus-visible { transform: none; }
}

.poster-art {
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
}
.poster-art svg { width: 100%; height: 100%; display: block; }

/* Real posters are A4 portrait. The 4/3 window above was sized for the woven
   swatch and would crop a poster's title away, so a card carrying artwork
   takes the poster's own proportions instead. */
.poster-art.has-photo { aspect-ratio: 99 / 140; }
.poster-img { width: 100%; height: 100%; object-fit: cover; display: block; }

.poster-info {
  padding: var(--space-2);
  border-top: 1px solid var(--band-line);
}
.poster-title {
  display: block;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 1rem;
  letter-spacing: 0.01em;
  color: var(--band-fg);
}
.poster-subtitle {
  display: block;
  font-family: var(--font-heading);
  font-style: italic;
  font-size: 0.9rem;
  color: var(--band-fg-dim);
  margin-top: 0.2rem;
}

/* -------------------------------------------------------------------------
   Event detail page
   ------------------------------------------------------------------------- */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-ui);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  color: var(--band-fg-dim);
  margin-bottom: var(--space-3);
}
.back-link:hover, .back-link:focus-visible { color: var(--band-fg); }

.event-subtitle {
  font-family: var(--font-heading);
  font-style: italic;
  color: var(--band-fg-dim);
  font-size: 1.15rem;
  margin-top: 0.3rem;
}

.event-poster-full {
  margin-top: var(--space-3);
  aspect-ratio: 16 / 8;
  overflow: hidden;
  border: 1px solid var(--band-line);
}
.event-poster-full svg { width: 100%; height: 100%; display: block; }

/* Same again on the event page: drop the 16/8 letterbox for a real poster and
   let it stand at its own height, capped so it doesn't dominate the page. */
.event-poster-full.has-photo {
  aspect-ratio: auto;
  max-width: 440px;
}
.event-poster-full .poster-img { height: auto; }

.event-meta {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
  font-family: var(--font-ui);
  font-size: 0.85rem;
  color: var(--band-fg-dim);
}
.event-meta + .event-meta { margin-top: 0.4rem; }
.event-meta strong { color: var(--band-fg); font-weight: 700; }

.page-image-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.page-image-stack img {
  width: 100%;
  height: auto;
  border: 1px solid var(--thread-current, var(--band-line-strong));
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}

.doc-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
  margin-top: var(--space-3);
}
@media (max-width: 640px) {
  .doc-grid { grid-template-columns: 1fr; }
}

/* Thread-weave divider — a woven strip of the four category threads with a
   glowing bead (the event's own color) running back and forth along it,
   echoing the bird-carrying-a-thread motif of the Taana Baana emblem. */
.thread-weave {
  position: relative;
  height: 3px;
  margin: var(--space-4) 0 0;
  border-radius: 2px;
  background: linear-gradient(
    90deg,
    var(--thread-music), var(--thread-dance), var(--thread-visual),
    var(--thread-theatre), var(--thread-music)
  );
  background-size: 200% 100%;
  animation: thread-drift 10s linear infinite;
  box-shadow: 0 0 6px rgba(221, 154, 72, 0.4);
}
.thread-weave::after {
  content: "";
  position: absolute;
  top: 50%; left: 0;
  width: 9px; height: 9px;
  margin-top: -4.5px;
  border-radius: 50%;
  background: var(--thread-current, var(--thread-dance));
  box-shadow: 0 0 8px 2px var(--thread-current, var(--thread-dance));
  animation: thread-runner 5s ease-in-out infinite;
}
.thread-weave::before {
  content: "";
  position: absolute;
  top: 50%; right: 0;
  width: 6px; height: 6px;
  margin-top: -3px;
  border-radius: 50%;
  background: var(--thread-visual);
  box-shadow: 0 0 4px var(--thread-visual);
  animation: thread-runner-reverse 7s ease-in-out infinite;
}
@keyframes thread-drift {
  to { background-position: -200% 0; }
}
@keyframes thread-runner {
  0%, 100% { left: 0%; }
  50% { left: calc(100% - 9px); }
}
@keyframes thread-runner-reverse {
  0%, 100% { right: 0%; }
  50% { right: calc(100% - 6px); }
}
@media (prefers-reduced-motion: reduce) {
  .thread-weave { animation: none; box-shadow: none; }
  .thread-weave::after { animation: none; left: 0; }
  .thread-weave::before { animation: none; right: 0; }
}

/* Floating thread decorations — subtle animated threads in the background
   echoing the bird carrying threads to the nest motif */
.thread-accent {
  position: absolute;
  pointer-events: none;
  opacity: 0.15;
}
.thread-accent.thread-1 {
  width: 200px; height: 3px;
  background: var(--thread-music);
  top: 15%; left: -100px;
  animation: float-thread-1 20s ease-in-out infinite;
}
.thread-accent.thread-2 {
  width: 150px; height: 2px;
  background: var(--thread-visual);
  top: 35%; right: -75px;
  animation: float-thread-2 25s ease-in-out infinite;
}
.thread-accent.thread-3 {
  width: 180px; height: 2px;
  background: var(--thread-dance);
  bottom: 20%; left: -90px;
  animation: float-thread-3 22s ease-in-out infinite;
}
@keyframes float-thread-1 {
  0% { transform: translateX(0) scaleX(1); opacity: 0.15; }
  50% { opacity: 0.25; }
  100% { transform: translateX(400px) scaleX(0.8); opacity: 0.1; }
}
@keyframes float-thread-2 {
  0% { transform: translateX(0) rotate(-5deg); opacity: 0.1; }
  50% { transform: translateX(-300px) rotate(5deg); opacity: 0.25; }
  100% { transform: translateX(0) rotate(-5deg); opacity: 0.1; }
}
@keyframes float-thread-3 {
  0% { transform: translateX(0) scaleX(1); opacity: 0.15; }
  50% { opacity: 0.2; }
  100% { transform: translateX(360px) scaleX(0.9); opacity: 0.08; }
}
@media (prefers-reduced-motion: reduce) {
  .thread-accent { animation: none; opacity: 0.08; }
}


/* Document cards — links through to document.html. The event's own category
   colour (--thread-current) drives the top rule, icon and hover, so the pair
   reads as part of that category rather than as generic chrome. */
.doc-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-2);
  background: var(--band-bg);
  border: 1px solid var(--band-line-strong);
  border-radius: var(--radius);
  color: var(--band-fg);
  overflow: hidden;
  transition: border-color 0.22s ease, background 0.22s ease,
              transform 0.22s ease, box-shadow 0.22s ease;
}
.doc-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--thread-current, var(--thread-dance));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s ease;
}
.doc-card:hover, .doc-card:focus-visible {
  border-color: var(--thread-current, var(--thread-dance));
  background: var(--band-surface);
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(44, 62, 80, 0.12);
}
.doc-card:hover::before, .doc-card:focus-visible::before { transform: scaleX(1); }

.doc-card-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--band-surface);
  color: var(--thread-current, var(--thread-dance));
  transition: background 0.22s ease;
}
.doc-card:hover .doc-card-icon { background: var(--band-bg); }
.doc-card-icon svg { width: 22px; height: 22px; }

.doc-card-body { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.doc-card-title {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
}
.doc-card-sub {
  font-size: 0.78rem;
  color: var(--band-fg-dim);
}

.doc-card-arrow {
  flex: none;
  margin-left: auto;
  display: grid;
  place-items: center;
  color: var(--thread-current, var(--thread-dance));
  transform: translateX(-4px);
  opacity: 0.55;
  transition: transform 0.22s ease, opacity 0.22s ease;
}
.doc-card-arrow svg { width: 20px; height: 20px; }
.doc-card:hover .doc-card-arrow,
.doc-card:focus-visible .doc-card-arrow { transform: translateX(0); opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .doc-card, .doc-card::before, .doc-card-arrow, .doc-card-icon { transition: none; }
  .doc-card:hover, .doc-card:focus-visible { transform: none; }
}

.event-not-found {
  padding: var(--space-5) 0;
  text-align: center;
  color: var(--band-fg-dim);
}

/* -------------------------------------------------------------------------
   Gallery
   ------------------------------------------------------------------------- */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-1);
}
@media (max-width: 900px) { .gallery-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 640px) { .gallery-grid { grid-template-columns: repeat(2, 1fr); } }

.gallery-tile {
  aspect-ratio: 4 / 5;
  position: relative;
  overflow: hidden;
  background: var(--band-surface);
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: opacity 0.25s ease, transform 0.3s ease;
}
.gallery-tile svg { width: 100%; height: 100%; }
.gallery-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease;
}
.gallery-tile:hover img,
.gallery-tile:focus-visible img { transform: scale(1.05); }

.gallery-tile .cap,
/* Lightbox --------------------------------------------------------------- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
}
.lightbox[hidden] { display: none; }
.lightbox-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(31, 53, 76, 0.92);
  backdrop-filter: blur(4px);
}
.lightbox-panel {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: min(1100px, 94vw);
  padding: var(--space-3);
}
.lightbox-figure {
  margin: 0;
  flex: 1;
  min-width: 0;
  text-align: center;
}
.lightbox-figure img {
  max-width: 100%;
  max-height: 78vh;
  margin-inline: auto;
  border: 1px solid rgba(255, 255, 255, 0.18);
}
.lightbox-figure figcaption {
  margin-top: var(--space-2);
  font-family: var(--font-ui);
  font-size: 0.82rem;
  color: var(--thread-cream-dim);
}
.lightbox-btn {
  flex: none;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  font-size: 1.9rem;
  line-height: 1;
  color: var(--thread-cream);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 50%;
  transition: background 0.2s ease;
}
.lightbox-btn:hover, .lightbox-btn:focus-visible { background: rgba(255, 255, 255, 0.2); }
.lightbox-close {
  position: absolute;
  top: var(--space-1);
  right: var(--space-1);
  font-size: 1.6rem;
}
@media (max-width: 640px) {
  .lightbox-panel { gap: var(--space-1); padding: var(--space-2); }
  .lightbox-btn { width: 38px; height: 38px; font-size: 1.5rem; }
}
@media (prefers-reduced-motion: reduce) {
  .gallery-tile img { transition: none; }
  .gallery-tile:hover img { transform: none; }
}

/* -------------------------------------------------------------------------
   Contact
   ------------------------------------------------------------------------- */
.contact-grid {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: var(--space-5);
}
@media (max-width: 860px) {
  .contact-grid { grid-template-columns: 1fr; gap: var(--space-4); }
}

.contact-detail {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--band-line);
}
.contact-detail:first-child { border-top: 1px solid var(--band-line); }
.contact-detail dt {
  font-family: var(--font-ui);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--band-fg-dim);
}
.contact-detail dd {
  margin: 0;
  font-size: 1.1rem;
}

.form-field {
  display: grid;
  gap: 0.5rem;
  margin-bottom: var(--space-3);
}
.form-field label {
  font-family: var(--font-ui);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--band-fg-dim);
}
.form-field input,
.form-field textarea {
  background: transparent;
  border: 1px solid var(--band-line-strong);
  color: var(--band-fg);
  font-family: var(--font-body);
  font-size: 1rem;
  padding: 0.8rem 0.9rem;
  border-radius: var(--radius);
  resize: vertical;
}
.form-field input:focus,
.form-field textarea:focus {
  border-color: var(--thread-dance);
  outline: none;
}
.form-note {
  font-family: var(--font-ui);
  font-size: 0.76rem;
  color: var(--band-fg-dim);
  margin-top: var(--space-2);
}
.form-status {
  font-family: var(--font-ui);
  font-size: 0.85rem;
  padding: var(--space-2);
  border: 1px solid var(--thread-visual);
  color: var(--band-fg);
  margin-top: var(--space-3);
}
.form-status[hidden] { display: none; }

/* -------------------------------------------------------------------------
   Footer — fixed navy chrome, matches header
   ------------------------------------------------------------------------- */
.site-footer {
  background: var(--ink);
  border-top: 1px solid var(--line);
  padding-block: var(--space-3);
}
.footer-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-family: var(--font-ui);
  font-size: 0.76rem;
  color: var(--thread-cream-dim);
  letter-spacing: 0.04em;
}

/* -------------------------------------------------------------------------
   Read more expandable (home theme block, event concept note/guidelines)
   ------------------------------------------------------------------------- */
.read-more-panel {
  overflow: hidden;
  max-height: 0;
  transition: max-height 0.4s ease;
}
.read-more-panel.is-open { max-height: 5000px; }

/* -------------------------------------------------------------------------
   Scroll reveal — progressive enhancement only. JS adds .reveal-pending
   (never present without JS), so content is fully visible with no script,
   no CSS, or a reduced-motion preference. Grid children get a staggered
   transition-delay set inline by initScrollReveal().
   ------------------------------------------------------------------------- */
.reveal-pending {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.4s ease-out, transform 0.4s ease-out;
}
.reveal-pending.reveal-visible {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .reveal-pending {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* -------------------------------------------------------------------------
   Event page — Register block. Registration is per-brochure rather than
   per-event, so the button states which of the two forms it opens.
   ------------------------------------------------------------------------- */
.event-register {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding: var(--space-3);
  background: var(--band-surface);
  border-left: 3px solid var(--thread-current, var(--thread-dance));
  border-radius: var(--radius);
}
.event-register-kind {
  margin: 0;
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 0.9rem;
  letter-spacing: 0.02em;
}
.event-register-note {
  margin: 0.15rem 0 0;
  font-size: 0.82rem;
  color: var(--band-fg-dim);
}
@media (max-width: 520px) {
  .event-register { flex-direction: column; align-items: stretch; }
  .event-register .btn { justify-content: center; }
}

/* -------------------------------------------------------------------------
   Register page — one card per form, each listing the events behind it.
   ------------------------------------------------------------------------- */
.register-card {
  position: relative;
  padding: var(--space-3);
  margin-bottom: var(--space-3);
  background: var(--band-bg);
  border: 1px solid var(--band-line-strong);
  border-radius: var(--radius);
}
.register-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(
    90deg,
    var(--thread-music) 0 25%,
    var(--thread-dance) 25% 50%,
    var(--thread-visual) 50% 75%,
    var(--thread-theatre) 75% 100%
  );
}
.register-head { margin-bottom: var(--space-2); }
.register-note {
  color: var(--band-fg-dim);
  max-width: 62ch;
  margin: 0 0 var(--space-3);
}

/* Hairlines come from a ring on each cell rather than from the container
   background showing through the gaps — auto-fill leaves empty trailing cells
   on the last row, and those would otherwise render as grey blocks. */
.register-events {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 1px;
  background: transparent;
  margin-bottom: var(--space-3);
}
.register-event {
  background: var(--band-bg);
  box-shadow: 0 0 0 1px var(--band-line);
}
.register-event a {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: var(--space-2);
  border-left: 3px solid var(--thread);
  transition: background 0.2s ease;
}
.register-event a:hover,
.register-event a:focus-visible { background: var(--band-surface); }
.register-event-name {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 0.9rem;
}
.register-event-sub {
  font-family: var(--font-heading);
  font-style: italic;
  font-size: 0.82rem;
  color: var(--band-fg-dim);
}
@media (max-width: 520px) {
  .register-btn { width: 100%; justify-content: center; text-align: center; }
}

/* A day that is internal to the school carries a short note under its heading. */
.schedule-day-note {
  margin: 0 0 var(--space-2);
  font-family: var(--font-ui);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--band-fg-dim);
}

/* -------------------------------------------------------------------------
   Weave rule — the four category threads drawing themselves across, crossing
   as they go. Plays once on scroll-in; deliberately does not loop.
   ------------------------------------------------------------------------- */
.weave-rule {
  display: block;
  width: 100%;
  height: 52px;
  margin-top: var(--space-2);
  pointer-events: none;
}
.weave-rule svg { display: block; width: 100%; height: 100%; overflow: visible; }
.weave-rule path {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  opacity: 0.75;
  /* the longest strand is ~1300 units; one dash covers the whole path so
     animating the offset draws it from one end */
  stroke-dasharray: 1600;
  stroke-dashoffset: 1600;
}
.weave-rule.is-woven path {
  animation: weave-draw 2.1s cubic-bezier(0.4, 0, 0.15, 1) forwards;
  animation-delay: calc(var(--i) * 160ms);
}
@keyframes weave-draw {
  to { stroke-dashoffset: 0; }
}
/* Centred variant, for section heads that are centred */
.weave-rule.is-centred { margin-inline: auto; }

@media (prefers-reduced-motion: reduce) {
  .weave-rule path { stroke-dashoffset: 0; }
  .weave-rule.is-woven path { animation: none; }
}

/* -------------------------------------------------------------------------
   Ticketed events + admission-number gate
   ------------------------------------------------------------------------- */
/* Small standing reminder above the cards — the intro says it in prose, but
   this is the line someone skimming for whether the event is for them will
   actually catch. */
.ticket-only-note {
  margin: 0 0 var(--space-2);
  padding: 0.5rem 0.85rem;
  display: inline-block;
  font-family: var(--font-ui);
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--band-fg);
  border-left: 3px solid var(--band-heading);
  background: var(--band-surface);
}

.ticket-events {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.ticket-card {
  position: relative;
  padding: var(--space-3);
  background: var(--band-bg);
  border: 1px solid var(--band-line-strong);
  border-radius: var(--radius);
}
.ticket-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--thread-music), var(--thread-dance));
}
.ticket-card-name {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 1.2rem;
  letter-spacing: 0.02em;
}
/* The booked act, directly under the event name. The names carry the weight —
   they are the reason to book — so "ft." itself stays quiet and lets them read
   as the line's subject. */
.ticket-card-ft {
  margin: 0.35rem 0 0;
  font-family: var(--font-ui);
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--band-heading);
}
.ticket-card-ft .ft {
  font-weight: 400;
  font-style: italic;
  letter-spacing: 0.04em;
  color: var(--band-fg-dim);
}
.ticket-card-when {
  margin: 0.35rem 0 var(--space-2);
  font-family: var(--font-ui);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--band-fg-dim);
}
.ticket-card-blurb { margin: 0; color: var(--band-fg-dim); font-size: 0.95rem; }

.ticket-gate, .ticket-unlocked {
  padding: var(--space-3);
  background: var(--band-surface);
  border-left: 3px solid var(--thread-dance);
  border-radius: var(--radius);
}
.ticket-gate-note { margin: var(--space-1) 0 var(--space-3); color: var(--band-fg-dim); max-width: 60ch; }
.ticket-format {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.04em;
  background: var(--band-bg);
  padding: 0.1rem 0.4rem;
  border-radius: var(--radius);
}
.ticket-label {
  display: block;
  font-family: var(--font-ui);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--band-fg-dim);
  margin-bottom: var(--space-1);
}
.ticket-row { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: stretch; }
.ticket-input {
  flex: 1 1 260px;
  min-width: 0;
  padding: 0.85rem 1rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1rem;
  letter-spacing: 0.06em;
  color: var(--band-fg);
  background: var(--band-bg);
  border: 1px solid var(--band-line-strong);
  border-radius: var(--radius);
}
.ticket-input:focus-visible { outline: 2px solid var(--thread-dance); outline-offset: 2px; }
.ticket-help { margin: var(--space-1) 0 0; font-size: 0.8rem; color: var(--band-fg-dim); }
.ticket-status { margin: var(--space-2) 0 0; font-size: 0.9rem; }
.ticket-status.is-error { color: var(--thread-music); font-weight: 500; }
@media (max-width: 520px) {
  .ticket-row .btn { width: 100%; justify-content: center; }
}
