/* =====================================================================
   FESTIVAL PAGE STYLES
   Extracted from netlify/functions/get-festival-page.js (Phase 3,
   4 Aug 2026), where it lived as ~1,600 lines of CSS inside a JS
   template literal — the single biggest block of page-local styling
   left on the site.

   It is NOT promoted into main.css, deliberately. Almost none of it is
   shared: of 299 rules, the only overlap with main.css was three media
   queries. Moving festival-only styling into the global stylesheet
   would make every page on the site download CSS that one page uses.
   Its own file is the right home — one place to change the festival
   look, editable as real CSS rather than a template literal, and
   cacheable separately.

   The page still loads main.css first, so tokens resolve here.
   Namespaced under .festival-* / .ftv / .fdest.
   ===================================================================== */

/* Festival accent palette (Cal eyeball post-#346): toxic retired —
           it's the site brand colour, not festival-distinctive. Coral
           (#ff5d3b warm orange-red) and cyan (#00d4ff electric blue) join
           pink + purple as the four festival-only options. The site brand
           --color-pink stays untouched in main.css. */
        /* ── Festival hero: Option A "title cuts through" (#329) ───────── */
        .festival-hero-accent-amber  { --festival-accent: #f2a03d; } /* warm honey-amber — bear-flag territory */
        .festival-hero-accent-coral  { --festival-accent: #ff5d3b; }
        .festival-hero-accent-cyan   { --festival-accent: #00d4ff; }
        .festival-hero-accent-pink   { --festival-accent: var(--color-pink, #ec4899); }
        .festival-hero-accent-purple { --festival-accent: var(--color-purple, #c93b80); }

        /* Festival structural tokens — the festival look in one place.
           (--festival-accent above is the per-festival colour; these are the
           shared surfaces/text. Edit here to retune every festival at once.) */
        .festival-page {
            --festival-on-accent: #1a0b00;                  /* text on the bright accent (pills/chips/date) */
            --festival-panel: rgba(13, 1, 21, 0.88);        /* glass logo card + sticky day-bar */
            --festival-card-bg: rgba(255, 255, 255, 0.035); /* programme row card */
            --festival-card-bg-hover: rgba(255, 255, 255, 0.06);
            --festival-hairline: rgba(255, 255, 255, 0.07); /* card borders, pill bg, glass border */
            --festival-muted: #cbb8d8;                       /* muted meta / stats text */
        }

        /* Section wrapper. Single integrated composition — no flex-column
           split between mosaic-above + title-below. ATF cap targets a 13"
           MacBook Air (~900px viewport, ~800px usable). Mobile keeps a 4:5
           portrait band so the title legibility is preserved. */
        .festival-hero-section {
            text-align: center;
            display: block;
            position: relative;
            max-height: min(700px, 85vh);
            min-height: 480px;
        }
        @media (min-width: 768px) {
            .festival-hero-section { max-height: min(820px, 88vh); min-height: 600px; }
        }

        /* Stage — full-bleed canvas hosting the lead artwork, secondary
           fan and rule-band title. Children are absolutely positioned
           (lead, fan); the title rule-band sits flow with auto-margins so
           it always centres horizontally even if the artwork is missing. */
        .festival-hero-stage {
            position: relative;
            width: 100%;
            height: 100%;
            min-height: inherit;
            max-height: inherit;
            /* overflow: hidden remains off (#341). Wall + lead each clip
               their own oversized inner content. Stage stays the layout
               container, layered via grid so band, wall, and lead all
               share the same cell. */
            background: #0a0a0a;
            isolation: isolate;
            display: grid;
            place-items: center;
        }
        /* All stage children share grid cell 1×1 — they layer in z-order:
           lead (0) → wall (1) → band (3). */
        .festival-hero-stage > .festival-hero-lead,
        .festival-hero-stage > .festival-hero-wall,
        .festival-hero-stage > .festival-hero-band {
            grid-row: 1;
            grid-column: 1;
        }
        .festival-hero-stage--text-only {
            /* Solid accent wash for the zero-event / no-art case. The
               eyebrow + name + dates ride the same rule-band treatment so
               the composition stays consistent across modes. */
            background: var(--festival-accent);
        }

        /* Lead artwork — full-bleed background image with a dark gradient
           overlay so the white title meets WCAG AA on arbitrary photos.
           Cloudinary g_auto crop already framed the focal subject in the
           4:5 / 16:9 transform applied upstream (see heroSrc). */
        .festival-hero-lead {
            /* Lead artwork is intentionally ambient — the festival page can
               surface ANY top-of-list event poster here, so we don't want
               it to read as "the festival's hero image". Heavy blur +
               brightness/saturation knock-down + slight upscale (so the
               blurred edges don't feather inside the stage) push it back
               into atmospheric texture. Title and secondary fan dominate. */
            position: absolute;
            inset: 0;
            z-index: 0;
            overflow: hidden; /* clip the inner img's scale(1.1) + blur so the ambient backdrop doesn't bleed past the stage */
            pointer-events: none;
        }
        .festival-hero-lead img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
            /* transform + filter live on the img (not the parent) so
               .festival-hero-lead's overflow:hidden actually clips them —
               an element's own overflow can't contain its own transform/filter. */
            transform: scale(1.1);
            filter: blur(24px) brightness(0.45) saturate(0.7);
        }
        .festival-hero-lead__dim {
            /* Dark vertical gradient — strengthened now that the lead is
               ambient/blurred, to keep #fff title at AA contrast against
               whatever colour bled through the blur. */
            position: absolute;
            inset: 0;
            background: linear-gradient(to top, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.4));
            pointer-events: none;
        }

        /* Title band — vertical centring wrapper for eyebrow / name /
           dates. Previously this had a yellow accent rule crossing the
           artwork at title mid-height (Option A signature); removed per
           Cal eyeball — the rule competed with the title and felt heavy
           against the new ambient backdrop. */
        .festival-hero-band {
            position: relative;
            z-index: 3;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            text-align: center;
            color: #fff;
            width: 100%;
            max-width: 1100px;
            margin: 0 auto;
            padding: 64px 24px;
            min-height: inherit;
            box-sizing: border-box;
        }
        @media (min-width: 768px) {
            .festival-hero-band { padding: 80px 32px; }
        }
        .festival-hero-band__eyebrow {
            font-family: var(--font-display);
            font-weight: 700;
            font-size: 0.75rem;
            letter-spacing: 0.2em;
            text-transform: uppercase;
            color: rgba(255, 255, 255, 0.7);
            margin: 0 0 12px;
            text-shadow: none;
        }
        .festival-hero-stage--text-only .festival-hero-band__eyebrow {
            color: rgba(0, 0, 0, 0.65);
        }
        .festival-hero-band__name {
            /* Title typography — Syne 800 white, no rotation. Cal eyeball
               follow-up to #335: scale reduced from clamp(2.25rem, 5vw, 4rem)
               to clamp(2rem, 4.2vw, 3.5rem) so long names (e.g. "Birmingham
               Drag & Burlesque Festival") have more horizontal headroom in
               the layered flank composition. Posters now keep their full
               size and the title floats over them when needed. */
            font-family: var(--font-display);
            font-weight: 800;
            font-size: clamp(2rem, 4.2vw, 3.5rem);
            line-height: 0.95;
            letter-spacing: -0.02em;
            margin: 0;
            text-transform: uppercase;
            color: #fff;
            /* Stylised dark stamp behind the title now does the heavy
               lifting for legibility (Cal eyeball follow-up to #339).
               Text shadow simplified to a subtle depth cue — was a
               layered halo over-compensating for a weak radial scrim. */
            text-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
            max-width: 100%;
        }
        .festival-hero-stage--text-only .festival-hero-band__name {
            /* Was color:#000, which measured 1.17:1 against the stage card
               (rgba(13,1,21,.88) over the accent wash) — the page's H1,
               effectively invisible. Black dates from the #330 hero rebuild,
               when the text-only backdrop was a light accent wash and the
               title "cut through" it; the dark card came later and nothing
               revisited the colour.

               This is the 0-events fallback (see resolveHeroMode), so it's the
               state EVERY newly-added festival ships in until its line-up is
               tagged — the worst page to have an unreadable heading on.

               White matches the base .festival-hero-band__name rule and
               measures 18:1. The shadow comes back for the lighter end of the
               accent gradient. */
            color: #fff;
            text-shadow: 0 2px 12px rgba(0, 0, 0, 0.55);
        }
        /* Logo-lockup title — the festival's own logo replaces the text H1. */
        .festival-hero-band__name--logo {
            display: flex;
            justify-content: center;
        }
        /* Visually-hidden heading text inside the logo lockup — keeps a real
           text <h1> for SEO + screen readers without showing duplicate text. */
        .festival-hero-band__sr {
            position: absolute;
            width: 1px;
            height: 1px;
            padding: 0;
            margin: -1px;
            overflow: hidden;
            clip: rect(0, 0, 0, 0);
            white-space: nowrap;
            border: 0;
        }
        .festival-hero-band__logo {
            width: auto;
            /* Cap with vw (a *definite* unit), not % — a percentage max-width
               against a shrink-wrapping flex parent is ignored during intrinsic
               sizing, so an oversized logo (e.g. a 1472px source) drags the
               whole hero card out to its max width. vw constrains reliably. */
            max-width: min(86vw, 460px);
            height: auto;
            filter: drop-shadow(0 2px 10px rgba(0, 0, 0, 0.45));
        }
        .festival-hero-band__dates {
            font-family: 'Roboto', sans-serif;
            font-weight: 300;
            font-size: clamp(1rem, 1.8vw, 1.375rem);
            letter-spacing: 0.02em;
            margin: 18px 0 0;
            color: rgba(255, 255, 255, 0.85);
            text-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
        }
                .festival-hero-stage--text-only .festival-hero-band__dates {
            /* Same defect as the title above, same cause: black on the dark
               stage card measured 1.13:1. Restores the base rule's white.

               Deliberately NOT left to fall through to the accent colour that
               every other hero uses — that reads well for coral (5.91:1), cyan
               (10.18), pink (5.11) and amber (8.47), but PURPLE fails at
               3.79:1, so a purple festival would ship an unreadable date line.
               White is 13:1 against all five. */
            color: rgba(255, 255, 255, 0.85);
            text-shadow: 0 2px 12px rgba(0, 0, 0, 0.55);
        }

        /* Year sticker — top-right of the stage, accent fill, -3° rotation. */

        /* Poster wall — Cal pivot replacing the flank composition (#330,
           #333, #334, #335, #336, #338, #339, #341 arc). Cal: "posters IN
           THE HERO still clipped top and bottom, the background in the
           hero text is good but i'd want more of a pattern made up of the
           posters in that case as the background image, maybe a angled
           offset tiled grid or something? BE SUPER CREATIVE".

           A tilted, brick-offset tile WALL fills the hero background.
           Heavily darkened so it reads as graphic texture, not foreground —
           the title's stylised dark stamp (#340) still does the legibility
           work on top. The wall is decorative (aria-hidden); event links
           remain on the day-grouped grid below the hero. */
        .festival-hero-wall {
            position: absolute;
            inset: 0;
            overflow: hidden;  /* clips the rotated, oversized inner */
            z-index: 1;
            pointer-events: none;
        }
        .festival-hero-wall__inner {
            /* Oversized + rotated. 220% width × 240% height with a -9° tilt
               guarantees the rotated rectangle covers every edge of the
               stage with no bare corner showing the dark background. Cal
               eyeball follow-up to PR #343 bumped tile sizes 140/180/200 →
               160/240/320 and reduced grid 9×14 → 7×10; inner box widened
               200/220 → 220/240 to keep edges fully covered after rotation. */
            position: absolute;
            top: 50%;
            left: 50%;
            width: 220%;
            height: 240%;
            transform: translate(-50%, -50%) rotate(-9deg);
            display: flex;
            flex-direction: column;
            gap: 14px;
            /* Tone-down, not knock-out — tiles read as content, not just
               background mood (PR #342 Cal eyeball: "more prominent").
               brightness(0.65) keeps a slight darken so the title stamp
               (#0a0a0a) still slabs cleanly above; saturate(1.05) gives
               a small pop. The radial vignette (::after) handles the
               extra legibility lift behind the title cluster. */
            filter: brightness(0.65) saturate(1.05) contrast(1.0);
            opacity: 1;
        }
        /* Soft dark vignette in the centre band so the title stamp +
           text still reads cleanly over now-bright tiles. Sits above
           tiles, below the band/stamp (band has z-index 3, stamp ::before
           is z-index -1 inside the band → still above this overlay since
           overlay is z-index 1 on the wall, and the wall's container is
           z-index 1 on the stage; stamp ends up at effective z 2 which
           is > 1). Corners stay un-dimmed so prominence is preserved. */
        .festival-hero-wall::after {
            content: "";
            position: absolute;
            inset: 0;
            background: radial-gradient(ellipse 60% 50% at center, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0.15) 60%, transparent 100%);
            pointer-events: none;
            z-index: 1;
        }
        .festival-hero-wall__row {
            display: flex;
            flex-direction: row;
            gap: 14px;
            flex: 0 0 auto;
        }
        /* Brick offset — every other row pushed by ~half a tile + gap so
           tiles interlock instead of forming a sterile grid. Per-breakpoint
           offset matches half the tile width + half the gap. */
        .festival-hero-wall__row--odd {
            transform: translateX(88px);
        }
        .festival-hero-wall__tile {
            width: 160px;
            aspect-ratio: 4 / 5;
            flex: 0 0 auto;
            overflow: hidden;
            /* Slightly punchier shadow now that tiles read as content
               rather than ambient texture (PR #342 Cal eyeball). */
            box-shadow: 0 6px 16px rgba(0, 0, 0, 0.6);
            background: #111;
            border-radius: 6px;
        }
        /* Per-tile micro-rotation REMOVED in PR #343 (Cal eyeball: "neater
           grid though angled"). The whole wall keeps its -9° rotation, but
           individual tiles now sit straight within the grid — the brick
           offset on odd rows still provides visual rhythm without the
           noisier hand-cut wobble. */
        .festival-hero-wall__tile img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
            border-radius: inherit;
        }
        @media (min-width: 768px) {
            .festival-hero-wall__inner { gap: 18px; }
            .festival-hero-wall__row { gap: 18px; }
            .festival-hero-wall__tile { width: 240px; }
            .festival-hero-wall__row--odd { transform: translateX(129px); }
        }
        @media (min-width: 1280px) {
            .festival-hero-wall__tile { width: 320px; }
            .festival-hero-wall__row--odd { transform: translateX(169px); }
        }

        /* Stylised title stamp — relocated from the now-deleted
           '.festival-hero-secondary-fan--flank .festival-hero-band::before'
           selector to plain '.festival-hero-stage .festival-hero-band::before'
           since the band no longer lives inside a flank wrapper. Visual
           outcome unchanged — same rotated dark slab, accent-coloured
           offset shadow, black border, mobile/desktop variants (#340).

           Stack: stamp z-index -1 inside the band, band z-index 3 on the
           stage → effective stack order: lead (0) → wall (1) → stamp (2)
           → band children (3). Stamp sits BEHIND text but in front of the
           wall, exactly as designed. */
        .festival-hero-stage > .festival-hero-band {
            /* Cap the band's effective hit area so it doesn't span the
               full stage width — keeps the stamp visually anchored to the
               title cluster instead of being a giant slab. */
            position: relative;
            z-index: 3;
            width: auto;
            max-width: min(100%, 900px);
            padding: 0 24px;
            min-height: 0;
            pointer-events: none;
        }
        .festival-hero-stage > .festival-hero-band > * {
            pointer-events: auto;
        }
        .festival-hero-stage .festival-hero-band::before {
            content: "";
            position: absolute;
            inset: -20px -32px;
            background: #0a0a0a;
            transform: rotate(-1.25deg);
            box-shadow:
                6px 6px 0 var(--festival-accent),
                0 24px 48px rgba(0, 0, 0, 0.6);
            border: 2px solid #000;
            z-index: -1;
            pointer-events: none;
        }
        @media (min-width: 768px) {
            .festival-hero-stage .festival-hero-band::before {
                inset: -24px -56px;
                box-shadow:
                    10px 10px 0 var(--festival-accent),
                    0 32px 64px rgba(0, 0, 0, 0.65);
            }
        }
        /* Hide the stamp in text-only mode — accent wash carries legibility. */
        .festival-hero-stage--text-only .festival-hero-band::before {
            display: none;
        }

        /* Legacy class scaffolding (mosaic / single / textonly / title) is
           retained ONLY where direct test surfaces still exist. Option A is
           the rendered composition; the helpers below back the v2 unit
           tests for resolveMosaicLayout, posterRotation, and
           renderHeroTitle which assert on internal helpers, not page
           markup. */
        .festival-hero-mosaic {
            position: relative;
            margin: 0 auto;
            min-height: 240px;
            padding: 32px 16px 24px;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        @media (min-width: 768px) { .festival-hero-mosaic { min-height: 380px; padding: 40px 24px 32px; } }
        @media (min-width: 1024px) { .festival-hero-mosaic { min-height: 440px; } }

        .festival-hero-poster {
            /* Per-poster position lives in --poster-transform so :hover
               can layer scale() on top WITHOUT dropping translate/rotate
               (Design Blocker #2 on PR #321). Default = rotation only;
               side posters override --poster-transform with translate+rotate.
               Width tightened (#324) so the fan composition fits within
               the 700px ATF hero cap on MacBook Air 13". aspect-ratio
               keeps the 4:5 poster proportion intact. */
            --poster-transform: rotate(var(--poster-rot, 0deg));
            position: absolute;
            display: block;
            width: 160px;
            aspect-ratio: 4 / 5;
            transform: var(--poster-transform);
            box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
            transition: transform 0.3s ease;
            background: #111;
        }
        @media (min-width: 768px) { .festival-hero-poster { width: 190px; } }
        @media (min-width: 1024px) { .festival-hero-poster { width: 220px; } }

        .festival-hero-poster img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
        }

        .festival-hero-poster::before {
            /* Tape strip at top-left corner per #320 brief — 60° rotation, ~3px tall. */
            content: "";
            position: absolute;
            top: -8px;
            left: -16px;
            width: 64px;
            height: 3px;
            background: var(--tape-colour, var(--color-pink, var(--color-pink)));
            opacity: 0.85;
            transform: rotate(-60deg);
            transform-origin: top left;
            box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
        }
        .festival-hero-poster--tape-toxic::before { --tape-colour: var(--color-pink, var(--color-pink)); }
        .festival-hero-poster--tape-pink::before { --tape-colour: var(--color-pink, #ec4899); }
        .festival-hero-poster--tape-purple::before { --tape-colour: var(--color-purple, #c93b80); }
        .festival-hero-poster--tape-amber::before { --tape-colour: #f2a03d; }
        .festival-hero-poster--tape-coral::before { --tape-colour: #ff5d3b; }
        .festival-hero-poster--tape-cyan::before { --tape-colour: #00d4ff; }
        .festival-hero-poster--tape-cream::before { --tape-colour: #f9f0dc; }

        .festival-hero-poster:hover {
            /* Layer scale on top of the per-poster transform — never replace it. */
            transform: var(--poster-transform) scale(1.04);
            z-index: 30;
        }
        .festival-hero-poster:focus-visible {
            outline: 4px solid var(--color-pink);
            outline-offset: 4px;
        }

        /* Centre poster (index 0) — largest, on top, no rotation. */
        .festival-hero-poster--0 {
            --poster-transform: rotate(0deg) scale(1.15);
            z-index: 20;
        }
        .festival-hero-poster--0:hover { transform: rotate(0deg) scale(1.2); }

        /* Side posters fan out. Hide >4 on tablet, hide >3 on mobile. */
        .festival-hero-poster--1 { --poster-transform: translate(-110%, 4%) rotate(var(--poster-rot, 0deg)); z-index: 15; }
        .festival-hero-poster--2 { --poster-transform: translate(110%, 4%) rotate(var(--poster-rot, 0deg)); z-index: 15; }
        .festival-hero-poster--3 { --poster-transform: translate(-200%, -2%) rotate(var(--poster-rot, 0deg)); z-index: 12; }
        .festival-hero-poster--4 { --poster-transform: translate(200%, -2%) rotate(var(--poster-rot, 0deg)); z-index: 12; }
        .festival-hero-poster--5 { --poster-transform: translate(-285%, 6%) rotate(var(--poster-rot, 0deg)); z-index: 8; }
        .festival-hero-poster--6 { --poster-transform: translate(285%, 6%) rotate(var(--poster-rot, 0deg)); z-index: 8; }

        @media (max-width: 1023px) {
            .festival-hero-poster--5,
            .festival-hero-poster--6 { display: none; }
        }
        @media (max-width: 767px) {
            .festival-hero-poster--3,
            .festival-hero-poster--4 { display: none; }
            .festival-hero-poster--1 { --poster-transform: translate(-78%, 8%) rotate(var(--poster-rot, 0deg)); opacity: 0.85; }
            .festival-hero-poster--2 { --poster-transform: translate(78%, 8%) rotate(var(--poster-rot, 0deg)); opacity: 0.85; }
        }

        /* Grid layout — v2 editorial override (mosaicLayout = 'grid'). 2×2
           for 4 events, 3×2 for 6, etc. Equal sizing, no rotation, 16px
           gap. Reads as "poster wall" rather than "hand of cards" — Cal's
           alternative to fan when the count is even and the festival has
           enough posters to commit to a deliberate grid. */
        .festival-hero-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 16px;
            max-width: 960px;
            margin: 0 auto;
            padding: 64px 16px 48px;
        }
        .festival-hero-grid[data-poster-count="4"] { grid-template-columns: repeat(2, 1fr); max-width: 640px; }
        @media (max-width: 767px) {
            .festival-hero-grid { grid-template-columns: repeat(2, 1fr); padding: 32px 16px; }
        }
        .festival-hero-grid-poster {
            display: block;
            aspect-ratio: 4 / 5;
            background: #111;
            box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
            transition: transform 0.2s ease;
            overflow: hidden;
        }
        .festival-hero-grid-poster img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
        }
        .festival-hero-grid-poster:hover { transform: translateY(-2px); }
        .festival-hero-grid-poster:focus-visible {
            outline: 4px solid var(--color-pink);
            outline-offset: 4px;
        }

        /* Festival title — v2 (#322 §2). Sits below the mosaic / single hero.
           Inherited typography per design-system §3.2 hero scale. The accent
           colour is no longer baked into the name — that's the accent band's
           job (rendered separately, between hero and presented-by strip). */
        .festival-hero-title {
            position: relative;
            z-index: 40;
            max-width: 800px;
            margin: 0 auto;
            padding: 4px 16px 16px;
            text-align: center;
            color: #fff;
        }
        .festival-hero-title__inner {
            /* Block-level so the eyebrow / name / dates each centre
               independently within the outer max-width box via
               text-align: center on .festival-hero-title. */
            position: relative;
            padding: 0 16px;
        }
        .festival-hero-title__eyebrow {
            font-family: var(--font-display);
            font-weight: 700;
            font-size: 0.75rem;
            letter-spacing: 0.2em;
            text-transform: uppercase;
            color: rgba(255, 255, 255, 0.4);
            margin: 0 0 12px;
        }
        .festival-hero-title__name {
            /* §3.3 #1 inherited treatment. Syne 800 white, NO rotation,
               NO sticker bg, NO misprint. Hero scale tightened (#326
               Cal eyeball) — the previous clamp(3rem, 8vw, 6rem) hit
               96px on a 13" MacBook Air which dwarfed the rest of the
               hero. clamp(2.25rem, 5vw, 4rem) = 36–64px. */
            font-family: var(--font-display);
            font-weight: 800;
            font-size: clamp(2.25rem, 5vw, 4rem);
            line-height: 0.95;
            letter-spacing: -0.02em;
            margin: 0;
            text-transform: uppercase;
            color: rgba(255, 255, 255, 0.95);
        }
        .festival-hero-title__dates {
            /* Space Grotesk Light 300 per §2 — distinct visual register from
               the Syne 800 name so the dates read as quiet annotation. */
            font-family: 'Roboto', sans-serif;
            font-weight: 300;
            font-size: clamp(1.125rem, 2vw, 1.5rem);
            letter-spacing: 0.02em;
            margin: 18px 0 0;
            color: rgba(255, 255, 255, 0.7);
        }

        /* Single hero — solo poster, blurred ambient backdrop. */
        .festival-hero-single {
            position: relative;
            width: 100%;
            min-height: 360px;
            display: flex;
            align-items: center;
            justify-content: center;
            overflow: hidden;
            background: #0a0a0a;
        }
        @media (min-width: 768px) {
            .festival-hero-single { min-height: 420px; }
        }
        .festival-hero-single--empty {
            /* var(--color-purple)/40 is Tailwind opacity shorthand and
               INVALID inside a CSS gradient (the stop gets dropped). Use
               color-mix() so the design var still drives the stop, with
               an explicit rgba() fallback for older engines. */
            background: linear-gradient(135deg, var(--surface-3), rgba(201, 59, 128, 0.4), var(--surface-0));
            background: linear-gradient(135deg, var(--surface-3), color-mix(in srgb, var(--color-purple, #c93b80) 40%, transparent), var(--surface-0));
            color: rgba(255, 255, 255, 0.3);
            font-size: 4rem;
        }
        .festival-hero-single__bg {
            position: absolute;
            inset: -10%;
            background-size: cover;
            background-position: center;
            filter: blur(40px) saturate(1.3);
            transform: scale(1.15);
            opacity: 0.6;
        }
        .festival-hero-single img {
            position: relative;
            max-width: min(90vw, 480px);
            max-height: 55vh;
            object-fit: contain;
            box-shadow: 0 16px 48px rgba(0, 0, 0, 0.6);
        }

        /* Text-only hero — coloured background, big title, "events coming soon". */
        .festival-hero-textonly {
            background: var(--festival-accent);
            color: #000;
            padding: 48px 16px;
            text-align: center;
            position: relative;
        }
        @media (min-width: 768px) {
            .festival-hero-textonly { padding: 64px 16px; }
        }
        .festival-hero-textonly__inner { max-width: 800px; margin: 0 auto; text-align: center; }
        .festival-hero-textonly__eyebrow {
            font-family: var(--font-display);
            font-weight: 800;
            font-size: 0.75rem;
            letter-spacing: 0.2em;
            margin: 0 0 16px;
            opacity: 0.7;
        }
        .festival-hero-textonly__name {
            /* v2 (#322 §2): Syne 800 — Bungee Outline rejected here too. */
            font-family: var(--font-display);
            font-weight: 800;
            font-size: clamp(2.5rem, 9vw, 6rem);
            line-height: 0.95;
            margin: 0;
            text-transform: uppercase;
            letter-spacing: -0.02em;
        }
        .festival-hero-textonly__dates {
            font-family: var(--font-display);
            font-weight: 800;
            font-size: clamp(1rem, 2vw, 1.5rem);
            letter-spacing: 0.05em;
            margin: 24px 0 0;
        }
        .festival-hero-textonly__hint {
            font-family: 'Roboto', sans-serif;
            font-weight: 500;
            font-size: 1rem;
            margin: 32px 0 0;
            opacity: 0.7;
        }

        .festival-hero-name {
            font-size: clamp(2.5rem, 7vw, 5.5rem);
            line-height: 0.9;
        }

        /* ── Meta block (#320 §2) ──────────────────────────────────────── */
        .festival-meta-block { display: flex; flex-direction: column; gap: 24px; }
        .festival-meta-row {
            display: flex;
            flex-wrap: wrap;
            justify-content: space-between;
            align-items: center;
            gap: 16px;
        }
        .festival-meta-dates {
            font-family: var(--font-display);
            font-weight: 800;
            font-size: 1.125rem;
            color: #fff;
            margin: 0;
        }
        .festival-meta-counts { color: rgba(255, 255, 255, 0.6); margin: 0; }
        .festival-meta-count { color: #fff; font-weight: 500; }
        .festival-meta-count strong { color: var(--color-pink, var(--color-pink)); font-weight: 800; }
        .festival-meta-count-sep { margin: 0 12px; color: rgba(255, 255, 255, 0.3); }
        .festival-meta-eyebrow {
            color: rgba(255, 255, 255, 0.4);
            font-family: var(--font-display);
            font-weight: 800;
            font-size: 0.6875rem;
            letter-spacing: 0.2em;
            margin: 0 0 12px;
        }
        .festival-meta-headline-list,
        .festival-meta-chip-list {
            list-style: none;
            margin: 0;
            padding: 0;
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
        }
        .festival-meta-chip {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            padding: 4px 12px 4px 4px;
            border-radius: 999px;
            background: rgba(255, 255, 255, 0.05);
            color: rgba(255, 255, 255, 0.85);
            font-size: 0.875rem;
            transition: background 0.2s ease, color 0.2s ease;
            text-decoration: none;
        }
        .festival-meta-chip:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
        .festival-meta-chip img,
        .festival-meta-headline-avatar-fallback,
        .festival-meta-chip-logo-fallback {
            width: 32px;
            height: 32px;
            border-radius: 999px;
            object-fit: cover;
            background: #222;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            color: rgba(255, 255, 255, 0.5);
        }
        .festival-meta-chip--headline { padding-right: 16px; }
        .festival-meta-intro {
            color: rgba(255, 255, 255, 0.85);
            font-size: 1rem;
            line-height: 1.5;
            max-width: 60ch;
            margin: 0;
        }
        .festival-meta-cta {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 16px;
            margin-top: 8px;
        }
        .festival-meta-cta-text {
            color: rgba(255, 255, 255, 0.7);
            font-size: 0.9375rem;
            margin: 0;
        }
        .festival-meta-cta-btn {
            display: inline-flex;
            align-items: center;
            background: var(--color-pink, var(--color-pink));
            color: #000;
            font-weight: 800;
            font-size: 0.875rem;
            text-transform: uppercase;
            letter-spacing: 0.15em;
            padding: 10px 20px;
            border-radius: 999px;
            min-height: 44px;
            transition: background 0.2s ease;
            text-decoration: none;
        }
        .festival-meta-cta-btn:hover { background: #fff; }

        /* ── Day-grouped programme (#320 §3) ───────────────────────────── */
        .festival-events-grouped .festival-day-heading {
            position: sticky;
            top: 80px; /* Below the global sticky header. */
            z-index: 10;
            background: var(--color-bg, #0c0c0e);
            padding-top: 12px;
        }

        /* ── Cross-link sections (#320 §4) ─────────────────────────────── */
        .festival-xlink-section-head { margin-bottom: 20px; }
        .festival-xlink-section-title {
            font-family: var(--font-display);
            font-weight: 800;
            font-size: clamp(1.25rem, 3vw, 1.75rem);
            color: #fff;
            text-transform: uppercase;
            letter-spacing: -0.02em;
            margin: 0;
        }
        .festival-xlink-row {
            display: flex;
            gap: 12px;
            overflow-x: auto;
            scroll-snap-type: x mandatory;
            -webkit-overflow-scrolling: touch;
            padding-bottom: 12px;
        }
        .festival-xlink-card {
            flex: 0 0 auto;
            width: 168px;
            scroll-snap-align: start;
            background: rgba(255, 255, 255, 0.03);
            border: 1px solid rgba(255, 255, 255, 0.08);
            border-radius: 12px;
            overflow: hidden;
            transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
            text-decoration: none;
            color: inherit;
        }
        .festival-xlink-card:hover {
            background: rgba(255, 255, 255, 0.06);
            border-color: rgba(255, 255, 255, 0.18);
            transform: translateY(-2px);
        }
        .festival-xlink-card:focus-visible {
            outline: 3px solid var(--color-pink);
            outline-offset: 2px;
        }
        .festival-xlink-card-img {
            width: 100%;
            aspect-ratio: 1 / 1;
            background: #111;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .festival-xlink-card-img img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
        }
        .festival-xlink-card-fallback {
            color: rgba(255, 255, 255, 0.3);
            font-size: 2rem;
        }
        .festival-xlink-card-body { padding: 12px 14px; }
        .festival-xlink-card-name {
            font-family: var(--font-display);
            font-weight: 700;
            font-size: 0.875rem;
            color: #fff;
            margin: 0;
            line-height: 1.2;
        }
        .festival-xlink-card-badge {
            display: inline-block;
            margin-top: 6px;
            font-size: 0.6875rem;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 0.1em;
            color: var(--color-pink, var(--color-pink));
        }
        .festival-xlink-seeall {
            display: inline-block;
            margin-top: 16px;
            color: rgba(255, 255, 255, 0.6);
            font-size: 0.875rem;
            font-weight: 700;
            text-decoration: underline;
            text-decoration-color: rgba(255, 255, 255, 0.2);
            text-underline-offset: 4px;
        }
        .festival-xlink-seeall:hover { color: #fff; }

        /* ── Presented-by strip (v2, #322 §4) ─────────────────────────── */
        .festival-presented-by__inner {
            display: flex;
            flex-direction: column;
            gap: 40px;
        }
        @media (min-width: 768px) {
            .festival-presented-by__inner { gap: 56px; }
        }
        .festival-presented-row {
            display: flex;
            flex-direction: column;
            gap: 12px;
        }
        .festival-presented-row__eyebrow {
            /* Bumped from 0.75rem/700/alpha-0.4 — the section now reads as
               featured, not footnote (Cal eyeball). */
            font-family: var(--font-display);
            font-weight: 800;
            font-size: 0.875rem;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            color: rgba(255, 255, 255, 0.65);
            margin: 0 0 4px;
        }
        .festival-presented-row__items {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 16px;
        }
        .festival-presented-row--headline .festival-presented-row__items { gap: 24px; }
        .festival-presented-row--supporting .festival-presented-row__items { gap: 20px; }
        .festival-presented-row--partner .festival-presented-row__items { gap: 12px; }

        /* Lineup band (the "FEATURING //" row) — centred so it reads as a
           deliberate festival line-up rather than a sparse left-aligned strip
           with a dead right gutter. Logos are normalised to roughly wordmark
           height so the mixed row of logos + stylised names sits on one tidy
           optical line. */
        .festival-presented-row--lineup {
            align-items: center;
            text-align: center;
        }
        .festival-presented-row--lineup .festival-presented-row__eyebrow {
            margin-bottom: 8px;
        }
        .festival-presented-row--lineup .festival-presented-row__items {
            justify-content: center;
            gap: 18px 30px;
            max-width: 60rem;
            margin: 0 auto;
        }
        /* normalise logo tiles (non-wordmark) to a consistent, compact height */
        .festival-presented-row--lineup .festival-sponsor-tile--supporting:not(.festival-lineup-tile--wordmark) {
            width: auto;
            height: 58px;
        }
        @media (min-width: 768px) {
            .festival-presented-row--lineup .festival-sponsor-tile--supporting:not(.festival-lineup-tile--wordmark) {
                height: 64px;
            }
        }

        /* Sponsor tile sizing per tier. logoStyle:'dark' wraps in a panel
           so dark-on-light logos render legibly on the dark bg; 'light'
           (the default) sits directly on the page. */
        .festival-sponsor-tile {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            transition: transform 0.2s ease, opacity 0.2s ease;
            text-decoration: none;
            color: inherit;
        }
        .festival-sponsor-tile:hover { transform: translateY(-1px); opacity: 0.9; }
        .festival-sponsor-tile:focus-visible {
            outline: 3px solid var(--color-pink);
            outline-offset: 2px;
        }
        .festival-sponsor-tile img {
            display: block;
            height: 100%;
            width: auto;
            max-width: 100%;
            object-fit: contain;
            object-position: center;
        }
        .festival-sponsor-tile--panel {
            background: rgba(255, 255, 255, 0.05);
            border-radius: 8px;
            padding: 12px 18px;
        }
        .festival-sponsor-tile--supporting {
            width: 120px;
            height: 64px;
        }
        .festival-sponsor-tile--partner {
            width: 88px;
            height: 44px;
        }
        @media (min-width: 768px) {
            .festival-sponsor-tile--supporting {
                width: 160px;
                height: 80px;
            }
            .festival-sponsor-tile--partner {
                width: 112px;
                height: 56px;
            }
        }

        /* Headline-tier stage banner — white-panel cards with accent-coloured
           offset shadow, framing the row top + bottom so it reads as a
           dedicated feature block. Mirrors the year-sticker / title-stamp
           design language (#345 accent extension). */
        .festival-headline-sponsors {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 20px;
            padding: 32px 0;
            text-align: center;
            border-top: 2px solid rgba(255, 255, 255, 0.08);
            border-bottom: 2px solid rgba(255, 255, 255, 0.08);
        }
        .festival-headline-sponsors__eyebrow {
            font-family: var(--font-display);
            font-weight: 800;
            font-size: 0.875rem;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            color: var(--festival-accent);
            margin: 0;
        }
        .festival-headline-sponsors__panels {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            justify-content: center;
            gap: 24px;
        }
        @media (min-width: 768px) {
            .festival-headline-sponsors__panels { gap: 40px; }
        }
        .festival-sponsor-headline {
            display: flex;
            align-items: center;
            justify-content: center;
            background: #fff;
            padding: 20px 28px;
            min-width: 200px;
            min-height: 120px;
            box-shadow: 6px 6px 0 var(--festival-accent), 0 12px 32px rgba(0, 0, 0, 0.5);
            border: 2px solid #000;
            transition: transform 0.2s ease, box-shadow 0.2s ease;
            text-decoration: none;
        }
        .festival-sponsor-headline:hover {
            transform: translate(-2px, -2px);
            box-shadow: 8px 8px 0 var(--festival-accent), 0 16px 40px rgba(0, 0, 0, 0.55);
        }
        .festival-sponsor-headline:focus-visible {
            outline: 3px solid var(--festival-accent);
            outline-offset: 4px;
        }
        .festival-sponsor-headline img {
            display: block;
            width: 100%;
            height: 100px;
            max-width: 240px;
            object-fit: contain;
            object-position: center;
        }
        @media (min-width: 768px) {
            .festival-sponsor-headline {
                min-width: 240px;
                min-height: 140px;
                padding: 24px 32px;
            }
            .festival-sponsor-headline img {
                height: 120px;
                max-width: 280px;
            }
        }
        .festival-sponsor-headline__name {
            font-family: var(--font-display);
            font-weight: 800;
            font-size: 1.25rem;
            color: #000;
            text-transform: uppercase;
            letter-spacing: 0.02em;
        }
        /* Single-headline case — bigger, alone, reads as a stage banner. */
        .festival-headline-sponsors[data-count="1"] .festival-sponsor-headline {
            min-width: 320px;
            min-height: 160px;
        }
        .festival-headline-sponsors[data-count="1"] .festival-sponsor-headline img {
            height: 140px;
            max-width: 360px;
        }
        .festival-sponsor-tile__name {
            font-family: 'Roboto', sans-serif;
            font-weight: 600;
            font-size: 0.875rem;
            color: rgba(255, 255, 255, 0.7);
            letter-spacing: 0.02em;
        }
        .festival-sponsor-tile--partner .festival-sponsor-tile__name { font-size: 0.8125rem; }

        /* Lineup wordmark — brand name set as a stylised wordmark when no logo
           asset exists. Auto-width (overrides the fixed logo-tile box) so the
           text isn't clipped; brand colour via the inline --wm-color var,
           falling back to the festival accent. The subtle offset shadow echoes
           the site's .misprint brand treatment. */
        .festival-sponsor-tile.festival-lineup-tile--wordmark {
            width: auto;
            height: auto;
            padding: 6px 4px;
        }
        .festival-lineup__wordmark {
            font-family: var(--font-display);
            font-weight: 800;
            text-transform: uppercase;
            letter-spacing: -0.02em;
            font-size: 1.25rem;
            line-height: 1;
            white-space: nowrap;
            color: var(--wm-color, var(--color-pink));
            text-shadow: -1.5px -1px 0 rgba(255,255,255,0.06);
        }
        @media (min-width: 768px) {
            .festival-lineup__wordmark { font-size: 1.5rem; }
        }
        /* Per-brand display faces (#432 follow-up — Cal: "the logos need more
           font variance"). Each brand picks a face that echoes its own vibe so
           the row reads as a set of distinct logos, not one repeated typeface.
           --syne is the geometric default already styled above. */
        .festival-lineup__wordmark--anton {
            font-family: 'Anton', sans-serif;
            font-weight: 400;
            letter-spacing: 0.02em;
        }
        .festival-lineup__wordmark--rye {
            /* vintage saloon caps — pub/bar character */
            font-family: 'Rye', serif;
            font-weight: 400;
            letter-spacing: 0;
            text-shadow: none;
        }
        .festival-lineup__wordmark--bevan {
            /* heavy slab serif — traditional, sign-painted */
            font-family: 'Bevan', serif;
            font-weight: 400;
            text-transform: none;
            letter-spacing: 0;
            text-shadow: none;
        }
        .festival-lineup__wordmark--shrikhand {
            /* fat juicy display — camp, loud, fun */
            font-family: 'Shrikhand', serif;
            font-weight: 400;
            text-transform: none;
            letter-spacing: 0.01em;
            text-shadow: none;
        }

        /* Chip rows for organising collectives + venues. Chip styling is
           visually distinct from sponsor panels — bordered pill with
           optional logo dot. */
        .festival-presented-chip {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            padding: 6px 14px 6px 6px;
            border-radius: 999px;
            border: 1px solid rgba(255, 255, 255, 0.15);
            background: transparent;
            color: rgba(255, 255, 255, 0.85);
            font-family: 'Roboto', sans-serif;
            font-weight: 600;
            font-size: 0.875rem;
            text-decoration: none;
            transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
        }
        .festival-presented-chip:hover {
            background: rgba(255, 255, 255, 0.05);
            border-color: rgba(255, 255, 255, 0.3);
            color: #fff;
        }
        .festival-presented-chip:focus-visible {
            outline: 3px solid var(--color-pink);
            outline-offset: 2px;
        }
        .festival-presented-chip img,
        .festival-presented-chip__icon {
            width: 32px;
            height: 32px;
            border-radius: 999px;
            object-fit: cover;
            background: rgba(255, 255, 255, 0.06);
            display: inline-flex;
            align-items: center;
            justify-content: center;
            color: rgba(255, 255, 255, 0.5);
        }

        /* ── Per-festival accent extensions (#345 Cal eyeball) ─────────────
           The hero already inherits a per-festival --festival-accent (toxic /
           pink / purple). These rules push the same accent + poster motif
           into the body of the page so each festival reads as visually
           distinct from the others. All scoped to .festival-page (or the
           grouped events wrapper) to avoid bleeding into other surfaces. */

        /* A. Day-heading accent stamp — small skewed accent tag prefixing
              each day header in multi-day festivals. Same visual vocabulary
              as the year sticker, smaller + inline. */
        .festival-page .festival-day-heading {
            display: inline-flex;
            align-items: center;
            gap: 12px;
        }
        .festival-page .festival-day-heading__stamp {
            display: inline-block;
            width: 8px;
            height: 22px;
            background: var(--festival-accent, var(--color-pink, var(--color-pink)));
            transform: skewX(-12deg);
            box-shadow: 2px 2px 0 #000;
            flex-shrink: 0;
        }

        /* B. Event card corner diamond — tiny accent square in the top-right
              corner of every linked event card on a festival page. Echoes the
              year-sticker beat from the hero. Scoped via the grid wrapper so
              it only fires inside festival pages, not the homepage card grid. */
        .festival-page .festival-day-events { position: relative; }
        .festival-page .festival-day-events > a {
            position: relative;
        }
        .festival-page .festival-day-events > a::after {
            content: "";
            position: absolute;
            top: 10px;
            right: 10px;
            width: 8px;
            height: 8px;
            background: var(--festival-accent, var(--color-pink, var(--color-pink)));
            transform: rotate(45deg);
            box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.6);
            z-index: 2;
            pointer-events: none;
        }

        /* C. Section heading underline — short accent bar under each major
              h2 in the festival body (programme, cross-links). Scoped to
              direct h2 children of the relevant section wrappers. */
        .festival-page .festival-events-grouped > h2,
        .festival-page section > h2.text-2xl,
        .festival-page .festival-xlink-section-title {
            position: relative;
            padding-bottom: 14px;
        }
        .festival-page .festival-events-grouped > h2::after,
        .festival-page section > h2.text-2xl::after,
        .festival-page .festival-xlink-section-title::after {
            content: "";
            position: absolute;
            bottom: 0;
            left: 0;
            width: 56px;
            height: 4px;
            background: var(--festival-accent, var(--color-pink, var(--color-pink)));
        }

        /* ── Poster-style body (#349 Cal eyeball) ─────────────────────────
           Cal: "I want the pages outside of the hero to be much better
           designed for engagement. They need to feel like a stylish
           festival poster." Body redesigned as a printed poster lineup —
           numbered section headers, big poster day banners, typographic
           programme rows, and a magazine-style drop-cap on the intro. All
           accent-driven so every festival reads as visually distinct. */

        /* Section header — copy-led heading ("The Running Order"). The
           numbered "01 / PROGRAMME" lockup (num + divider spans) was retired. */
        .festival-page .festival-section-header {
            display: flex;
            align-items: baseline;
            gap: 18px;
            padding: 32px 0 18px;
            margin: 0 0 8px;
            border-bottom: 4px solid var(--festival-accent, var(--color-pink, var(--color-pink)));
            font-family: var(--font-display);
            text-transform: uppercase;
            letter-spacing: 0.01em;
            line-height: 0.85;
        }
        .festival-page .festival-section-header__label {
            font-weight: 800;
            font-size: clamp(1.5rem, 3.5vw, 2.5rem);
            color: #fff;
            letter-spacing: 0.02em;
        }
        /* The legacy section-underline rule (C above) added a 56px bar to
           every section h2. We now own that beat via the section-header's
           own border-bottom — kill the legacy ::after on this header so we
           don't double-rule. */
        .festival-page .festival-events-grouped > h2.festival-section-header::after,
        .festival-page section > h2.festival-section-header::after {
            content: none;
        }

        /* Poster day-header — big skewed weekday name with an accent rule
           running across the page, plus a small meta line beneath. */
        .festival-page .festival-day-group { padding: 0 0 24px; }
        /* Sticky day header — pins under the site header while you scroll the
           day's events; the next day's header pushes it up and takes over, so
           it doubles as the "which day am I in" cue (replaces the old day-jump
           bar). Compact horizontal row + solid bg so it sits cleanly over the
           rows scrolling beneath it without dominating the viewport. */
        .festival-page .festival-poster-day-header {
            position: sticky;
            top: 88px;
            z-index: 30;
            display: flex;
            align-items: center;
            flex-wrap: wrap;
            gap: 0.45rem 1rem;
            padding: 0.85rem 0 0.8rem;
            margin: 0 0 0.6rem;
            background: var(--color-bg);
            border-bottom: 3px solid var(--festival-accent, var(--color-pink, var(--color-pink)));
            font-family: var(--font-display);
            text-align: left;
            text-transform: none;
            letter-spacing: normal;
        }
        .festival-page .festival-poster-day-header__name {
            font-weight: 800;
            font-size: clamp(1.75rem, 4vw, 2.75rem);
            line-height: 1;
            letter-spacing: -0.01em;
            color: #fff;
            text-transform: uppercase;
            margin: 0;
        }
        /* date pill sits inline next to the weekday — neutralise the block
           pill's vertical margins inside the flex row */
        .festival-page .festival-poster-day-header .festival-day-heading__stamp { margin: 0; }
        .festival-page .festival-poster-day-header__meta {
            margin-left: auto;
            font-weight: 700;
            font-size: 0.8125rem;
            letter-spacing: 0.2em;
            color: var(--festival-accent, var(--color-pink, var(--color-pink)));
            text-transform: uppercase;
            font-family: var(--font-display);
        }
        @media (max-width: 767px) {
            .festival-page .festival-poster-day-header { top: 70px; padding: 0.7rem 0 0.65rem; }
        }

        /* Programme rows — typographic running-order list. */
        .festival-page .festival-poster-rows {
            display: block;
            margin: 0;
        }
        .festival-page .festival-poster-row {
            display: grid;
            grid-template-columns: 88px 280px 1fr;
            align-items: center;
            gap: 24px;
            padding: 28px 0;
            border-top: 1px solid rgba(255, 255, 255, 0.12);
            color: inherit;
            text-decoration: none;
            transition: background 0.2s ease, padding-left 0.2s ease;
        }
        .festival-page .festival-poster-row:last-child {
            border-bottom: 1px solid rgba(255, 255, 255, 0.12);
        }
        .festival-page .festival-poster-row--no-poster {
            grid-template-columns: 88px 1fr;
        }
        .festival-page .festival-poster-row:hover {
            background: linear-gradient(90deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0) 80%);
            padding-left: 16px;
        }
        .festival-page .festival-poster-row:focus-visible {
            outline: 3px solid var(--festival-accent, var(--color-pink, var(--color-pink)));
            outline-offset: 4px;
        }
        .festival-page .festival-poster-row__time {
            font-family: var(--font-display);
            font-weight: 800;
            font-size: clamp(1.25rem, 2.4vw, 1.75rem);
            letter-spacing: -0.01em;
            color: var(--festival-accent, var(--color-pink, var(--color-pink)));
            line-height: 1;
        }
        .festival-page .festival-poster-row__main { min-width: 0; }
        .festival-page .festival-poster-row__name {
            font-family: var(--font-display);
            font-weight: 800;
            font-size: clamp(1.5rem, 3.8vw, 2.6rem);
            line-height: 1.05;
            color: #fff;
            text-transform: uppercase;
            letter-spacing: -0.01em;
            margin: 0 0 6px;
            transition: color 0.2s ease;
        }
        .festival-page .festival-poster-row:hover .festival-poster-row__name {
            color: var(--festival-accent, var(--color-pink, var(--color-pink)));
        }
        .festival-page .festival-poster-row__meta {
            font-family: 'Roboto', sans-serif;
            font-weight: 500;
            font-size: 0.8125rem;
            letter-spacing: 0.06em;
            text-transform: uppercase;
            color: rgba(255, 255, 255, 0.55);
            margin: 0;
        }
        /* Cal eyeball follow-up to #349: programme-row posters must respect
           their TRUE aspect ratio. A 1:1 square or 16:9 landscape poster
           used to be cropped to 4:5 by aspect-ratio + object-fit: cover.
           Now the wrapper is width-constrained only and the img renders at
           its natural ratio (height: auto + object-fit: contain). The grid
           uses align-items: center so varying poster heights sit centred
           next to the time + name column. The hero wall keeps its 4:5
           smart-crop tile — different surface, different rules. */
        .festival-page .festival-poster-row__poster {
            width: 280px;
            overflow: visible;
            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
            transition: transform 0.2s ease;
            border-radius: 6px;
        }
        .festival-page .festival-poster-row:hover .festival-poster-row__poster {
            transform: rotate(-1.5deg) scale(1.02);
        }
        .festival-page .festival-poster-row__poster img {
            display: block;
            width: 100%;
            height: auto;
            object-fit: contain;
            border-radius: inherit;
        }
        @media (min-width: 640px) and (max-width: 1023px) {
            .festival-page .festival-poster-row {
                grid-template-columns: 64px 180px 1fr;
                gap: 16px;
                padding: 20px 0;
            }
            .festival-page .festival-poster-row--no-poster {
                grid-template-columns: 64px 1fr;
            }
            .festival-page .festival-poster-row__poster { width: 180px; }
        }
        @media (max-width: 639px) {
            .festival-page .festival-poster-row {
                display: flex;
                flex-direction: column;
                align-items: stretch;
                gap: 12px;
                padding: 20px 0;
            }
            .festival-page .festival-poster-row:hover { padding-left: 0; }
            .festival-page .festival-poster-row__poster {
                width: 100%;
                max-width: 100%;
            }
            .festival-page .festival-poster-row__poster img { width: 100%; }
            .festival-page .festival-poster-row__main {
                display: flex;
                flex-direction: column;
                gap: 4px;
            }
            .festival-page .festival-poster-row__time {
                font-size: 0.875rem;
                font-weight: 800;
                letter-spacing: 0.1em;
            }
            .festival-page .festival-poster-row__name {
                font-size: clamp(1.4rem, 6vw, 2rem);
            }
            .festival-page .festival-poster-row__meta {
                font-size: 0.8125rem;
            }
            .festival-page .festival-poster-row--no-poster { display: flex; }
            .festival-page .festival-poster-day-header__name { transform: skewY(-1deg); }
        }

        /* Drop-cap on the editorial intro — magazine-style poster opening.
           Targets the first paragraph only via .festival-intro__lede so a
           multi-paragraph description doesn't cap every one. */
        .festival-page .festival-intro__lede::first-letter {
            font-family: var(--font-display);
            font-weight: 800;
            font-size: 4em;
            color: var(--festival-accent, var(--color-pink, var(--color-pink)));
            float: left;
            line-height: 0.85;
            margin: 6px 12px 0 0;
            text-transform: uppercase;
        }

        /* Official site + socials row beneath the blurb. */
        .festival-links {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 12px 16px;
            margin-top: 28px;
            padding-top: 24px;
            border-top: 1px solid rgba(255, 255, 255, 0.1);
        }
        .festival-link {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            min-height: 44px;
            color: rgba(255, 255, 255, 0.8);
            text-decoration: none;
            transition: color 0.2s ease, border-color 0.2s ease;
        }
        .festival-link:hover { color: var(--festival-accent, var(--color-pink)); }
        .festival-link--site {
            font-family: 'Roboto', sans-serif;
            font-weight: 700;
            font-size: 0.9375rem;
            letter-spacing: 0.01em;
            padding: 0 18px;
            border: 1.5px solid rgba(255, 255, 255, 0.25);
            border-radius: 999px;
        }
        .festival-link--site:hover { border-color: var(--festival-accent, var(--color-pink)); }
        .festival-link--site i { color: var(--festival-accent, var(--color-pink)); }
        .festival-link--social {
            width: 44px;
            font-size: 1.375rem;
        }

        /* The corner-diamond rule (B above) was scoped to the day-events
           grid children (a-tags that used to be card boxes). Now they are
           typographic rows; the diamond looks crowded perched on a row.
           Suppress it on poster rows specifically (keep the rule alive for
           any non-row usage of .festival-day-events elsewhere). */
        .festival-page .festival-poster-rows > a::after { content: none; }

        /* E. Selection colour — picks up the festival accent so highlights
              feel themed instead of the browser default. Per-accent text
              colour keeps WCAG AA contrast on each background. */
        .festival-page.festival-hero-accent-amber ::selection {
            background: var(--festival-accent);
            color: #000; /* warm amber is bright — black wins for AA */
        }
        .festival-page.festival-hero-accent-coral ::selection {
            background: var(--festival-accent);
            color: #000; /* coral mid-tone — black wins for AA */
        }
        .festival-page.festival-hero-accent-cyan ::selection {
            background: var(--festival-accent);
            color: #000; /* electric cyan is bright — black for contrast */
        }
        .festival-page.festival-hero-accent-pink ::selection {
            background: var(--festival-accent);
            color: #fff; /* pink reads better with white */
        }
        .festival-page.festival-hero-accent-purple ::selection {
            background: var(--festival-accent);
            color: #fff; /* purple needs white for AA */
        }

        /* ===================================================================
           PROTOTYPE — festival programme: per-day timeline (spine + nodes),
           event hover-cards, venue/category chips, full-date pills, sticky
           day-jump + stats bar, glass logo card. Appended last so it augments
           the #349 poster styling above (same .festival-page specificity).
           =================================================================== */

        /* full-date pill (overrides the legacy skewed decorative stamp:
           rule A sets transform: skewX + a tiny w/h, so reset transform too) */
        .festival-page .festival-day-heading__stamp {
            position: static; display: inline-flex; align-items: center; justify-content: center;
            width: auto; min-width: 0; max-width: none; height: auto; overflow: visible; top: auto; left: auto;
            transform: none;
            margin: 0.15rem 0 0.7rem; padding: 0.42rem 0.95rem; border-radius: 999px; white-space: nowrap;
            background: var(--festival-accent); color: var(--festival-on-accent); border: none;
            font-family: var(--font-display), system-ui, sans-serif; font-weight: 800; font-size: 0.875rem; line-height: 1; letter-spacing: 0.03em; text-transform: none;
            box-shadow: 0 8px 18px -8px color-mix(in srgb, var(--festival-accent) 75%, transparent);
        }

        /* per-day timeline spine */
        .festival-page .festival-day-events.festival-poster-rows { position: relative; padding-left: 36px; }
        .festival-page .festival-day-events.festival-poster-rows::before {
            content: ''; position: absolute; left: 15px; top: 14px; bottom: 14px; width: 2px; border-radius: 2px;
            background: linear-gradient(180deg, var(--festival-accent), color-mix(in srgb, var(--festival-accent) 12%, transparent));
        }

        /* event row -> hover card, with a node on the spine */
        .festival-page .festival-poster-row {
            position: relative; margin: 0 0 1rem; padding: 1.1rem 1.45rem;
            border: 1px solid var(--festival-hairline); border-radius: 18px;
            background: var(--festival-card-bg); text-decoration: none;
            box-shadow: 0 12px 30px -18px rgba(0,0,0,0.65);
            transition: transform .18s ease, box-shadow .18s ease, background .18s ease, border-color .18s ease;
        }
        .festival-page .festival-poster-row::after { display: none; }   /* drop the old hairline divider */
        .festival-page .festival-poster-row::before {
            content: ''; position: absolute; left: -21px; top: 50%; transform: translateY(-50%);
            width: 14px; height: 14px; border-radius: 50%; background: var(--festival-accent);
            box-shadow: 0 0 0 4px var(--color-bg); z-index: 2;
        }
        .festival-page .festival-poster-row:hover {
            transform: translateY(-3px); padding-left: 1.45rem; background: var(--festival-card-bg-hover);
            border-color: color-mix(in srgb, var(--festival-accent) 45%, transparent);
            box-shadow: 0 18px 38px -18px rgba(0,0,0,0.75);
        }
        .festival-page .festival-poster-row__time { flex: 0 0 auto; min-width: 4.2rem; }

        /* venue + category chips */
        .festival-page .festival-poster-row__meta { display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; margin-top: .4rem; }
        .festival-page .festival-poster-row__meta .row-venue { font-weight: 600; color: var(--color-light); opacity: .92; }
        .festival-page .festival-poster-row__meta .row-tag {
            font-size: 0.6875rem; letter-spacing: .05em; text-transform: uppercase; font-weight: 700;
            padding: .22rem .6rem; border-radius: 999px; color: var(--festival-accent);
            background: color-mix(in srgb, var(--festival-accent) 15%, transparent);
            border: 1px solid color-mix(in srgb, var(--festival-accent) 38%, transparent);
        }

        /* day-group jump target lands below the sticky site header + day header */
        .festival-page .festival-day-group { scroll-margin-top: 96px; }

        /* hero: glass logo card over the mosaic, accent dates */
        .festival-hero-stage .festival-hero-band::before { display: none; }
        .festival-hero-stage > .festival-hero-band {
            background: var(--festival-panel); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
            border-radius: 24px; border: 1px solid var(--festival-hairline);
            box-shadow: 0 22px 50px -18px rgba(0,0,0,0.8); padding: 1.75rem 2rem 2.75rem;
        }
        .festival-hero-band__dates { color: var(--festival-accent); font-weight: 800; margin-top: 1.4rem; }
        /* prominent ticket / website CTA in the hero card */
        .festival-hero-band__cta {
            display: inline-flex; align-items: center; gap: .5rem;
            margin-top: 1.5rem; min-height: 48px; box-sizing: border-box;
            padding: .8rem 1.7rem; border-radius: 999px;
            background: var(--festival-accent); color: var(--festival-on-accent);
            font-family: var(--font-display), system-ui, sans-serif; font-weight: 800;
            text-transform: uppercase; letter-spacing: .04em; font-size: 0.9375rem;
            text-decoration: none;
            box-shadow: 0 12px 28px -10px color-mix(in srgb, var(--festival-accent) 70%, transparent);
            transition: transform .3s var(--bounce-physics, ease), box-shadow .3s ease;
        }
        .festival-hero-band__cta:hover { transform: translateY(-2px); box-shadow: 0 18px 36px -12px color-mix(in srgb, var(--festival-accent) 80%, transparent); }

        /* connect hero -> body: dissolve the mosaic into the body bg at the
           hero's foot (replaces the removed hard accent-band divider). Sits
           above the poster wall (z 1), below the glass band (z 3) + sticker. */
        .festival-page .festival-hero-stage::after {
            content: ''; position: absolute; inset: 0; z-index: 2; pointer-events: none;
            background: linear-gradient(to top, var(--color-bg) 0%, color-mix(in srgb, var(--color-bg) 60%, transparent) 22%, transparent 55%);
        }

        @media (max-width: 640px) {
            .festival-page .festival-day-events.festival-poster-rows { padding-left: 0; }
            .festival-page .festival-day-events.festival-poster-rows::before,
            .festival-page .festival-poster-row::before { display: none; }
        }
