/* ===========================================================================
   RED CENTRENATS - SITE STYLESHEET
   Concept: "Redline Country"  (see design/DESIGN-DIRECTION.md)

   This is the base/layout/typography/component/device layer. It contains NO
   raw hex colours, NO raw pixel type sizes, NO raw durations and NO raw
   cubic-beziers - everything routes through public/css/tokens.css. Where a
   value genuinely didn't exist in tokens.css it was added there first (see
   the final report for the short list, each with a one-line reason inline).

   Two categories of value are deliberately NOT tokenised, on purpose:
     - hand-authored geometry (clip-path polygon points, SVG data-URIs, the
       tacho arc's circumference) - these are one-off shape recipes tied to a
       specific component's markup contract, not reusable design decisions.
     - the WCAG 44px target size and similar external standards constants
       ARE tokenised (--target-min) because they are reused everywhere.

   No CSS nesting is used anywhere in this file (no build step to flatten
   it) - every selector is written flat, even where nesting would be shorter.

   ---------------------------------------------------------------------------
   TABLE OF CONTENTS
   ---------------------------------------------------------------------------
     1.  Imports
     2.  Reset & base
     3.  Accessibility primitives (focus, skip-link, sr-only, target size)
     4.  Typography
     5.  Layout primitives (container, section, bleed, grid, stack)
     6.  Reveal & scroll-driven animation hooks (data-reveal / data-step)
     7.  Buttons & form controls
     8.  Components
         8.1  Header & navigation
         8.2  Hero
         8.3  Stat ticker / marquee
         8.4  Programme - "The Four Days" pinned sequence, plus (8.4b) the
              full day-by-day programme timetable block (views/blocks/program.ejs)
         8.5  Prize pool table
         8.6  Two-path entrant / spectator split
         8.7  Ticket tier cards
         8.8  FAQ accordion
         8.9  Sponsor / partner wall
         8.10 Countdown
         8.11 Newsletter
         8.12 Footer
         8.18 CTA band (single full-width conversion band) - numbered to
              continue after the concurrently-built 8.13-8.17 block components
              (see that section's own header comment for the collision-
              avoidance convention), not interleaved with them
     9.  Signature graphic devices (§F)
         9.1  Smoke Plume
         9.2  Tacho Redline Arc
         9.3  Staging Lights
         9.4  Licence-Plate Frame
         9.5  Scrutineering Sticker
         9.6  Torn Decal Edge
         9.7  Halftone Dust Wash
         9.8  Contour Horizon
     10. Acknowledgement of Country - deliberate exception (§I)
   =========================================================================== */

/* ===========================================================================
   1. IMPORTS
   =========================================================================== */
@import url('/css/fonts.css');
@import url('/css/tokens.css');


/* ===========================================================================
   2. RESET & BASE
   =========================================================================== */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  scroll-behavior: smooth; /* tokens.css's reduced-motion block forces this back to auto */
  color-scheme: dark;      /* native form controls / scrollbars default to the dark-first system */
  /* clip, not hidden: hidden creates a scroll container, which silently breaks
     position:sticky on the header the moment a .rcn-bleed breakout appears
     above it in the DOM. clip has no such side effect. */
  overflow-x: clip;
}

body {
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: var(--leading-body);
  /* Colour bruises (29 Jul, Rick: "unusual and arty transitions/fades/mild
     colour changes or colour bruises"). Four vast low-alpha radial washes of
     the two brand hues sit in the page field itself, positioned by PERCENTAGE
     of the document's full height - so they land at different depths on every
     page and the near-black field mildly warms and cools as you scroll
     through it, alternating red / ochre / ochre / red. Static paint, zero
     motion, zero JS; opaque bands (header, ticker, footer, and by design the
     Arrernte acknowledgement, SS10) simply cover them where they pass. */
  background:
    radial-gradient(70rem 44rem at 86% 4%, var(--bruise-red), transparent 70%),
    radial-gradient(56rem 40rem at 6% 30%, var(--bruise-ochre), transparent 70%),
    radial-gradient(74rem 50rem at 94% 58%, var(--bruise-ochre), transparent 70%),
    radial-gradient(62rem 44rem at 10% 86%, var(--bruise-red), transparent 70%)
    var(--surface-0);
  color: var(--ink-1);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
  min-height: 100vh;
}

img, picture, svg, video, canvas {
  max-width: 100%;
  display: block;
}
img { height: auto; }

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

button {
  background: none;
  border: none;
  cursor: pointer;
  text-align: inherit;
}

a {
  color: inherit;
  text-decoration: none;
}

ul[class], ol[class] { list-style: none; }

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

::selection {
  background: var(--color-red-500);
  color: var(--ink-1);
}

/* Paper-surface sections flip both native control theming and selection
   colour so a highlighted word on a paper card doesn't render dark-on-dark.
   (29 Jul: the ticket tier cards LEFT the paper surface - Rick: the light
   cards "do not feel like they belong to the rest of the website at all".
   The contact popup is now the only paper component, so this scoping is
   [data-surface="paper"] alone.) */
[data-surface="paper"] {
  color-scheme: light;
}
/* CONTRAST, not tidiness. The global `p { color: var(--ink-2) }` is a
   DARK-SURFACE secondary ink (#C9BFB6). Inherited onto a paper surface it put
   body copy at 1.52:1 against --surface-paper - effectively invisible.
   --ink-on-paper at 0.78 alpha gives a secondary tone that is genuinely
   secondary and still clears AA. Every text element that can land on paper is
   listed, because inheritance does not beat the element-level `p` rule. */
[data-surface="paper"] p,
[data-surface="paper"] li,
[data-surface="paper"] dd {
  color: color-mix(in srgb, var(--ink-on-paper) 78%, var(--surface-paper));
}
[data-surface="paper"] strong,
[data-surface="paper"] h1, [data-surface="paper"] h2,
[data-surface="paper"] h3, [data-surface="paper"] h4 {
  color: var(--ink-on-paper);
}
[data-surface="paper"] ::selection {
  background: var(--color-red-600);
  color: var(--surface-paper);
}

/* Respect the user's OS font-size preference; nothing in this system fixes
   font-size in absolute units that would fight browser zoom/text-resize. */


/* ===========================================================================
   3. ACCESSIBILITY PRIMITIVES
   =========================================================================== */

/* Skip link. Markup already exists on every page (per project brief); this is
   purely presentational: invisible until it receives focus, then it's the
   first paintable thing on the page. */
.skip-link {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  z-index: var(--z-modal);
  padding: var(--space-3) var(--space-6);
  background: var(--surface-0);
  color: var(--ink-1);
  font-family: var(--font-body);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  border-radius: var(--radius-md);
  border: 2px solid var(--color-red-500);
  transform: translateY(-150%);
  transition: transform var(--duration-fast) var(--ease-throttle);
}
.skip-link:focus {
  transform: translateY(0);
}

/* Screen-reader-only content: visually hidden, still in the accessibility
   tree. Unprefixed - this is a generic, widely-recognised utility name, not a
   component. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Universal focus treatment. outline is never removed without this
   replacement running in the same rule - see §I. :focus-visible only, so
   mouse users don't see a ring on every click. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}
/* Paper-surface components swap in the paper-tuned ring (§C: the inner ring
   colour has to match the surface it's adapting to, or it disappears). */
.rcn-plate:focus-visible,
[data-surface="paper"] a:focus-visible,
[data-surface="paper"] button:focus-visible {
  box-shadow: var(--focus-ring-paper);
}

/* Every element carrying this class is a genuine interactive target and gets
   the WCAG 44px minimum in both dimensions, regardless of its visual size. */
.rcn-tap-target {
  min-width: var(--target-min);
  min-height: var(--target-min);
}


/* ===========================================================================
   4. TYPOGRAPHY

   Binding rules from §D, enforced here rather than left to authors:
     - Display 1/2/3 and H4 are ALL CAPS, negative display tracking, tight
       leading. They use font-variation-settings to drive BOTH the optical-
       size (opsz) axis and the weight (wght) axis together - once you set
       font-variation-settings on a variable font, it overrides font-weight
       for that axis, so wght must live inside the same declaration or the
       two silently disagree.
     - UI labels/buttons/nav are uppercase Archivo semibold with POSITIVE
       tracking (--tracking-ui) - the opposite rule to display type, and
       deliberate (small caps text needs opening up, not tightening).
     - Body copy is sentence case, never uppercase, and never below weight
       400. The old site shipped font-weight:100 paragraphs; that mistake
       does not get a way back in here.
     - The odometer/numeral role is tabular-nums so digits don't reflow
       width while counting or updating.
   =========================================================================== */

h1, .rcn-display-1 {
  font-family: var(--font-display);
  font-weight: 900;
  font-variation-settings: 'opsz' var(--opsz-display), 'wght' 900;
  font-size: var(--text-display-1);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  text-transform: uppercase;
  text-wrap: balance;
}

h2, .rcn-display-2 {
  font-family: var(--font-display);
  font-weight: 800;
  font-variation-settings: 'opsz' var(--opsz-display), 'wght' 800;
  font-size: var(--text-display-2);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  text-transform: uppercase;
  text-wrap: balance;
}

h3, .rcn-display-3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-variation-settings: 'opsz' var(--opsz-heading), 'wght' 700;
  font-size: var(--text-display-3);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  text-transform: uppercase;
  text-wrap: balance;
}

h4, .rcn-h4 {
  font-family: var(--font-display);
  font-weight: 700;
  font-variation-settings: 'opsz' var(--opsz-label), 'wght' 700;
  font-size: var(--text-h4);
  line-height: var(--leading-h4);
  letter-spacing: var(--tracking-h4);
  text-transform: uppercase;
  text-wrap: balance; /* matches h1/h2/h3 (SS4) - a lone orphan word on h4's last line was otherwise
                          possible in narrower contexts (card captions, accordion-nested headings) */
}

p {
  font-weight: 400; /* floor, never lighter - see brief §A on the old site's font-weight:100 mistake */
  color: var(--ink-2);
  max-width: 68ch; /* measure control for readable prose; components override where needed */
}
p:first-child { margin-block-start: 0; }
p + p { margin-block-start: var(--space-4); }

.rcn-body-lg {
  font-size: var(--text-body-lg);
  line-height: var(--leading-body-lg);
  font-weight: 400;
  color: var(--ink-1);
}

.rcn-small {
  font-size: var(--text-small);
  line-height: var(--leading-small);
  font-weight: 500;
}

/* Micro / eyebrow label - uppercase, widest tracking in the system. */
.rcn-eyebrow {
  display: inline-block;
  font-family: var(--font-body);
  font-size: var(--text-micro);
  line-height: var(--leading-small);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  color: var(--color-ochre-300);
}

/* Odometer / numeral role - Archivo, tabular figures confirmed present in the
   shipped font (see fonts.css). font-feature-settings kept alongside the
   modern property as a belt-and-suspenders fallback for older engines. */
.rcn-odometer {
  font-family: var(--font-body);
  font-weight: 800;
  font-size: var(--text-odometer);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  color: var(--ink-1);
}

/* UI label mixin values (uppercase Archivo semibold, positive tracking) are
   applied directly on each interactive component (nav links, buttons, form
   labels, tags) rather than shared through a class, since this file has no
   preprocessor @extend/@mixin - see §7/§8 for the repeated declaration. */

/* Controlled "speed skew" - §D.
   Single DISPLAY WORDS ONLY, inside a Display 1/2 headline. Never body copy,
   never a ticket price, never any figure a user must read precisely: skewing
   a numeral a user is scanning for accuracy is explicitly forbidden by the
   brief. One word/numeral per headline, the rest of the line stays upright. */
.rcn-skew {
  display: inline-block;
  transform: skewX(var(--skew-emphasis));
  letter-spacing: var(--tracking-skew-compensate);
}

/* Plate-style character spacing, used by the licence-plate device (§9.4) and
   anywhere else Archivo needs to read like stamped plate characters. */
.rcn-tracking-plate {
  letter-spacing: var(--tracking-plate);
  font-variant-numeric: tabular-nums;
}

blockquote {
  font-family: var(--font-body);
  font-style: italic; /* Archivo ships a true variable italic, not synthetic oblique */
  font-size: var(--text-body-lg);
  line-height: var(--leading-body-lg);
  color: var(--ink-1);
  max-width: 60ch;
}


/* ===========================================================================
   5. LAYOUT PRIMITIVES
   =========================================================================== */

.rcn-container {
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

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

/* Full-viewport-width breakout, for photography only - never for body text,
   which always stays inside the .rcn-container measure (§E). Requires
   `overflow-x: clip` on html/body (set in §2) rather than `hidden`, or a
   sticky header anywhere on the page loses its stickiness. */
.rcn-bleed {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
}

/* 12-column grid helper. Mobile-first: every direct child is full-width
   until 640px, where the span utilities below take over. */
.rcn-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--gutter);
}
.rcn-grid > * { grid-column: span 12; }

@media (min-width: 640px) {
  .rcn-col-1  { grid-column: span 1; }
  .rcn-col-2  { grid-column: span 2; }
  .rcn-col-3  { grid-column: span 3; }
  .rcn-col-4  { grid-column: span 4; }
  .rcn-col-5  { grid-column: span 5; }
  .rcn-col-6  { grid-column: span 6; }
  .rcn-col-7  { grid-column: span 7; }
  .rcn-col-8  { grid-column: span 8; }
  .rcn-col-9  { grid-column: span 9; }
  .rcn-col-10 { grid-column: span 10; }
  .rcn-col-11 { grid-column: span 11; }
  .rcn-col-12 { grid-column: span 12; }
}

/* Vertical-rhythm flow utility (the "owl selector"). --stack-gap is
   overridable per instance without a new class. */
.rcn-stack {
  display: flex;
  flex-direction: column;
}
.rcn-stack > * + * {
  margin-block-start: var(--stack-gap, var(--space-6));
}
.rcn-stack--sm { --stack-gap: var(--space-3); }
.rcn-stack--lg { --stack-gap: var(--space-12); }


/* ===========================================================================
   6. REVEAL & SCROLL-DRIVEN ANIMATION HOOKS

   Convention (shared with public/js/motion.js, which owns the JS side):
     - Elements that should fade/slide in on scroll get `data-reveal`. Motion
       adds `.is-visible` via IntersectionObserver when the element enters
       the viewport (fires once). This file only defines the before/after
       states and the transition between them.
     - Pinned-sequence steps ("The Four Days") get `data-step`. Motion
       toggles `.is-active` on the step whose threshold is currently met.
     - Children of a `[data-reveal]` container stagger up to 6 deep at
       --stagger-step apart (§G); a 7th+ child gets no further delay, which
       is the "fall back to a single group fade" behaviour for long lists
       like the sponsor wall - wrap those in one reveal container instead of
       marking every logo individually.
   =========================================================================== */

/* PROGRESSIVE ENHANCEMENT - the .js-ready gate.

   motion.js adds `js-ready` to <html> as its very first action. Every
   animated-FROM state that depends on JS to ever be undone MUST be scoped under
   it, so that if motion.js 404s, throws, is blocked by CSP or is aborted on a
   flaky connection, the content is simply visible rather than stuck at
   opacity 0 forever.

   This is not theoretical: an unscoped `[data-reveal] { opacity: 0 }` renders
   the entire page blank whenever the script fails, and it fails silently. Do
   not remove the `html.js-ready` prefix from these two rule sets. */
html.js-ready [data-reveal] {
  opacity: 0;
  transform: translateY(var(--reveal-lift));
  transition: opacity var(--duration-slow) var(--ease-drift),
              transform var(--duration-slow) var(--ease-drift);
}
html.js-ready [data-reveal].is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Per-child stagger.

   This MUST be driven by the `--reveal-delay` custom property, because that is
   what motion.js actually sets on each `[data-reveal-child]` (capped at 6
   children - beyond the cap it deliberately leaves the property unset, which is
   how "fall back to a single group fade" is expressed).

   An earlier version of this file staggered with `[data-reveal] > *:nth-child(n)
   { transition-delay: ... }` instead. That was a silent no-op twice over: those
   children had no `transition` and no animated-from state of their own, so there
   was nothing for a delay to delay; and nothing anywhere read the
   `--reveal-delay` property the JS was setting. The two halves of the contract
   never met. Keep the property name in sync with motion.js. */
html.js-ready [data-reveal] [data-reveal-child] {
  --reveal-lift: var(--reveal-lift-child);
  opacity: 0;
  transform: translateY(var(--reveal-lift));
  transition: opacity var(--duration-slow) var(--ease-drift),
              transform var(--duration-slow) var(--ease-drift);
  transition-delay: var(--reveal-delay, 0ms);
}
html.js-ready [data-reveal].is-visible [data-reveal-child] {
  opacity: 1;
  transform: translateY(0);
}

/* SCROLL-LINKED REVEAL - the primary path.

   Rick, 3 Aug: the reveals were "short and sharp at the last moment". He is
   right, and the reason is worth writing down because the page looked fine in
   Chrome: the CONTAINERS were already scroll-linked here, but every
   [data-reveal-child] - which is the actual card, tile, panel or paragraph the
   visitor looks at - was not. Children had no entry in this block at all, so
   they sat at opacity 0 until the IntersectionObserver flipped `.is-visible`
   at the trigger line and then ran a 600ms transition. Container drifting in
   smoothly, contents popping. Only the pop is noticeable.

   So both are listed here now, and the range says exactly what Rick asked for:

     start  entry 0%              the instant the element's top edge crosses
                                  the bottom of the viewport - it begins at
                                  0 opacity the moment it "comes into view at
                                  the bottom".
     end    cover --reveal-travel  after that much further SCROLLING, which by
                                  the token's definition is the moment the
                                  element's top reaches the 45% trigger line.

   A LENGTH is used for the end, not the old `cover 30%`, because cover
   percentages are relative to (viewport + element) height: the same 30% meant
   a short card finished after ~330px of scroll and a tall section after
   ~900px, so no two blocks on a page agreed about pacing. A length makes every
   element travel the same distance and land in the same place on screen.

   Easing is --ease-throttle, not --ease-drift. --ease-drift is a heavy
   decelerate curve, correct for a timed transition, but scrubbed against
   scroll it dumps ~80% of the movement into the first quarter of the range -
   which is the "short and sharp" complaint reintroduced by other means.
   --ease-throttle is symmetric, so the movement is spread evenly across the
   scroll the way a scrub should be. */
@supports (animation-timeline: view()) {
  html.js-ready [data-reveal],
  html.js-ready [data-reveal] [data-reveal-child] {
    /* Longhands, not the `animation` shorthand: `animation-duration: auto` is
       only meaningful alongside a progress timeline, and an engine that parses
       the shorthand strictly would throw the WHOLE declaration away - which
       would leave these elements at the opacity 0 the rules above set them to.
       A silent blank page is not an acceptable failure mode for a typo. */
    animation-name: rcn-reveal-in;
    animation-duration: auto;
    animation-timing-function: var(--ease-throttle);
    animation-fill-mode: both;
    animation-timeline: view();
    animation-range: entry 0% cover var(--reveal-travel);
  }

  /* Per-child stagger, as extra scroll distance rather than a time delay - a
     scroll-linked animation has no clock for `animation-delay` to act on, so
     the fan has to be geometric. --reveal-index is set by motion.js on the
     same children (and under the same 6-child cap) that get --reveal-delay,
     which is what the timed fallback below still uses. */
  html.js-ready [data-reveal] [data-reveal-child] {
    animation-range: entry 0%
                     cover calc(var(--reveal-travel)
                                + var(--reveal-index, 0) * var(--reveal-stagger-dist));
  }

  /* "You are already home - stop scrubbing."

     motion.js adds this in exactly two situations, both of which are cases
     where scroll can never finish the job:
       - the element was already on screen when the page loaded, so scrubbing
         it would leave real content sitting half transparent above the fold;
       - the visitor has hit the bottom of the document, where there is no
         --reveal-travel of scrolling left to give.
     Dropping the animation hands the element back to the .is-visible rules
     above, which land it. Note this is NOT keyed off .is-visible itself: that
     class arrives on the CONTAINER at the trigger line, and killing the
     animation there would strand any child further down a tall grid mid-fade,
     which is the original bug with extra steps. */
  html.js-ready [data-reveal].rcn-reveal-landed,
  html.js-ready [data-reveal].rcn-reveal-landed [data-reveal-child] {
    animation: none;
  }
}

/* TIMED FALLBACK for engines with no scroll-driven animations (Firefox,
   Safari < 26). motion.js drives --reveal-p from 0 to 1 across the same scroll
   distance, so the two paths agree on where an element starts, where it lands
   and how far it lifts; only the machinery differs. If that scrubber ever
   fails it lands everything via .rcn-reveal-landed rather than leaving the
   page transparent - same stance as the .js-ready gate above. */
@supports not (animation-timeline: view()) {
  html.js-ready [data-reveal],
  html.js-ready [data-reveal] [data-reveal-child] {
    opacity: var(--reveal-p, 0);
    transform: translateY(calc((1 - var(--reveal-p, 0)) * var(--reveal-lift)));
    transition: none;
  }
  html.js-ready [data-reveal].rcn-reveal-landed,
  html.js-ready [data-reveal].rcn-reveal-landed [data-reveal-child] {
    --reveal-p: 1;
  }
}

@keyframes rcn-reveal-in {
  from { opacity: 0; transform: translateY(var(--reveal-lift)); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Reduced motion: present, not performed.

   This feature states its own reduced-motion behaviour rather than leaning on
   tokens.css's global `animation-duration: 0.001ms` reset, because that reset
   is a TIME instruction and a scroll-linked animation has no time axis to
   collapse - it would keep scrubbing against the scroll, which is precisely
   the movement the setting asks to be rid of. Information is kept: everything
   is simply already in place. */
@media (prefers-reduced-motion: reduce) {
  html.js-ready [data-reveal],
  html.js-ready [data-reveal] [data-reveal-child] {
    animation: none !important;
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* Pinned step markers - default dormant state, activated state.
   Scoped under .js-ready for the same reason as [data-reveal] above: without
   JS there is nothing to ever set .is-active, so every step would stay dimmed
   to 0.35 and the whole sequence would read as permanently disabled. */
html.js-ready [data-step] {
  opacity: 0.35;
  transform: scale(0.97) translateY(var(--space-2));
  transition: opacity var(--duration-base) var(--ease-throttle),
              transform var(--duration-base) var(--ease-throttle);
}
html.js-ready [data-step].is-active {
  opacity: 1;
  transform: scale(1) translateY(0);
}


/* ===========================================================================
   7. BUTTONS & FORM CONTROLS
   =========================================================================== */

.rcn-btn {
  position: relative;
  overflow: hidden;
  isolation: isolate; /* contains the smoke pseudo-element's mix-blend-mode to this button, not the page behind it */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--target-min);
  padding-inline: var(--space-6);
  padding-block: var(--space-3);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  border-radius: var(--radius-md);
  border: 2px solid transparent;
  transition: transform var(--duration-fast) var(--ease-throttle),
              box-shadow var(--duration-fast) var(--ease-throttle),
              background-color var(--duration-fast) var(--ease-throttle),
              border-color var(--duration-fast) var(--ease-throttle),
              color var(--duration-fast) var(--ease-throttle);
}

.rcn-btn--primary {
  background: var(--gradient-red);
  color: var(--ink-1);
  box-shadow: var(--shadow-1);
}
.rcn-btn--primary:hover,
.rcn-btn--primary:focus-visible {
  box-shadow: var(--glow-red), var(--shadow-2);
  transform: translateY(calc(var(--space-1) * -1));
}
.rcn-btn--primary:active {
  transform: translateY(0) scale(0.98);
  transition-timing-function: var(--ease-brake); /* the one place overshoot easing is allowed - card/button press only */
}

.rcn-btn--secondary {
  background: transparent;
  border-color: var(--border-strong);
  color: var(--ink-1);
}
.rcn-btn--secondary:hover,
.rcn-btn--secondary:focus-visible {
  border-color: var(--color-red-500);
  color: var(--color-red-300);
}

.rcn-btn--ghost {
  background: none;
  padding-inline: var(--space-2);
  color: var(--ink-2);
}
.rcn-btn--ghost:hover,
.rcn-btn--ghost:focus-visible {
  color: var(--ink-1);
}

.rcn-btn--on-paper {
  color: var(--ink-on-paper);
}
.rcn-btn--on-paper.rcn-btn--primary { color: var(--ink-1); } /* the gradient fill is still dark enough for ink-1 */

/* Primary-CTA smoke device (§F device 1b) - see §9.1 for the full device;
   this is just the hook wired to the button's interactive states. */
.rcn-btn--primary::after {
  content: '';
  position: absolute;
  inset: -40%;
  z-index: -1;
  /* Must name a real DERIVATIVE, not the master's filename. The image pipeline
     archives masters to _assets/masters/ and leaves only sized variants in
     public/media/, so `...-scaled.jpg` 404s silently and the smoke never shows. */
  background-image: var(--smoke-img, url('/media/photos/burnout-red-centrenats-2023-lynchy-3840x2560-1-scaled-1920.webp'));
  background-size: cover;
  background-position: center;
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity var(--duration-base) var(--ease-throttle);
  pointer-events: none;
}
.rcn-btn--primary:hover::after,
.rcn-btn--primary:focus-visible::after,
.rcn-btn--primary:active::after {
  opacity: 0.5;
}

/* ---------------- form controls ---------------- */

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

.rcn-field__label {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--ink-2);
}

.rcn-field__input,
.rcn-field__select,
.rcn-field__textarea {
  min-height: var(--target-min);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  color: var(--ink-1);
  font-family: var(--font-body);
  font-size: var(--text-body);
  transition: border-color var(--duration-fast) var(--ease-throttle),
              box-shadow var(--duration-fast) var(--ease-throttle);
}
/* Client-reported "field heights 75-118px, uneven": min-height alone let a
   native <select> come out ~7px shorter than a same-padded <input> (82 vs
   75px measured) - browsers give <select> its own UA-internal content-box
   metrics for the native dropdown chrome, which don't match a plain text
   input's line-box even with identical CSS padding/border, so min-height
   never actually got triggered for the select; it just kept its own,
   smaller, intrinsic height. An explicit height forces both onto the exact
   same box. Textarea is deliberately excluded - a taller multi-line control
   is correct, not uneven. */
.rcn-field__input,
.rcn-field__select {
  height: var(--field-h);
}
.rcn-field__textarea { min-height: calc(var(--target-min) * 2); resize: vertical; }
.rcn-field__input::placeholder,
.rcn-field__textarea::placeholder { color: var(--ink-3); }
.rcn-field__input:focus-visible,
.rcn-field__select:focus-visible,
.rcn-field__textarea:focus-visible {
  outline: none;
  border-color: var(--color-red-500);
  box-shadow: var(--focus-ring);
}

.rcn-field--error .rcn-field__input,
.rcn-field--error .rcn-field__textarea { border-color: var(--color-error-500); }
.rcn-field--success .rcn-field__input,
.rcn-field--success .rcn-field__textarea { border-color: var(--color-success-500); }

/* Validation message: colour is NEVER the only signal - every state pairs an
   icon with a text string (§I, §C do-not-list). */
.rcn-field__message {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-small);
  font-weight: 500;
}
.rcn-field__message--error { color: var(--color-red-300); }
.rcn-field__message--success { color: var(--color-success-500); }

.rcn-icon-check,
.rcn-icon-alert {
  width: 1em;
  height: 1em;
  flex: none;
  display: inline-block;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
}
.rcn-icon-check {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20 6 9 17l-5-5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20 6 9 17l-5-5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.rcn-icon-alert {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2 1 21h22L12 2Z' fill='black'/%3E%3Cline x1='12' y1='9' x2='12' y2='13' stroke='white' stroke-width='2'/%3E%3Ccircle cx='12' cy='16.5' r='1' fill='white'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2 1 21h22L12 2Z' fill='black'/%3E%3Cline x1='12' y1='9' x2='12' y2='13' stroke='white' stroke-width='2'/%3E%3Ccircle cx='12' cy='16.5' r='1' fill='white'/%3E%3C/svg%3E");
}


/* ===========================================================================
   8. COMPONENTS
   =========================================================================== */

/* ---------------------------------------------------------------------------
   8.1 HEADER & NAVIGATION
   Height 72-80px desktop (--header-h steps up at 960px in tokens.css), 64px
   mobile. Mobile hamburger becomes a full-height takeover panel.

   NAV-BAR BREAKPOINT - RAISED FROM 960px TO 1280px (client-reported "wonky
   nav" fix, see design/CLIENT-QUERIES.md history). The arithmetic:
     logo (~94px) + header__inner gap x2 (24px each) + Buy-tickets CTA (~146px)
     = ~288px fixed overhead, leaving the rest of the 1200px content-max
     container for the 7-link list.
     The 7 labels ("Tickets" .. "Become an Entrant") measure ~686px of text
     alone at this type scale. At 960-1279px the --gutter token is 32px,
     shrinking available content width below what a flex row can hold once
     you add ANY real padding to the links - the old flat `gap` gave every
     link a 0-width hit-zone boundary, so text-wrapping was the only escape
     valve flexbox had, which is exactly the "random gaps / two-line" bug the
     client flagged. At >=1280px, --gutter steps to 40px, but the container's
     own max-width is already capped at 1200px, so available content width
     stops shrinking further (1200 - 2*40 = 1120px) - a stable floor forever,
     unlike the 960-1279 range where it's still climbing. That stable floor
     comfortably fits logo + padded links + CTA with ~30px to spare (measured
     with Playwright, not eyeballed - see the report for the walkthrough).
     Below 1280px there simply isn't room for a legible one-line desktop nav
     no matter how it's padded, so the mobile takeover panel now covers the
     960-1279px range too. A cramped/wrapped bar is worse than a hamburger.
   --------------------------------------------------------------------------- */

.rcn-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: var(--surface-1);
  border-block-end: 1px solid var(--border-subtle);
  transition: background-color var(--duration-base) var(--ease-throttle),
              box-shadow var(--duration-base) var(--ease-throttle);
}
.rcn-header.is-scrolled {
  background: var(--surface-2);
  box-shadow: var(--shadow-2);
}

.rcn-header__inner {
  /* Positioned ancestor for .rcn-header__ctas below the desktop breakpoint -
     see the "Header- Desktop" card, 5 Aug 2026. Affects nothing else: every
     other child here is already in normal flex flow. */
  position: relative;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
}

/* `flex: 0 0 auto` is load-bearing, not tidiness.

   The logo's flex item is a <picture>, which is an inline element with no
   intrinsic width of its own - so flexbox is free to shrink it to nothing, and
   the image inside goes with it. With four menu items there was slack and it
   never happened. Adding Sponsors & Traders and Volunteers (client card "8",
   3 Aug 2026) removed the slack, and the RCN12 mark silently collapsed to ZERO
   WIDTH at every desktop size - present in the DOM, 64px tall, 0px wide.

   It cost nothing to fix and it is the site's primary brand mark, so it must
   never be the thing that gives way when the bar runs out of room. Anything
   added to this header in future should shrink the NAV, not this. */
.rcn-header__logo {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
}
/* The site's own mark is the largest thing in the bar, always. The colour
   lockup is a squarish stack, so it needs more HEIGHT than a wordmark would to
   carry the same visual weight - it is only ~1.7x as wide as it is tall. */
.rcn-header__logo img {
  height: calc(var(--header-h) - var(--space-4));
  width: auto;
}

.rcn-nav__list {
  display: none;
  align-items: center;
  /* Zero, deliberately - see .rcn-nav__link. Spacing between items now comes
     from equal padding on every link (an even hit-area rhythm) rather than a
     flat gap between wildly different label widths, which is what made the
     spacing read as random. */
  gap: 0;
  flex-wrap: nowrap;
  /* `ul[class]` only strips list-style (see SS2 Reset) - it does not zero the
     UA default 40px padding-inline-start a bare <ul> ships with. That stray
     40px was quietly eating into the nav's width budget on every page. */
  padding: 0;
}
/* 1280 is where four items fit. It is NOT where six fit - see client card "8",
   3 Aug 2026 (add Sponsors & Traders and Volunteers), which is parked pending a
   decision. Measured with six items in place: the bar needs 1683px, or 1503px
   with the label shortened to "Sponsors". Both are well above a 1440 laptop, so
   adding them costs the desktop menu on most screens. Do not raise this number
   without raising that trade-off. */
@media (min-width: 1280px) {
  .rcn-nav__list { display: flex; }
}

.rcn-nav__link {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--target-min);
  /* Padding-based hit area, not a fixed height and not a raw list gap - this
     is the optical-evenness fix. Every link gets the same breathing room
     regardless of label length, so "FAQ" and "Become an Entrant" read as
     equally-weighted tabs instead of a ragged mix of tight and loose gaps. */
  padding-inline: var(--space-2);
  /* The actual fix for the two-line wrap bug: without this, each link is a
     flex item whose automatic minimum width collapses to its longest WORD
     (flexbox's min-content-for-text rule), so the smallest available-width
     shortfall makes every multi-word label wrap. nowrap makes the link's
     content width a hard floor the flex algorithm cannot shrink below. */
  white-space: nowrap;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--ink-2);
  transition: color var(--duration-fast) var(--ease-throttle);
}
.rcn-nav__link::after {
  content: '';
  position: absolute;
  left: 0; right: 100%; bottom: 0;
  height: 2px;
  background: var(--color-red-500);
  transition: right var(--duration-fast) var(--ease-throttle);
}
.rcn-nav__link:hover,
.rcn-nav__link:focus-visible,
.rcn-nav__link[aria-current="page"] { color: var(--ink-1); }
.rcn-nav__link:hover::after,
.rcn-nav__link:focus-visible::after,
.rcn-nav__link[aria-current="page"]::after { right: 0; }

/* Hamburger toggle - visible only below the desktop nav breakpoint (now
   1280px, see the SS8.1 header comment above). Three bars collapse into a
   close mark via rotation/opacity, never a layout-triggering property. */
.rcn-hamburger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--target-min);
  height: var(--target-min);
  position: relative;
  color: var(--ink-1);
}
@media (min-width: 1280px) {
  .rcn-hamburger { display: none; }
}
.rcn-hamburger__bar,
.rcn-hamburger__bar::before,
.rcn-hamburger__bar::after {
  content: '';
  position: absolute;
  width: 22px;
  height: 2px;
  background: currentColor;
  transition: transform var(--duration-fast) var(--ease-throttle),
              opacity var(--duration-fast) var(--ease-throttle);
}
.rcn-hamburger__bar { transform: translateY(0); }
.rcn-hamburger__bar::before { transform: translateY(-7px); }
.rcn-hamburger__bar::after { transform: translateY(7px); }
.rcn-hamburger[aria-expanded="true"] .rcn-hamburger__bar { background: transparent; }
.rcn-hamburger[aria-expanded="true"] .rcn-hamburger__bar::before { transform: translateY(0) rotate(45deg); }
.rcn-hamburger[aria-expanded="true"] .rcn-hamburger__bar::after { transform: translateY(0) rotate(-45deg); }

/* Full-height mobile takeover panel.

   The off-canvas closed state is gated behind html.js-ready per the file-level
   progressive-enhancement contract: motion.js is what opens the panel, so with
   no JS the panel must render in normal document flow (a plain list under the
   header) rather than parked off-screen with nothing able to bring it back.

   The markup carries NO `hidden` attribute. It used to, and that was the bug
   behind "mobile menu won't open": Chrome's UA sheet declares
   `[hidden] { display: none !important }`, which no author `display` can
   override - so the panel stayed display:none even with .is-open applied and
   aria-expanded="true". Removal from the a11y tree/tab order while closed is
   `inert`'s job (motion.js SS9), not `hidden`'s. */
.rcn-nav-panel {
  display: flex;
  flex-direction: column;
  padding: var(--space-24) var(--gutter) var(--space-10);
  background: var(--surface-2);
}
html.js-ready .rcn-nav-panel {
  position: fixed;
  inset: 0;
  z-index: var(--z-nav-panel);
  transform: translateY(-100%);
  transition: transform var(--duration-base) var(--ease-throttle);
}
html.js-ready .rcn-nav-panel.is-open { transform: translateY(0); }
@media (min-width: 1280px) {
  .rcn-nav-panel { display: none; }
}

.rcn-nav-panel__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  /* Same stray UA 40px padding-inline-start as .rcn-nav__list (SS8.1) - zeroed
     for the same reason: it's leftover bullet indent, not a design choice. */
  padding: 0;
}
.rcn-nav-panel__link {
  display: block;
  padding-block: var(--space-3);
  font-family: var(--font-display);
  font-weight: 800;
  font-variation-settings: 'opsz' var(--opsz-heading), 'wght' 800;
  font-size: var(--text-display-3);
  text-transform: uppercase;
  letter-spacing: var(--tracking-display);
  color: var(--ink-1);
}
/* js-ready gate: the hidden initial state only exists when the JS that
   reveals it is running. Unscoped, a motion.js failure would leave every
   mobile nav link invisible - silently. */
html.js-ready .rcn-nav-panel__link {
  opacity: 0;
  transform: translateX(calc(var(--space-6) * -1));
  transition: opacity var(--duration-base) var(--ease-drift),
              transform var(--duration-base) var(--ease-drift);
}
/* Staggered list-item reveal, kept from the current site's genuinely fine
   pattern per §H, restyled to the new type/colour system. Fires when the
   panel opens; capped the same way as the generic reveal stagger. */
.rcn-nav-panel.is-open .rcn-nav-panel__link { opacity: 1; transform: translateX(0); }
.rcn-nav-panel.is-open .rcn-nav-panel__list > *:nth-child(1) { transition-delay: calc(var(--stagger-step) * 0); }
.rcn-nav-panel.is-open .rcn-nav-panel__list > *:nth-child(2) { transition-delay: calc(var(--stagger-step) * 1); }
.rcn-nav-panel.is-open .rcn-nav-panel__list > *:nth-child(3) { transition-delay: calc(var(--stagger-step) * 2); }
.rcn-nav-panel.is-open .rcn-nav-panel__list > *:nth-child(4) { transition-delay: calc(var(--stagger-step) * 3); }
.rcn-nav-panel.is-open .rcn-nav-panel__list > *:nth-child(5) { transition-delay: calc(var(--stagger-step) * 4); }
.rcn-nav-panel.is-open .rcn-nav-panel__list > *:nth-child(6) { transition-delay: calc(var(--stagger-step) * 5); }
.rcn-nav-panel.is-open .rcn-nav-panel__list > *:nth-child(7) { transition-delay: calc(var(--stagger-step) * 5); }
.rcn-nav-panel.is-open .rcn-nav-panel__list > *:nth-child(8) { transition-delay: calc(var(--stagger-step) * 5); }


/* ---------------------------------------------------------------------------
   8.2 HERO
   Full-bleed real photography, halftone-dust treated (§9.7), scrim gradient
   for legibility, Display-1 headline with one skewed emphasis word, dates as
   a licence-plate chip (§9.4), dual CTA row.
   --------------------------------------------------------------------------- */

.rcn-hero {
  position: relative;
  min-height: clamp(34rem, 92vh, 56.25rem);
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  color: var(--ink-1);
}

.rcn-hero__media {
  position: absolute;
  inset: 0;
}
.rcn-hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* base (no-parallax) state; the scroll-timeline rule below progressively
     enhances this, and --parallax-y is the documented hook a JS fallback can
     drive on browsers without animation-timeline: scroll() support. */
  transform: translateY(var(--parallax-y, 0px));
}
@supports (animation-timeline: scroll()) {
  .rcn-hero__media img {
    animation: rcn-parallax-shift linear both;
    animation-timeline: scroll(root);
    animation-range: 0 100vh;
    --parallax-shift: calc(var(--parallax-max) * 0.7); /* mid-ground layer - see §9.1/§9.8 for the other two */
  }
}

/* Scrim sits ABOVE the smoke layer so smoke can brighten the photo first and
   the scrim still guarantees text contrast on top of whatever that produces -
   never a filter:brightness() on the photo itself (§I). */
.rcn-hero__scrim {
  position: absolute;
  inset: 0;
  background: var(--scrim-gradient-up);
}

.rcn-hero__content {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--content-max);
  margin-inline: auto;
  padding: var(--gutter);
  padding-block-end: var(--space-16);
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.rcn-hero__eyebrow { color: var(--color-ochre-300); }

.rcn-hero__title {
  /* inherits h1 rules via the shared .rcn-display-1 class in markup, or use h1 directly */
  opacity: 0;
  transform: translateY(40px);
  animation: rcn-hero-launch var(--duration-cinematic) var(--ease-launch) both;
}
/* If the template wraps each headline line in .rcn-hero__line for the 80ms-
   per-line stagger described in §G, these delays apply on top of the base
   animation above (later start, same easing/duration). Un-wrapped headlines
   simply animate as one block via .rcn-hero__title itself. */
.rcn-hero__line { display: block; }
.rcn-hero__line:nth-child(1) { animation-delay: 0ms; }
.rcn-hero__line:nth-child(2) { animation-delay: 80ms; }
.rcn-hero__line:nth-child(3) { animation-delay: 160ms; }
.rcn-hero__line:nth-child(4) { animation-delay: 240ms; }

.rcn-hero__cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  opacity: 0;
  transform: translateY(40px);
  animation: rcn-hero-launch var(--duration-cinematic) var(--ease-launch) both;
  animation-delay: calc(var(--duration-cinematic) + var(--duration-fast)); /* headline duration + 200ms, per §G */
}

@keyframes rcn-hero-launch {
  from { opacity: 0; transform: translateY(40px); }
  to   { opacity: 1; transform: translateY(0); }
}

.rcn-hero__dates {
  align-self: flex-start;
}

/* Interior-page hero (views/blocks/pageHero.ejs) - added for the build: the
   brief calls for something "shorter than the homepage hero" for the 35
   interior pages, which the base .rcn-hero (92vh) doesn't give a way to
   express. Same component, shorter clamp. */
.rcn-hero--page {
  min-height: clamp(16rem, 38vw, 26rem);
}

/* No-photo state for pageHero - roughly half of the 35 interior pages have a
   null image in the harvested content. Rather than an empty band, this falls
   back to a flat raised surface with the Contour Horizon device (§9.8) as
   quiet texture, per the brief ("so imageless pages still feel designed").
   No scrim/smoke here - there's no photograph for a scrim to sit above, and a
   flat surface-1 already has full ink-1 contrast on its own. */
.rcn-hero--flat {
  background: var(--surface-1);
  align-items: center;
}

/* Slim banner - /search only (Rick, 6 Aug 2026: "a very thin top banner
   without all of that ridiculous vertical whitespace").

   An opt-in modifier rather than a change to .rcn-hero--page, which the 35
   content pages and the enquiry-result page also use: on a content page the
   tall band is the design, and on search it is dead space between the header
   and the one control the page exists for. Nothing picks this class up unless
   a template asks for it by name. */
.rcn-hero--slim {
  min-height: 0;
}
.rcn-hero--slim .rcn-hero__content {
  /* Overrides the base block padding only - the inline gutter still comes from
     .rcn-hero__content, so the title stays on the container's left edge. */
  padding-block: var(--space-6);
  gap: var(--space-2);
}
.rcn-hero--slim .rcn-hero__title {
  font-size: var(--text-display-3);
}
/* The section below loses its own top padding too. Two full-height blocks
   stacked is what produced the gap in the first place, and halving only one
   of them leaves most of it. */
.rcn-hero--slim + .rcn-section {
  padding-block-start: var(--space-8);
}


/* ---------------------------------------------------------------------------
   8.3 STAT TICKER / MARQUEE
   Dark surface-1 band, 40-48px tall, looping marquee, -webkit-text-stroke
   texture on repeats (kept from the current site per §H - one of the few
   existing choices worth reusing as-is).
   --------------------------------------------------------------------------- */

.rcn-ticker {
  background: var(--surface-1);
  overflow: hidden;
  white-space: nowrap;
  padding-block: var(--space-3);
  border-block: 1px solid var(--border-subtle);
}

/* Markup contract (corrected 2026-07-27 to match what actually ships): the
   template authors the content ONCE; motion.js duplicates it until one set
   covers the viewport, appends the second set for the seamless -50% loop,
   adds `.is-marquee-active` and sets `--marquee-duration` from measured
   width. The animation is therefore gated on that class: before this fix the
   track animated unconditionally, so a no-JS visit watched a single
   unduplicated item oscillate by half its own width - and the JS-measured
   `--marquee-duration` was set but never read (the fixed 32s token was used
   regardless of content length). Static, readable text is the correct no-JS
   and pre-JS state; only a built track may move. */
.rcn-ticker__track {
  display: inline-flex;
  width: max-content;
  gap: var(--space-10);
}
.rcn-ticker.is-marquee-active .rcn-ticker__track {
  animation: rcn-ticker-scroll var(--marquee-duration, var(--duration-marquee)) linear infinite;
}
.rcn-ticker:hover .rcn-ticker__track,
.rcn-ticker:focus-within .rcn-ticker__track {
  animation-play-state: paused; /* lets anyone actually read a given stat */
}

.rcn-ticker__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--ink-2);
  -webkit-text-stroke: 1px var(--border-strong);
}
.rcn-ticker__item::after {
  content: '\2022'; /* bullet separator */
  margin-inline-start: var(--space-3);
  color: var(--color-red-500);
  -webkit-text-stroke: 0;
}

/* The loop distance is a MEASURED pixel value set by motion.js
   (--marquee-shift = one exact content period, gaps included), not an assumed
   -50%. Half the track was wrong on two counts - the track holds an odd
   number of repeated units, and N flex items only carry N-1 gaps - so every
   wrap landed out of register and the row visibly snapped once per cycle.
   The percentage fallback only exists for safety; is-marquee-active is added
   by the same JS that sets the variable, so it should never be consumed. */
@keyframes rcn-ticker-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-1 * var(--marquee-shift, 50%))); }
}


/* ---------------------------------------------------------------------------
   8.4 PROGRAMME - "THE FOUR DAYS" PINNED SEQUENCE
   Section pins via position:sticky while the user scrolls through 4-6
   sub-steps; each [data-step] panel is toggled .is-active by motion.js
   (IntersectionObserver on invisible markers inside the track - see §6).
   Also used standalone (not pinned) as plain programme/day cards elsewhere.
   --------------------------------------------------------------------------- */

.rcn-days__track {
  position: sticky;
  top: var(--header-h);
  height: 100vh;
  display: flex;
  align-items: center;
  overflow: hidden;
}

.rcn-days__panel {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
}
/* [data-step] in §6 already supplies the opacity/transform/.is-active pair;
   this just constrains it to full-bleed stacked panels within the track. */

.rcn-days__progress {
  position: absolute;
  inset-inline-end: var(--gutter);
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  z-index: 1;
}
.rcn-days__dot {
  width: var(--space-3);
  height: var(--space-3);
  border-radius: 50%;
  background: var(--surface-3);
  border: 1px solid var(--border-strong);
  transition: background-color var(--duration-base) var(--ease-throttle);
}
.rcn-days__dot.is-active { background: var(--color-red-500); }

/* Reduced motion: no pinning, no stepped reveal - render every day as a
   plain stacked list of cards instead (§G/§I: the fallback must show all six
   steps, not fewer). This is a layout change, legitimately CSS's job. */
@media (prefers-reduced-motion: reduce) {
  .rcn-days__track { position: static; height: auto; display: block; }
  .rcn-days__panel { position: static; opacity: 1; transform: none; margin-block-end: var(--space-8); }
  .rcn-days__progress { display: none; }
}

.rcn-day-card {
  display: flex;
  flex-direction: column;
  background: var(--surface-card);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-1);
  transition: transform var(--duration-fast) var(--ease-throttle),
              box-shadow var(--duration-fast) var(--ease-throttle);
}
.rcn-day-card:hover,
.rcn-day-card:focus-within {
  transform: translateY(calc(var(--space-1) * -1));
  box-shadow: var(--shadow-3);
}
.rcn-day-card:active { transition-timing-function: var(--ease-brake); }

.rcn-day-card__media {
  position: relative;
  aspect-ratio: 4 / 3;
}
.rcn-day-card__media img { width: 100%; height: 100%; object-fit: cover; }

.rcn-day-card__body {
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.rcn-day-card__title { font-size: var(--text-display-3); }
.rcn-day-card__desc { color: var(--ink-2); max-width: none; }

.rcn-day-card__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--color-red-300);
  min-height: var(--target-min);
}
.rcn-day-card__link:hover,
.rcn-day-card__link:focus-visible { color: var(--color-red-500); }


/* ---------------------------------------------------------------------------
   8.4b FULL PROGRAMME TIMETABLE (views/blocks/program.ejs)

   Added for the build: the pinned "Four Days" sequence above is the homepage
   teaser card grid; this styles the actual recovered day-by-day schedule
   (5 days x multiple precincts x sessions), which had no styling of its own.
   Sticky day-jump nav so the page is navigable without endless scrolling on a
   phone in a paddock; a Staging Lights marker (§9.3) per day showing how far
   into the event it sits; sessions as a description list with tabular-nums
   times so the time column aligns, using `display:contents` on the row
   wrapper so <dt>/<dd> pairs can sit in a two-column time/detail grid while
   staying valid, groupable <dl> markup.
   --------------------------------------------------------------------------- */

.rcn-program__nav {
  position: sticky;
  top: var(--header-h);
  z-index: var(--z-sticky);
  background: var(--surface-1);
  border-block: 1px solid var(--border-subtle);
  overflow-x: auto;
}
.rcn-program__nav-list {
  display: flex;
  gap: var(--space-2);
  padding-inline: var(--gutter);
  padding-block: var(--space-2);
  width: max-content;
}
.rcn-program__nav-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--target-min);
  padding-inline: var(--space-4);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--ink-2);
  white-space: nowrap;
  transition: color var(--duration-fast) var(--ease-throttle);
}
.rcn-program__nav-link:hover,
.rcn-program__nav-link:focus-visible { color: var(--ink-1); }

.rcn-program__day {
  padding-block: var(--section-y-tight);
  border-block-end: 1px solid var(--border-subtle);
}
.rcn-program__day:last-of-type { border-block-end: none; }

.rcn-program__day-header {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-block-end: var(--space-8);
}

.rcn-program__precinct { margin-block-end: var(--space-10); }
.rcn-program__precinct:last-child { margin-block-end: 0; }
.rcn-program__precinct h3 { margin-block-end: var(--space-4); }

.rcn-program__subsession {
  color: var(--color-ochre-300);
  margin-block: var(--space-6) var(--space-2);
}

.rcn-program__sessions {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3) var(--space-6);
  margin-block-end: var(--space-6);
}
/* Lets the <dt>/<dd> children of this wrapper participate directly in the
   parent's two-column grid, so the markup can stay a real, groupable <dl>
   (SS H: "sessions as a description list ... with <time> elements") instead
   of divs pretending to be one. */
.rcn-program__session { display: contents; }

.rcn-program__time {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  font-weight: 700;
  color: var(--ink-1);
  white-space: nowrap;
}
.rcn-program__detail { color: var(--ink-2); }

.rcn-program__categories {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-2);
}
.rcn-program__categories li {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  font-size: var(--text-small);
  color: var(--ink-3);
}

.rcn-program__footnote {
  color: var(--ink-3);
  margin-block-start: var(--space-10);
}


/* ---------------------------------------------------------------------------
   8.5 PRIZE POOL TABLE
   Odometer header (rolling counter - the ~20-line JS is motion.js's job per
   §G; this file only styles the resolved digits), category rows below,
   tacho-arc (§9.2) as a section-level visual anchor rather than per-row.
   --------------------------------------------------------------------------- */

.rcn-prize__header {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  margin-block-end: var(--space-12);
}

.rcn-prize__table-wrap { overflow-x: auto; } /* horizontal scroll rather than squeezed columns on small screens */

.rcn-prize__table { width: 100%; min-width: 28rem; }
.rcn-prize__table th,
.rcn-prize__table td {
  padding: var(--space-4) var(--space-3);
  text-align: left;
  border-block-end: 1px solid var(--border-subtle);
}
.rcn-prize__table th {
  font-family: var(--font-body);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  font-size: var(--text-small);
  color: var(--ink-3);
}
.rcn-prize__table td:last-child,
.rcn-prize__table th:last-child {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  font-weight: 700;
  color: var(--ink-1);
}

/* Added for the build: prizeTable is explicitly a GENERIC table (headers/rows
   from content), so which column holds a currency figure varies page to page
   - it is the middle column on two of the three real tables this renders
   today, not the last one, so the :last-child rule above can't be relied on
   for alignment. This class is applied directly to any cell containing a
   currency figure, regardless of position. */
.rcn-prize__amount {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}


/* ---------------------------------------------------------------------------
   8.6 TWO-PATH ENTRANT / SPECTATOR SPLIT
   50/50 desktop, stacks on mobile. Distinct photography and copy per path.
   --------------------------------------------------------------------------- */

.rcn-split {
  display: grid;
  grid-template-columns: 1fr;
}
@media (min-width: 640px) {
  .rcn-split { grid-template-columns: 1fr 1fr; }
}

.rcn-split__path {
  position: relative;
  min-height: clamp(20rem, 40vw, 30rem);
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  color: var(--ink-1);
}
.rcn-split__path img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.rcn-split__scrim {
  position: absolute;
  inset: 0;
  background: var(--scrim-gradient-up);
}
.rcn-split__content {
  position: relative;
  z-index: 1;
  padding: var(--space-8) var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.rcn-split__path--entrant .rcn-split__content { border-inline-start: 4px solid var(--color-red-500); }
.rcn-split__path--spectator .rcn-split__content { border-inline-start: 4px solid var(--color-ochre-500); }
/* §C do-not-list: red-500 and ochre-500 never sit as adjacent equal-weight
   flat fills (1.23:1 contrast, they vibrate). Here they're each a thin
   4px accent line over photography, not two adjacent flat blocks, so the
   rule doesn't apply - flagged in case a future edit turns these into
   solid card backgrounds instead, which WOULD trigger it. */


/* ---------------------------------------------------------------------------
   8.7 TICKET TIER CARDS
   surface-paper - the one deliberate light-surface component (§H). Tickets
   read as printed/physical. Scrutineering-sticker badge (§9.5) for
   Early Bird/Selling Fast/Sold Out. Price in tabular-nums.
   --------------------------------------------------------------------------- */

.rcn-ticket {
  position: relative;
  /* Fix for client-reported "collapsed, ragged row": the three tier cards sat
     in one .rcn-grid row with heights 793/484/392px - the grid's own default
     align-items:stretch already stretches this article's GRID CELL to the
     row's tallest card, but the card surface itself (background/border/
     shadow) only wrapped its own content, so the paper card visibly stopped
     short of the stretched cell and the row read as collapsed. height:100%
     lets the card's own painted surface fill that already-stretched cell. */
  height: 100%;
  /* 29 Jul: dark card, same surface language as every other card on the site
     (.rcn-card, .rcn-highlights__card). The cream --surface-paper tier cards
     were the one light block on an otherwise dark site and Rick killed them:
     "the light background does not feel like it belongs to the rest of the
     website at all". NO border - the OZTIX banner bleeds to the card edge
     with no frame around it (his second instruction). */
  background: var(--surface-card);
  color: var(--ink-1);
  border-radius: var(--radius-md);
  padding: var(--space-8) var(--space-6);
  box-shadow: var(--shadow-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  transition: transform var(--duration-fast) var(--ease-throttle),
              box-shadow var(--duration-fast) var(--ease-throttle);
}
/* CTA pins to the bottom of every card regardless of how much intro/benefits/
   outro copy precedes it, so the three buttons land on one shared baseline
   across the row - the other half of the equal-height fix above. Cards with
   no live CTA yet (the free children's tier) simply have no last child to
   pin, which is fine: its cap device (below) already gives it a designed,
   non-empty top instead. */
.rcn-ticket > .rcn-btn {
  margin-block-start: auto;
}
.rcn-ticket:hover,
.rcn-ticket:focus-within {
  transform: translateY(calc(var(--space-1) * -1));
  box-shadow: var(--shadow-3);
}
.rcn-ticket:active { transition-timing-function: var(--ease-brake); }

/* §C do-not-list: a card carries at most ONE accent colour at high
   saturation. Featured tier gets the brand red; do not also add an ochre
   accent to the same card. */
.rcn-ticket--featured { outline: 2px solid var(--color-red-500); outline-offset: -2px; }

.rcn-ticket__badge {
  position: absolute;
  top: calc(var(--space-6) * -1);
  right: var(--space-6);
}

/* Added for the build: the tier's OZTIX banner image (promotional graphic
   with its own baked-in text, not halftone-eligible photography) reads better
   bleeding to the card's edge than sitting inset inside .rcn-ticket's own
   padding - negative margins pull it flush, rounded to match the card's own
   top corners. Locked to the real banners' own 1000x300 ratio (object-fit:
   cover) rather than left to grow with whatever crop a future banner ships
   at - the two real banners already share this ratio exactly, so nothing is
   cropped today, and any future one that doesn't match gets a clean centred
   crop instead of setting its own card height. */
.rcn-ticket__image {
  margin: calc(var(--space-8) * -1) calc(var(--space-6) * -1) 0;
  aspect-ratio: var(--ratio-ticket-banner);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  overflow: hidden;
}
.rcn-ticket__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* No-image state (the free children's tier has image: null in real content -
   the exact "odd box that reads unstyled" the client called out). Reuses the
   SAME no-photo device already established for .rcn-hero--flat (§8.2) rather
   than inventing a one-off treatment: Contour Horizon at the ticket banner's
   own aspect ratio and bleed, so this card's name starts at the identical Y
   position as its two siblings instead of sitting flush against the top
   padding like a bare text box. Purely decorative (aria-hidden) - no content
   invented, see hard rule §2. */
.rcn-ticket__cap {
  position: relative;
  margin: calc(var(--space-8) * -1) calc(var(--space-6) * -1) 0;
  aspect-ratio: var(--ratio-ticket-banner);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  overflow: hidden;
  /* One step below the card surface, now that the card itself is surface-1 -
     the cap must read as a recess, not vanish into the card. */
  background: var(--surface-0);
}
.rcn-ticket__cap .rcn-contour {
  color: var(--color-ochre-500);
  opacity: 0.4;
}

.rcn-ticket__name {
  font-size: var(--text-h4);
  color: var(--ink-1);
}

.rcn-ticket__price {
  font-family: var(--font-body);
  font-weight: 800;
  font-size: var(--text-odometer);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  line-height: 1;
  color: var(--color-red-300); /* the dark-surface-legal red at text size (§C) */
}

.rcn-ticket__list { display: flex; flex-direction: column; }
.rcn-ticket__list li {
  padding-block: var(--space-2);
  border-block-end: 1px solid var(--border-subtle);
  color: var(--ink-2);
}
.rcn-ticket__list li:last-child { border-block-end: none; }


/* ---------------------------------------------------------------------------
   8.8 FAQ ACCORDION
   Real <button> + aria-expanded semantics (§H/§I). Single-open recommended
   to reduce mobile scroll fatigue, enforced by the template/JS, not CSS.
   Icon rotates 45deg (+ becomes x). :has() drives the open state directly
   off aria-expanded, so no separate JS-toggled class is required for the
   panel to open - only a small script needs to flip aria-expanded itself.
   A class-based fallback is included for engines without :has() support.
   --------------------------------------------------------------------------- */

/* 29 Jul, Rick - "park every FAQ in the unactivated version of that card
   styling", then "so we decided not to put an at-rest background on FAQ
   cards?". So each question IS a card at rest: the card surface, the corner
   bruise and the ignition keyline, exactly as a gate card looks before you
   touch it. What it deliberately does NOT get is the ARMED state - no hover
   lift, no red glow, no colour flip - because a question is a disclosure
   control, not a link to somewhere else. */
.rcn-faq__item {
  position: relative;
  counter-increment: rcn-gate;
  background:
    radial-gradient(120% 90% at 88% -12%, var(--fx-glow-red-soft), transparent 62%),
    var(--surface-card);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.rcn-faq__item::before {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: 0;
  /* Half the card keyline's 3px (Rick, 29 Jul): a FAQ list stacks 25 of these
     one under another, so at card weight the page reads as a wall of red
     rules. The card grids keep the heavier line - there are only ever a
     handful of cards in view at once. */
  height: 1.5px;
  background: linear-gradient(90deg, var(--color-red-500), var(--color-ochre-500) 72%, transparent);
  opacity: 0.5;
}
/* Every FAQ list gets its own numbering, restarting per group/page section. */
.rcn-faq__list { counter-reset: rcn-gate; }

.rcn-faq__trigger {
  width: 100%;
  min-height: var(--target-min);
  display: flex;
  align-items: center;
  /* flex-start, not space-between: the new numbering chip below makes this a
     THREE-item row (chip, question, icon), and space-between would spread
     equal space into both gaps rather than sitting the chip snug against the
     question. The icon still lands flush right via its own auto margin. */
  justify-content: flex-start;
  gap: var(--space-4);
  /* Card padding now that the item is a surface, not a bare ruled row. */
  padding: var(--space-4) var(--space-5);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-body-lg);
  color: var(--ink-1);
  text-align: left;
}
/* 29 Jul, Rick - "park every FAQ in the unactivated version of that card
   styling" (SS11.23's gate chip), i.e. the chip alone, permanently at rest:
   an accordion question is never a link-card, so it gets no keyline, no
   corner glow and no hover-armed red state - just the same skewed,
   ochre-bordered plate numbering every question consistently. */
.rcn-faq__trigger::before {
  /* decimal-leading-zero, not a hardcoded '0' prefix: the manual prefix
     rendered question 10 as "010" (spotted on /faq#entrants). This pads
     1-9 and leaves 10+ alone. */
  content: counter(rcn-gate, decimal-leading-zero);
  flex: none;
  font-family: var(--font-body);
  font-weight: 800;
  font-size: var(--text-small);
  letter-spacing: var(--tracking-plate);
  color: var(--color-ochre-300);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-3);
  transform: skewX(var(--skew-emphasis));
}

.rcn-faq__icon {
  position: relative;
  flex: none;
  width: 20px;
  height: 20px;
  margin-inline-start: auto;
  transition: transform var(--duration-fast) var(--ease-throttle);
}
.rcn-faq__icon::before,
.rcn-faq__icon::after {
  content: '';
  position: absolute;
  background: var(--color-red-500);
}
.rcn-faq__icon::before { top: 50%; left: 0; width: 100%; height: 2px; transform: translateY(-50%); }
.rcn-faq__icon::after { left: 50%; top: 0; width: 2px; height: 100%; transform: translateX(-50%); }

.rcn-faq__panel {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows var(--duration-fast) var(--ease-throttle);
}
.rcn-faq__panel > div { min-height: 0; overflow: hidden; }
.rcn-faq__panel-inner {
  /* padding-BLOCK (vertical) deliberately does NOT live here by default - see
     the bugfix note below. padding-INLINE is fine to keep: horizontal inset
     doesn't affect the vertical 0fr collapse this element sits inside. */
  padding-inline: var(--space-5);
  padding-block: 0;
  color: var(--ink-2);
}

/* Primary mechanism: :has() reads aria-expanded directly off the trigger. */
.rcn-faq__item:has(> .rcn-faq__trigger[aria-expanded="true"]) .rcn-faq__panel {
  grid-template-rows: 1fr;
}
.rcn-faq__item:has(> .rcn-faq__trigger[aria-expanded="true"]) .rcn-faq__icon {
  transform: rotate(45deg);
}
/* Fallback for engines without :has() - identical result if the template
   also toggles .is-open on the item (belt-and-suspenders, cheap to include). */
.rcn-faq__item.is-open .rcn-faq__panel { grid-template-rows: 1fr; }
.rcn-faq__item.is-open .rcn-faq__icon { transform: rotate(45deg); }

/* BUGFIX (found during the rhythm sweep, pre-dates this pass): every CLOSED
   accordion panel was showing a 24px sliver of its own answer text
   overlapping the next question, in every browser, with or without reduced
   motion. Root cause: `.rcn-faq__panel-inner` was both (a) the grid item
   `.rcn-faq__panel`'s 0fr/1fr row animates, AND (b) the box carrying
   `padding-block`. A border-box element's rendered height can never go
   below its own padding+border sum, no matter what `min-height`, `height` or
   `overflow` say (CSS 2.1 SS10.6.3) - so that padding permanently floored the
   "closed" row at 24px instead of 0. Fix: padding-block is 0 by default
   (above) and only restored here, in the two OPEN states, where the floor no
   longer matters because the row is meant to be full height anyway. Not
   transitioned on purpose - it snaps together with the (already smooth)
   grid-template-rows animation, which is the standard pattern for this
   exact CSS trick. */
.rcn-faq__item:has(> .rcn-faq__trigger[aria-expanded="true"]) .rcn-faq__panel-inner,
.rcn-faq__item.is-open .rcn-faq__panel-inner {
  padding-block: var(--space-2) var(--space-4);
}


/* ---------------------------------------------------------------------------
   8.9 SPONSOR / PARTNER WALL
   Neutral surface-1 band. Logos supplied as-is, consistent max-height, never
   re-coloured or cropped, generous gaps.
   --------------------------------------------------------------------------- */

.rcn-sponsors {
  background: var(--surface-1);
}
.rcn-sponsors__grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
}
/* Never re-coloured or cropped: no filter, no object-fit crop, no invert -
   this rule exists specifically to stop a future "make it match the dark
   theme" edit from tinting a sponsor's own brand mark (§E item 12). */
.rcn-sponsors__grid img {
  /* `height` (not `max-height`): every logo here also carries a `sizes`
     attribute for responsive srcset selection, and the browser uses that
     value as the laid-out CSS width whenever width/height both resolve to
     auto - which silently skipped max-height for any logo wide enough that
     sizes-width * (1 / its own aspect ratio) already came in under 48px
     (real content: Aeroflow, a genuine 5:1 banner, rendered at 40/36px tall
     instead of 48 - visibly smaller than every 1.07-3.75:1 logo beside it).
     An explicit height forces the auto-width-from-aspect-ratio path instead,
     so every logo (1.07:1 to 5:1) lands on the same row height. Still never
     stretches or crops: width stays auto, object-fit stays contain. */
  height: var(--space-12);
  width: auto;
  max-width: 100%;
  filter: none;
  object-fit: contain;
}


/* ---------------------------------------------------------------------------
   8.10 COUNTDOWN - THE ODOMETER CLOCK (29 Jul rebuild)
   Rick: "8 large numbers on a car speedo odometer representing the countdown
   days/hours/minutes/seconds ... as if it were 3D", with reference photos of
   real geared odometer drums rolling behind a dash cutout.

   Construction, outside in:
     .rcn-odo          the instrument bezel - brushed dark metal slab
     .rcn-odo__window  the cutout: a hole DARKER than any page surface, with
                       inset shadows + --odo-curve overlay shading the digit
                       drums into cylinders (the whole 3D read lives here)
     .rcn-odo__group   one unit (days/hours/mins/secs), two barrels + label
     .rcn-odo__barrel  one digit wheel: overflow-hidden viewport, exactly one
                       digit tall
     .rcn-odo__strip   the drum: its digit RUN printed twice plus a trailing
                       repeat of the first digit, slid by
                       translateY(calc(var(--odo-pos) * -1 * digit height)).
                       Runs are per-wheel, geared like the real instrument
                       (29 Jul spec): units wheels carry 0-9, the seconds and
                       minutes TENS wheels carry only 0-5 (they can never
                       show 6-9, even mid-spin), the hours tens wheel only
                       0-2. Two full runs so motion.js can always roll
                       DOWNWARD (a countdown decrements; the incoming digit
                       enters from above) and then renormalise +run
                       invisibly - cell N and cell N+run paint identical
                       pixels. The trailing cell exists for the glide (below).

   The strip transform is the ONLY thing that moves - compositor-friendly,
   no layout. Server renders the real current digits into --odo-pos, so the
   clock is correct with JS disabled; motion.js only makes it tick. */

.rcn-countdown {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-6);
}

.rcn-odo {
  background: var(--odo-bezel);
  border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-5);
  box-shadow: var(--shadow-3), var(--odo-bezel-edge);
  /* The mild dashboard rake: enough perspective to catch the bezel gradient
     as a surface, not enough to distort the digits. Static - not motion. */
  transform: perspective(60rem) rotateX(4deg);
}

.rcn-odo__window {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--odo-group-gap);
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--odo-wheel-shade);
  box-shadow: var(--odo-cutout-shadow);
}
/* Curvature + glass, one overlay across all eight drums, plus a hard side
   vignette where the drum stack disappears into the housing. z-index 2 keeps
   the light ABOVE the 3D drums. */
.rcn-odo__window::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  background:
    linear-gradient(90deg, rgba(0, 0, 0, 0.6), transparent 5%, transparent 95%, rgba(0, 0, 0, 0.6)),
    var(--odo-curve);
  pointer-events: none;
}

.rcn-odo__group {
  display: flex;
  gap: var(--odo-gap);
}
/* Thin machined divider between unit groups. */
.rcn-odo__group + .rcn-odo__group {
  position: relative;
}
.rcn-odo__group + .rcn-odo__group::before {
  content: '';
  position: absolute;
  inset-block: 8%;
  inset-inline-start: calc(var(--odo-group-gap) / -2 - 1px);
  width: 3px;
  /* A machined spacer post catching the same top light as the drums, not a
     flat printed line - faded and rubbed like the rest of the old cluster:
     lower ink, and the wear mask breaks the line into worn patches. */
  background: linear-gradient(180deg, transparent, rgba(247, 243, 239, 0.48) 30%, rgba(247, 243, 239, 0.48) 70%, transparent);
  box-shadow: 2px 0 2px rgba(0, 0, 0, 0.8);
  -webkit-mask-image: var(--wear-mask);
  mask-image: var(--wear-mask);
  -webkit-mask-size: 140px 140px;
  mask-size: 140px 140px;
}

/* THE DRUM (29 Jul v2). Each barrel is a one-digit viewport with its own
   perspective; inside it the strip is a genuine 3D cylinder - `faces` digit
   plates arranged around the X axis, the whole drum turned by --odo-rot.
   Digits morph as they roll over the top and bottom of the window because
   they really are curling around a cylinder under perspective - no per-frame
   faking. The drum is pushed back by its own radius (translateZ(-r)) so the
   FRONT face sits exactly on the window plane: projected at scale 1 (sharp,
   uncropped) with the rest of the drum receding behind the bezel cutout.

   --odo-jitter is the mechanical mis-park: motion.js re-randomises it within
   +/- --odo-jitter-max at every roll, because a machined drum never indexes
   to perfect centre twice (Rick's direction). It rides in screen space,
   before the 3D pipeline. */
.rcn-odo__barrel {
  position: relative;
  display: block;
  width: var(--odo-digit-w);
  height: var(--odo-digit-h);
  overflow: hidden;
  background: var(--odo-wheel);
  perspective: var(--odo-persp);
}
/* Each drum's own side falloff - above the 3D content (z-index), below the
   window's shared light. Sells eight individual cylinders. */
.rcn-odo__barrel::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--odo-wheel-side);
  pointer-events: none;
}
/* Drum geometry per face count: radius = (h/2) / tan(PI/faces). Pure trig
   constants, not design values - they belong to the face count. */
.rcn-odo__barrel--f10 { --odo-r: calc(var(--odo-digit-h) * 1.539); --odo-step: 36deg; --odo-roll: var(--odo-roll-f10); }
.rcn-odo__barrel--f6  { --odo-r: calc(var(--odo-digit-h) * 0.866); --odo-step: 60deg; --odo-roll: var(--odo-roll-f6); }

.rcn-odo__strip {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transform: translateY(var(--odo-jitter, 0px))
             translateZ(calc(var(--odo-r) * -1))
             rotateX(var(--odo-rot, 0deg));
  /* Linear at the train's shared 6deg/s gear rate (tokens): a driven gear
     turns at its driver's constant speed - the indexed stop IS the ending. */
  transition: transform var(--odo-roll, var(--odo-roll-f10)) linear;
  will-change: transform;
}
/* Renormalisation (motion.js): rewind one full revolution with zero movement. */
.rcn-odo__strip.is-odo-snapping { transition: none; }

/* THE GLIDE - both red seconds wheels (Rick: "the wheels on an odometer are
   always rolling ... permanent smooth geared roll on both of the red seconds
   digits at all time"). Compositor-driven linear spins that never pause: the
   units drum turns one revolution per 10s (a digit centred exactly on each
   second boundary), and the tens drum is geared to it at exactly 1/6 speed
   (one revolution per 60s - dead-centre only as the units drum passes zero,
   like a real continuously-geared drum pair). motion.js supplies each drum's
   period via inline animation-duration and phase-locks both to the wall
   clock with a negative animation-delay re-checked every tick, so they can
   drift neither from the clock nor from each other. Rotation runs NEGATIVE:
   digits descend, the incoming number rolls in over the top - the countdown
   direction. */
@keyframes rcn-odo-glide {
  from {
    transform: translateY(var(--odo-jitter, 0px))
               translateZ(calc(var(--odo-r) * -1)) rotateX(0deg);
  }
  to {
    transform: translateY(var(--odo-jitter, 0px))
               translateZ(calc(var(--odo-r) * -1)) rotateX(-360deg);
  }
}
.rcn-odo__strip.is-odo-gliding {
  animation: rcn-odo-glide 10s linear infinite;
}

/* One printed plate of the drum. --fi is its slot index; the digits are
   printed DESCENDING around the cylinder (see countdown.ejs) because a
   countdown drum only ever turns one way. */
.rcn-odo__digit {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  /* Centre the glyph in the area RIGHT of the gear-teeth strip. */
  padding-inline-start: var(--odo-teeth-w);
  transform: rotateX(calc(var(--fi) * var(--odo-step))) translateZ(var(--odo-r));
  backface-visibility: hidden;
  /* Pre-raster every plate. Under a compositor-driven glide a GPU can
     otherwise rotate a face into view before its tiles are drawn - the
     glyph's lower half "paints in" a beat late (reported on real hardware;
     not reproducible under software raster, where every frame verified
     pixel-perfect). */
  will-change: transform;
  font-family: var(--font-body);
  font-weight: 800;
  font-size: var(--odo-digit-size);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  color: var(--ink-1);
  /* Printed-on-metal: a hard drop into the drum surface plus a soft spill,
     so the glyphs sit IN the light rig rather than floating over it. */
  text-shadow: 0 2px 1px rgba(0, 0, 0, 0.9), 0 4px 6px rgba(0, 0, 0, 0.6);
  /* Decades of wear: fractal-noise alpha mask rubs the printed ink (and the
     gear teeth) thin in organic patches. Each face samples the noise at an
     offset derived from its own index, so no two digits wear alike. */
  -webkit-mask-image: var(--wear-mask);
  mask-image: var(--wear-mask);
  -webkit-mask-size: 260px 260px;
  mask-size: 260px 260px;
  -webkit-mask-position: calc(var(--fi) * 43px) calc(var(--fi) * 71px);
  mask-position: calc(var(--fi) * 43px) calc(var(--fi) * 71px);
}
/* The gear-teeth strip down each plate's left edge. Part of the face, so it
   turns with the drum by construction - the teeth are the drum's edge gear
   showing through the cutout. Tooth pattern tiles the face height exactly
   (--odo-tooth-pitch teeth per face), so rows run continuously across face
   boundaries mid-roll. */
.rcn-odo__digit::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: var(--odo-teeth-w);
  background-image: var(--odo-teeth);
  background-size: 100% calc(var(--odo-digit-h) / var(--odo-tooth-pitch));
  background-repeat: repeat-y;
}
/* The live wheel: real odometers paint the fastest drum differently (the
   red/white tenths wheel). Seconds get the hot ink - and a TIGHTER mis-park
   range than the white wheels' 8px: these two never park, so a large fixed
   offset would read as a permanently crooked drum, not mechanical charm. */
.rcn-odo__group--seconds .rcn-odo__digit { color: var(--odo-ink-seconds); }
.rcn-odo__group--seconds .rcn-odo__strip { --odo-jitter-max: 3px; }

/* Unit labels ride in a row UNDER the bezel, one per group, aligned to the
   group centres by mirroring the window's flex geometry. */
.rcn-odo__labels {
  display: flex;
  gap: var(--odo-group-gap);
  padding-inline: var(--space-2);
  margin-block-start: var(--space-2);
}
.rcn-odo__label {
  width: calc(var(--odo-digit-w) * 2 + var(--odo-gap));
  text-align: center;
  font-size: var(--text-small);
  /* Max weight: wide strokes give the deboss shadows room to read as a
     pressed floor rather than a thin engraved line. */
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  /* STAMPED into the plastic fascia (Rick, 29 Jul - supersedes the ochre
     screen-print): the lettering is the bezel material itself, pressed in.
     Deboss = darker-than-surface ink, a shadow pooling along the recess's
     top wall, and the top-left key light catching the recess's bottom lip
     as a bright line under each stroke. The wear mask still rubs it. */
  color: rgba(0, 0, 0, 0.52);
  text-shadow:
    0 -1px 1px rgba(0, 0, 0, 0.75),
    0 1px 0 rgba(255, 250, 242, 0.38);
  -webkit-mask-image: var(--wear-mask);
  mask-image: var(--wear-mask);
  -webkit-mask-size: 200px 200px;
  mask-size: 200px 200px;
}
/* Each word samples a different patch of the noise, so the four labels don't
   share one wear fingerprint. */
.rcn-odo__label:nth-child(2) { -webkit-mask-position: 47px 83px; mask-position: 47px 83px; }
.rcn-odo__label:nth-child(3) { -webkit-mask-position: 101px 31px; mask-position: 101px 31px; }
.rcn-odo__label:nth-child(4) { -webkit-mask-position: 23px 149px; mask-position: 23px 149px; }

@media (prefers-reduced-motion: reduce) {
  /* Movement removed, information kept: digits change by instant swap, and
     the perpetual seconds glide never turns (motion.js steps that wheel like
     the others instead - the !important covers a mid-session OS toggle). */
  .rcn-odo__strip { transition: none; animation: none !important; }
  .rcn-odo { transform: none; }
}
/* Per §I: no aria-live region on a per-second counter (would be read aloud
   continuously). If the template wants an hourly milestone announcement,
   that single aria-live="polite" node lives in the markup - this file has
   no opinion on it beyond not visually distinguishing it. */


/* ---------------------------------------------------------------------------
   8.11 NEWSLETTER
   --------------------------------------------------------------------------- */

/* The tickets CTAs anchor here (#keep-up-to-date), so the sticky header must
   not sit on top of the section it just scrolled to - same reasoning, and the
   same value, as .rcn-faq__group-heading in 11.10. */
.rcn-newsletter-section { scroll-margin-top: calc(var(--header-h) + var(--space-6)); }

.rcn-newsletter {
  background: var(--surface-card);
  border-radius: var(--radius-md);
  padding: var(--space-10) var(--space-6);
}
.rcn-newsletter__form {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  align-items: flex-end;
}
.rcn-newsletter__form .rcn-field { flex: 1 1 16rem; }


/* ---------------------------------------------------------------------------
   8.12 FOOTER
   surface-1/surface-0, contour-horizon device (§9.8) as quiet background,
   acknowledgement recap link (not the full statement - §E item 3 lives in
   its own homepage section, §10 below), socials, legal, partner logos.
   --------------------------------------------------------------------------- */

.rcn-footer {
  position: relative;
  overflow: hidden;
  /* 29 Jul restyle (Rick: "do something interesting with footer and site main
     bg's ... unusual and arty"). Three painted layers over the footer surface:
     1. --seam-heat: the separating hairline the 28 Jul direction asked for,
        now a heated-metal edge - brand red cooling into ochre, dying out
        before it reaches either side - instead of a flat grey line.
     2. An ember glow (--bruise-red-ember) rising from BELOW the fold, centred,
        like brake-light wash from under the page: strongest at the footer's
        bottom edge, gone before it reaches the link columns.
     3. A faint ochre dawn bruise in the top-right corner, answering the page
        field's own bruises so the footer belongs to the same weather.
     All static paint. The old border-block-start is replaced by layer 1. */
  background:
    var(--seam-heat) top / 100% 1px no-repeat,
    radial-gradient(80rem 30rem at 50% 115%, var(--bruise-red-ember), transparent 70%) border-box,
    radial-gradient(56rem 26rem at 88% -8%, var(--bruise-ochre-dawn), transparent 70%) border-box,
    var(--surface-1);
  /* Asymmetric on purpose. `--section-y` (128px) both sides left a large empty
     band under the last line of the sub-footer with nothing beneath it - the
     "too much white space under the links" on the client's card. The TOP kept
     the full section rhythm so the footer still separates cleanly from the
     page above; only the trailing space was cut, since nothing follows it.

     29 Jul: halved again on both ends - "take 50% of the top vertical
     whitespace out of the footer and 50% of the bottom whitespace of the sub
     footer". calc() off the shared tokens, not new literals, so both ends
     still track --section-y/--space-10 if those are ever retuned. */
  padding-block: calc(var(--section-y) / 2) calc(var(--space-10) / 2);
}

.rcn-footer__grid {
  position: relative;
  z-index: 1;
  display: grid;
  gap: var(--space-8);
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
}

.rcn-footer__heading {
  font-size: var(--text-h4);
  margin-block-end: var(--space-3);
}

/* Acknowledgement of Country, now the statement itself rather than a link to
   a homepage section (see the comment in partials/footer.ejs for why it moved).

   Deliberately plain: no motion, no graphic device, no red, no uppercase
   display face - CLAUDE.md hard rule 7. It reads at full body size rather than
   the small print around it, and is capped to a comfortable measure so it is
   not a single 1200px line lost among the legal links. */
/* Now the grid's 4th column (28 Jul client direction), not a full-width band
   below it - the standalone block's own space-10 top margin was the "fat
   uneven whitespace" under the sub-footer text, since nothing else on the
   page carried that same gap before the legal strip. As a column it just
   takes the grid's own `gap`, same as its siblings. Still plain type, no
   background, no border, no motion - CLAUDE.md hard rule 7. */
.rcn-footer__ack {
  color: var(--ink-2);
  font-size: var(--text-body);
  line-height: var(--leading-body);
  margin: 0;
}

.rcn-footer__socials {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
}
/* Icon buttons (28 Jul client direction: "should be styled logo icons", not
   text labels). Fixed 44x44 circle - right for a single glyph, which is why
   the previous text-label version of this rule had to abandon the circle
   (the word "Instagram" doesn't fit one). The icon paths are drawn inline in
   footer.ejs since the build has no icon font/sprite to pull from. */
.rcn-footer__social-link {
  width: var(--target-min);
  height: var(--target-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  color: var(--ink-1);
  transition: border-color var(--duration-fast) var(--ease-throttle),
              color var(--duration-fast) var(--ease-throttle),
              background-color var(--duration-fast) var(--ease-throttle);
}
.rcn-footer__social-link svg {
  width: 18px;
  height: 18px;
}
.rcn-footer__social-link:hover,
.rcn-footer__social-link:focus-visible {
  border-color: var(--color-red-500);
  color: var(--surface-1);
  background-color: var(--color-red-500);
}

/* Sub-footer. Client card "Global Sub footer" 2026-07-27: "the links are not
   aligned" and "too much white space under the links".

   Both were real. It was one flex row of three items with no align-items, so
   with the acknowledgement now a full sentence rather than a short link, the
   legal links wrapped to a second line and sat 90px below the other two - the
   misalignment in the screenshot.

   A grid fixes it properly rather than nudging the flex: the acknowledgement
   takes its own full-width row (it is a sentence, not a nav item and should
   not compete with one), and the copyright and legal links share a second row
   that is baseline-aligned and cannot wrap into each other. */
/* The sub-footer proper: copyright, legal links, studio credit. Tightened
   (28 Jul client direction: "shrink all of the vertical whitespace out of
   that sub footer") now that it no longer also carries the acknowledgement -
   it is the page's least important line and had inherited the same
   --space-10/--space-6 rhythm as actual content. */
.rcn-footer__legal {
  position: relative;
  z-index: 1;
  font-size: var(--text-small);
  color: var(--ink-3);
  border-block-start: 1px solid var(--border-subtle);
  padding-block-start: var(--space-4);
  margin-block-start: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
@media (min-width: 720px) {
  .rcn-footer__legal {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-6);
  }
}
.rcn-footer__legal p { margin: 0; }

/* Legal links and the NitroSites credit share the right-hand end of the bar
   (client direction: credit "on the right of the sub footer"), copyright
   stays left. Wraps as one group on narrow screens rather than the credit
   jumping to its own row. */
.rcn-footer__legal-end {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-6);
}

.rcn-footer__credit {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  color: var(--ink-3);
}
.rcn-footer__credit a {
  display: inline-flex;
  align-items: center;
  line-height: 0;
  min-height: var(--target-min);
}
.rcn-footer__admin-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Full 44px tap target per SS I even though the icon itself stays small
     and quiet - a deliberately low-key entry point, not an invisible one. */
  min-width: var(--target-min);
  min-height: var(--target-min);
  margin-inline-start: calc(var(--space-2) * -1);
  color: var(--ink-3);
  border-radius: var(--radius-sm);
  transition: color var(--duration-fast) var(--ease-throttle);
}
.rcn-footer__admin-link:hover,
.rcn-footer__admin-link:focus-visible {
  color: var(--color-ochre-300);
}
.rcn-footer__credit img {
  height: 20px;
  width: auto;
  opacity: 0.75;
  transition: opacity var(--duration-fast) var(--ease-throttle);
}
.rcn-footer__credit a:hover img,
.rcn-footer__credit a:focus-visible img { opacity: 1; }

/* Footer link lists and the brand block. Deliberately quiet: the footer is
   wayfinding, not a second navigation feature. */
.rcn-footer__logo {
  display: inline-flex;
  margin-block-end: var(--space-4);
}
.rcn-footer__logo img {
  height: auto;
  width: clamp(140px, 14vw, 200px);
}
.rcn-footer__meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-small);
  color: var(--ink-2);
  margin: 0;
}
.rcn-footer__list,
.rcn-footer__legal-links {
  list-style: none;
  margin: 0;
  padding: 0;
}
.rcn-footer__list {
  display: flex;
  flex-direction: column;
  /* No inter-item gap - the touch target comes from each link's own
     padding-block, not from stacking a 44px min-height AND a gap on top of
     it. That double-counting was the "far too much vertical whitespace"
     under Event (28 Jul direction) - six short links each claiming a full
     44px row with a further gap between them, on text that reads at ~20px. */
  gap: 0;
}
.rcn-footer__legal-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}
.rcn-footer__list a,
.rcn-footer__legal-links a {
  display: inline-flex;
  align-items: center;
  /* WCAG 2.2 SC 2.5.8 target size via the spacing exception, not a forced
     44px box: each link gets enough padding to keep a comfortable tap area
     without the row itself ballooning past its text. */
  min-height: 32px;
  padding-block: var(--space-1);
  color: var(--ink-2);
  text-decoration: none;
}
.rcn-footer__legal-links a { min-height: auto; padding-block: var(--space-2); }
.rcn-footer__list a:hover,
.rcn-footer__list a:focus-visible,
.rcn-footer__legal-links a:hover,
.rcn-footer__legal-links a:focus-visible {
  color: var(--ink-1);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Header ticket CTA - hidden on small screens, where the same action lives
   inside the takeover panel instead, so the header bar never crowds.
   Deliberately kept at the 960px step even though the inline link list
   itself waits until 1280px (SS8.1): logo + hamburger + this one button fit
   with room to spare well before there's enough width for the full 7-link
   row, so tablet widths still get one-tap ticket access instead of forcing
   everything behind the hamburger. */
/* nowrap for the same reason the nav links need it: a flex item's automatic
   minimum size lets "Buy tickets" break between its two words the moment the
   bar gets tight, which it did as soon as the LMCT+ lockup grew. */
.rcn-header__cta { display: none; white-space: nowrap; }
@media (min-width: 960px) {
  .rcn-header__cta { display: inline-flex; }
}
/* Two CTAs since the HEADER card (2026-07-30). ENTER NOW is held back to
   1100px: between 960 and 1100 the bar has room for one button, not two, and
   dropping the second is better than letting it squeeze the logo lockup. */
.rcn-header__ctas {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.rcn-header__ctas .rcn-btn--secondary { display: none; }
@media (min-width: 1100px) {
  .rcn-header__ctas .rcn-btn--secondary { display: inline-flex; }
}

/* "Header- Desktop", 5 Aug 2026: "Push down the Buy Tickets and Enter Now
   buttons... just like how we did it on northernnats.com.au to make room
   for the LMCT logo... maintain RCN's button design, DO NOT COPY northern
   nats's button design."

   Read literally: borrow the POSITION only. northernnats.com.au drops its
   two buttons out of the nav bar entirely, straddling the seam between the
   bar and the hero below. RCN's own .rcn-btn styling is untouched here - not
   a single colour or radius value changes, only where the button sits (and,
   as of the 5 Aug follow-up below, ENTER NOW's own fill).

   Permanent, on purpose (Rick, 5 Aug: "those buttons should never park back
   on the menu bar when you scroll like they do presently") - an earlier
   version of this rule retracted the drop on scroll via `:not(.is-scrolled)`,
   reasoning that a permanently-offset button would be a site-wide artefact on
   every inner page. Overridden by direct instruction: the dropped position is
   now unconditional at every scroll depth, on every page, same breakpoint as
   the CTAs themselves (960px) - below that they are not even in the bar. */
@media (min-width: 960px) {
  .rcn-header__ctas {
    position: absolute;
    right: 0;
    /* 5 Aug follow-up: "drop down another 15px" from the original straddle -
       top: 100% is the bar's own bottom edge, so the offset is measured from
       there rather than changing the -50% straddle amount, which is what
       centres the buttons on that edge in the first place. */
    top: calc(100% + 15px);
    transform: translateY(-50%);
    z-index: var(--z-header);
  }
}

/* 5 Aug follow-up: "tint the transparent bg of the enter now button with 50%
   black so it remains legible at all times". ENTER NOW is `.rcn-btn--
   secondary`, transparent-background by design (see the base rule above) -
   fine sitting on the dark header bar, but now that it straddles the hero
   below it, it can land on a light patch of any photo/video with nothing to
   separate the white border/text from the image behind it.

   Scoped to the header specifically, NOT a change to .rcn-btn--secondary
   itself - that class is shared across hero CTAs, card grids, popups and
   more (checked), and this problem is unique to sitting over unpredictable
   photography, which only happens here. */
.rcn-header__ctas .rcn-btn--secondary {
  background: rgba(0, 0, 0, 0.5);
}

/* Sticky Buy Tickets bar - SportsCLOUD feedback 2026-07-30, behaviour in
   motion.js SS10c. Present only below 960px, which is exactly where
   .rcn-header__cta above is display:none; above that the sticky header
   already carries the button and a floating second copy would be noise.

   Sits above the safe-area inset so it clears the iOS home indicator, and
   the body gets matching bottom padding while it is shown so the last line
   of the footer can still be scrolled clear of it. */
.rcn-sticky-cta {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: var(--z-sticky);
  display: flex;
  justify-content: center;
  padding: var(--space-3) var(--gutter);
  padding-block-end: calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(to top, var(--surface-1), color-mix(in srgb, var(--surface-1) 82%, transparent));
  border-block-start: 1px solid var(--border-subtle);
}
@media (min-width: 960px) {
  .rcn-sticky-cta { display: none; }
}
/* js-ready gate per the file-level contract: the off-screen resting state is
   only safe to apply when the JS that reveals it is running. */
html.js-ready .rcn-sticky-cta {
  transform: translateY(110%);
  opacity: 0;
  visibility: hidden;
  transition: transform var(--duration-base) var(--ease-throttle),
              opacity var(--duration-base) var(--ease-throttle),
              visibility var(--duration-base) step-end;
}
html.js-ready .rcn-sticky-cta.is-visible {
  transform: translateY(0);
  opacity: 1;
  visibility: visible;
  transition-timing-function: var(--ease-throttle), var(--ease-throttle), step-start;
}
.rcn-sticky-cta__btn { width: 100%; max-width: 26rem; justify-content: center; }

@media (prefers-reduced-motion: reduce) {
  html.js-ready .rcn-sticky-cta { transition: none; }
}

/* ---------------------------------------------------------------------------
   TICKET GRID + FREE-ENTRY NOTE

   The tier cards were previously laid out on the generic 12-column grid with a
   fixed `.rcn-col-4`, which hard-codes three columns regardless of how many
   purchasable tiers actually exist. There are two today; if the client adds or
   removes one, a fixed span leaves a hole. `auto-fit` sizes to the real count.

   `.rcn-ticket-note` carries a tier that has no purchase action (currently the
   free children's entry). It shares the tier cards' surface so it still belongs
   to the ticketing family, but runs full width and horizontal so it cannot be
   mistaken for a buyable tier sitting alongside two that are.
   --------------------------------------------------------------------------- */
.rcn-ticket-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
  gap: var(--space-8);
  /* `stretch` was here to make the CTAs share a baseline. It cost more than it
     bought: on /spectators the 3 Day pass lists seven benefits and Single Day
     lists two, so stretching left 312px of empty paper between the second
     card's last benefit and its button - a card that looks broken, or worse,
     looks like something failed to load. Aligned buttons are only worth having
     when the cards are comparable lengths; a card that is honestly short reads
     better than a card padded with a void. */
  align-items: start;
}
/* A lone tier (e.g. /entrants' single Vehicle Entry card) has no peer to
   share the row with, so 1fr grows it across the ENTIRE container - Rick,
   29 Jul: "this card looks terrible and not like a card at all when it's
   full content width". Cap the column at a real card width whenever there
   is exactly one tier; two or more tiers are unaffected and still share the
   row as before. */
.rcn-ticket-grid:has(> .rcn-ticket-grid__cell:only-child) {
  grid-template-columns: minmax(min(100%, 22rem), 28rem);
}
/* The cell stretches to the row height, and the card must then fill the cell.
   `.rcn-ticket` carries `height: 100%`, but a percentage height resolves against
   the cell's CONTENT box only once the cell itself is a definite-height flex
   container - so the card also needs `flex: 1` here. Without it the card falls
   back to its natural content height and the row goes ragged again, which is
   exactly the defect this grid was introduced to fix. */
.rcn-ticket-grid__cell { display: flex; }
.rcn-ticket-grid__cell > .rcn-ticket { flex: 1; }

.rcn-ticket-note {
  margin-block-start: var(--space-8);
  padding: var(--space-8);
  /* Dark like its sibling tier cards (29 Jul - paper left the ticket family). */
  background: var(--surface-card);
  color: var(--ink-1);
  border-radius: var(--radius-md);
  border-inline-start: 4px solid var(--color-ochre-500);
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4) var(--space-10);
}
@media (min-width: 768px) {
  /* Name in a narrow first column, body copy beside it - a note reads as a note
     when its heading sits alongside rather than stacked like a card title. */
  .rcn-ticket-note { grid-template-columns: minmax(12rem, 1fr) 2fr; align-items: start; }
}
.rcn-ticket-note__name {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: 'opsz' var(--opsz-heading), 'wght' 800;
  font-size: var(--text-display-3);
  line-height: var(--leading-display);
  text-transform: uppercase;
  letter-spacing: var(--tracking-display);
  color: var(--ink-1);
}
.rcn-ticket-note__body > * + * { margin-block-start: var(--space-4); }
.rcn-ticket-note__body p { color: var(--ink-2); }

/* Multiple notes share one row (template wraps them in .rcn-ticket-notes;
   see ticketTiers.ejs 2026-07-27). A bare note - name only, no body - drops
   to h4 scale: display-3 was sized for a note that leads a paragraph block,
   and two display-3 one-liners stacked read as shouting banners. */
.rcn-ticket-notes--row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: var(--space-6);
}
.rcn-ticket-notes--row .rcn-ticket-note { margin-block-start: var(--space-8); grid-template-columns: 1fr; }
.rcn-ticket-note--bare { padding: var(--space-6) var(--space-8); }
.rcn-ticket-note--bare .rcn-ticket-note__name { font-size: var(--text-h4); }

/* Missing-image placeholder, emitted by lib/media.js when a content block
   references a manifest key that does not exist. Deliberately VISIBLE rather
   than a silent empty box - a broken image key should get noticed in review,
   not ship as an invisible gap. It keeps an aspect box so it cannot collapse
   the layout around it. */
.rcn-img-missing {
  display: block;
  aspect-ratio: 3 / 2;
  background:
    repeating-linear-gradient(45deg,
      var(--surface-2) 0 10px,
      var(--surface-1) 10px 20px);
  border: 1px dashed var(--color-red-500);
  border-radius: var(--radius-sm);
}

/* 404 / 500. Given generous vertical space so an error still feels like part of
   the site rather than a dead end, and offered three real ways onward. */
.rcn-404 {
  min-height: 60vh;
  display: flex;
  align-items: center;
}
.rcn-404 .rcn-body-lg {
  max-width: 52ch;
  color: var(--ink-2);
  margin-block: var(--space-6) var(--space-10);
}
.rcn-404__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}


/* ===========================================================================
   8.13-8.17 BLOCK COMPONENTS - added while building views/blocks/{richText,
   cardGrid, imageFeature, gallery, embed}.ejs (see that build's report for
   full rationale). Kept in one clearly separate block, appended after §8.12
   rather than interleaved with the numbered 8.x sections above, per the
   collision-avoidance convention agreed with the concurrent agent building
   views/blocks/{hero,pageHero,program,ticketTiers,prizeTable,statBand,
   ctaBand}.ejs, who owns everything numbered above this comment.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   8.13 PROSE - shared treatment for pre-sanitised harvested HTML. Reused by
   richText's `html`, the FAQ accordion's `a` (answer) and embed's
   `asideHtml` - anywhere server-rendered markup (h2-h4/p/ul/ol/a/strong/
   blockquote/table) needs to look designed rather than dropped in raw.
   Applied via `.rcn-prose` alongside the component's own class, never as a
   replacement for it.
   --------------------------------------------------------------------------- */
.rcn-prose {
  /* Full container width, matching the header - client direction 2026-07-27
     ("Fix Content Width", with a screenshot marking the header's edges).

     This was 68ch (~620px) and centred, which is the classic comfortable
     reading measure. The client found it too cramped against a 1200px header,
     and on the long legal and program pages they are right that it read as a
     narrow ribbon down the middle of a wide page.

     The trade-off is real and worth stating: at 1200px, 16px body copy runs to
     roughly 120 characters a line, well past the 45-75 that is easiest to read
     continuously, and the eye can lose its place returning to the next line.
     `--prose-max` exists so that is one value to change, not a hunt through
     the stylesheet, if a middle setting is ever wanted. */
  max-width: var(--prose-max);
  color: var(--ink-2);
  margin-inline: 0;
}
.rcn-prose > * + * { margin-block-start: var(--space-4); }

/* Superscripts and subscripts appear in real harvested copy - "paying down to
   5<sup>th</sup> place" on the Burnouts page, for one. Browsers default <sup> to
   inline, but the surrounding reset sets a line-height that made it wrap onto
   its own line, so that sentence rendered as three broken lines:
   "paying down to 5" / "th" / "place." Pin the line-height so it stays inline. */
.rcn-prose sup,
.rcn-prose sub {
  font-size: 0.66em;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}
.rcn-prose sup { top: -0.5em; }
.rcn-prose sub { bottom: -0.25em; }

.rcn-prose h2,
.rcn-prose h3,
.rcn-prose h4 {
  color: var(--ink-1);
  margin-block-start: var(--space-10);
  margin-block-end: var(--space-1);
}
.rcn-prose > h2:first-child,
.rcn-prose > h3:first-child,
.rcn-prose > h4:first-child { margin-block-start: 0; }
/* Nested inside a smaller container (an accordion panel, a form aside) the
   display face at its full h3 size reads oversized - one step down via the
   same font-variation-settings axis the global h3 rule already uses. */
.rcn-faq__panel-inner.rcn-prose h3,
.rcn-form__aside.rcn-prose h3 {
  font-size: var(--text-h4);
}

.rcn-prose p { color: var(--ink-2); max-width: none; }

.rcn-prose ul,
.rcn-prose ol {
  padding-inline-start: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.rcn-prose li::marker { color: var(--color-red-500); font-weight: 700; }

/* Tick glyphs the client's own copy uses as bullets, wrapped at render time
   by accentGlyphs() (lib/view-helpers.js) purely so CSS can reach them.
   Same brand red as the list markers above and the ticket-tier benefit ticks
   (SS11.19), so a tick reads identically wherever it appears. */
.rcn-prose .rcn-glyph {
  color: var(--color-red-500);
  font-weight: 700;
  margin-inline-end: var(--space-1);
}

.rcn-prose a {
  color: var(--color-red-300);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color var(--duration-fast) var(--ease-throttle);
}
.rcn-prose a:hover,
.rcn-prose a:focus-visible { color: var(--color-red-500); }

.rcn-prose strong { color: var(--ink-1); font-weight: 700; }

.rcn-prose blockquote {
  padding-inline-start: var(--space-6);
  border-inline-start: 3px solid var(--color-red-500);
  color: var(--ink-1);
}

/* Tables (harvested content occasionally includes one, e.g. a one-cell
   contact-email table on the FAQ page) scroll horizontally rather than
   squeezing columns or overflowing the viewport - same technique as the
   prize-pool table (§8.5), applied generically since prose tables aren't a
   fixed shape. */
.rcn-prose table { display: block; width: 100%; overflow-x: auto; }
.rcn-prose th,
.rcn-prose td {
  padding: var(--space-3);
  text-align: left;
  border-block-end: 1px solid var(--border-subtle);
}
.rcn-prose th {
  font-family: var(--font-body);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  font-size: var(--text-small);
  color: var(--ink-3);
}

.rcn-prose__images {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin-block-start: var(--space-8);
}
@media (min-width: 640px) {
  .rcn-prose__images { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
}
/* Equal tiles - OPT-IN PER BLOCK. Do not make this global again.

   Client card PLAN YOUR TRIP, 2026-07-30: "Images are not in the same size on
   the Red Centre Experiences section. Please make all images equal in size."
   True on that row - a portrait campaign graphic beside two landscape photos,
   each at its own aspect, which read as a mistake.

   Written first as a rule for every prose image row on the site, which was
   wrong for the same reason the diptych was: it silently re-cropped four other
   pages nobody had asked about. /tickets and /enter carry SQUARE OZTIX ticket
   graphics, and forcing those into a 3:2 box crops artwork that has pricing
   text in it - precisely the failure the RCN12 poster fix existed to stop.

   So the block opts in, and only Plan Your Trip does. A page-specific request
   gets a page-specific change. */
.rcn-prose__image { border-radius: var(--radius-md); overflow: hidden; margin: 0; }

.rcn-prose__images--equal .rcn-prose__image { aspect-ratio: 3 / 2; }
.rcn-prose__images--equal .rcn-prose__image img,
.rcn-prose__images--equal .rcn-prose__image picture {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.rcn-prose__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-block-start: var(--space-8);
}
/* A cta with no href yet ("coming soon" - real content has several) renders
   the same button shape so nothing wobbles once the link lands, but is
   visibly and functionally inert. */
.rcn-btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: default;
  pointer-events: none;
}


/* ---------------------------------------------------------------------------
   8.14 CARD GRID - discipline/guide links (Festival Info hub, Plan Your
   Trip). One link per card (the heading) with the whole surface clickable
   via the link's own ::after stretched over the card - never a second
   redundant link around the image.
   --------------------------------------------------------------------------- */
.rcn-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: var(--gutter);
}
/* A 6-card grid on a 4-column screen leaves two cards stranded hard left with
   a two-column void beside them (Rick, 29 Jul: "this six pack of cards really
   needs the bottom row to be in the two middle columns"). Nudging the 5th card
   to column 2 centres the pair under the row above; the 6th auto-flows into
   column 3.

   `:nth-child(5):nth-last-child(2)` is the whole test - it matches only when
   the 5th card is also the second-from-last, i.e. exactly 6 cards. Scoped to
   >=1120px because that is where this grid is genuinely 4 columns (4 x 15rem
   + 3 gutters), and it never reaches 5 columns since --content-max caps the
   track count. Below that the rows re-flow and no correction applies. */
@media (min-width: 1120px) {
  .rcn-card-grid > .rcn-card:nth-child(5):nth-last-child(2) { grid-column: 2; }
}

/* THREE ACROSS, TRAILING ROW CENTRED  (opt-in: cardGrid `threeAcross`)

   Client card "Plan your trip page- card layout", 2026-07-31: "displaying the
   first 3 card and on the 2nd row the displaying the 4th and 5th tile/card.
   Also dont forget to center the cards." Five cards were flowing 4 + 1, which
   leaves a single tile stranded under three columns of nothing.

   SIX tracks, each card spanning two, rather than three tracks. That is the
   only way to centre a two-card row under a three-card row: two cards centred
   across three columns would each have to occupy a column and a half, which a
   three-track grid cannot express. On six tracks they simply start at track 2.

   The nth-child pairs are exact-count tests, same idea as the six-card rule
   above: `:nth-child(4):nth-last-child(2)` matches the 4th card only when it
   is also second-from-last, i.e. exactly five cards; the pair below it matches
   exactly four. Anything else keeps clean full rows and needs no correction.

   Opt-in, not automatic, because forcing three columns is a design decision
   about ONE section - the entrant hub's 24 tiles and the six-card hubs are
   approved at their own natural column counts and must not be re-flowed. */
@media (min-width: 960px) {
  .rcn-card-grid--three {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
  .rcn-card-grid--three > .rcn-card { grid-column: span 2; }
  /* exactly 5: the 4th starts at track 2, the 5th follows at track 4 */
  .rcn-card-grid--three > .rcn-card:nth-child(4):nth-last-child(2) { grid-column: 2 / span 2; }
  /* exactly 4: the lone 4th card sits in the middle */
  .rcn-card-grid--three > .rcn-card:nth-child(4):nth-last-child(1) { grid-column: 3 / span 2; }
}

.rcn-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface-card);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-1);
  transition: transform var(--duration-fast) var(--ease-throttle),
              box-shadow var(--duration-fast) var(--ease-throttle);
}
.rcn-card:hover,
.rcn-card:focus-within {
  transform: translateY(calc(var(--space-1) * -1));
  box-shadow: var(--shadow-3);
}
.rcn-card:active { transition-timing-function: var(--ease-brake); }

.rcn-card__media {
  position: relative;
  aspect-ratio: var(--ratio-card);
  background: var(--surface-2);
}
.rcn-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-base) var(--ease-throttle);
}
.rcn-card:hover .rcn-card__media img,
.rcn-card:focus-within .rcn-card__media img { transform: scale(1.04); }

.rcn-card__body {
  padding: var(--space-5);
  flex: 1;
  display: flex;
  /* Column, not row (changed 2026-07-27): the landing-page hub cards add a
     blurb and a "Coming soon" tag under the title (§11.9), and a row layout
     set them BESIDE the title as skinny squeezed columns.

     TOP-anchored, not centred (changed 29 Jul - Rick: "you cannot vertically
     centre all content in same-height cards and have them look uniform with
     different content ... all number chips, all titles, all text must start
     at the same height"). Grid rows stretch every card to the tallest one in
     its row, and card content length genuinely varies (a 1-line title + short
     blurb vs. a 3-line title + long blurb) - centring that stack inside a
     stretched cell puts each card's own content block at a DIFFERENT y purely
     because its total height differs, which is exactly the misalignment
     reported. Top-anchoring plus the title's own min-height reservation
     (below) is what makes chip/title/text share one baseline regardless of
     how much each card's own copy runs. The one place vertical centring is
     still correct - the short, length-matched "Who are you?" set - restores
     it explicitly (SS11.23). */
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
}
.rcn-card--no-image .rcn-card__body { padding-block: var(--space-8); }

/* min-height reserves room for the longest real title (3 lines, e.g.
   "Bojangles Pub Ride Through") so every card lands on the same height
   regardless of whether its own title is 1, 2 or 3 lines - client-reported
   as "25 cards, heights 309-350px". The card images were already a
   consistent aspect-ratio box (not the cause); rows differed only because
   grid stretch equalises WITHIN a row, and different rows had different
   longest-title lengths. Reserving the space directly means every card's
   own natural height is already identical, so rows can't go ragged. */
.rcn-card__title {
  font-size: var(--text-display-3);
  margin: 0;
  min-height: calc(var(--text-display-3) * var(--leading-display) * 3);
}
.rcn-card__link { color: var(--ink-1); }
.rcn-card__link::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}
.rcn-card__link:hover,
.rcn-card__link:focus-visible { color: var(--color-red-300); }


/* ---------------------------------------------------------------------------
   8.15 IMAGE FEATURE - split image + copy, alternating side by index parity.
   A second image (when supplied - real content always has two) overlaps the
   first like a physical print propped in its corner, rather than two
   same-size photos stacked flatly.
   --------------------------------------------------------------------------- */
.rcn-feature {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-10);
  /* `center` was leaving a large asymmetric void under a short text column
     sitting beside a tall image - the copy floated in the middle of its cell
     with dead space above and below, which reads as accidental rather than as
     an editorial choice. `start` aligns both columns to a shared top edge, so
     the section has a definite beginning and any leftover space collects in one
     place at the bottom instead of being split unevenly around the text. */
  align-items: start;
}
@media (min-width: 960px) {
  .rcn-feature { grid-template-columns: 1fr 1fr; gap: var(--space-16); }
  .rcn-feature--reversed .rcn-feature__media { order: 2; }
  .rcn-feature--reversed .rcn-feature__body { order: 1; }
}

.rcn-feature__media { position: relative; }
.rcn-feature__media-primary {
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-2);
}
.rcn-feature__media-secondary { display: none; }
@media (min-width: 640px) {
  /* Selector is doubled up (`.a.b`) ON PURPOSE - do not simplify it.

     This element carries both `.rcn-feature__media-secondary` and `.rcn-photo`.
     `.rcn-photo` sets `position: relative` and is declared LATER in this file
     (section 9.7, the photo device) than this rule. Two single-class selectors
     have identical specificity, so source order decided it and `.rcn-photo` won:
     the secondary image silently lost its absolute positioning and rendered as a
     detached block floating below the primary image instead of overlapping its
     bottom-right corner.

     Qualifying with `.rcn-photo` raises specificity above the bare `.rcn-photo`
     rule so the intended positioning holds regardless of source order. */
  .rcn-feature__media-secondary.rcn-photo,
  .rcn-feature__media-secondary {
    display: block;
    position: absolute;
    z-index: 1;
    right: calc(var(--space-8) * -1);
    bottom: calc(var(--space-8) * -1);
    width: 42%;
    max-width: 14rem;
    border: 4px solid var(--surface-0);
    border-radius: var(--radius-md);
    overflow: hidden;
    box-shadow: var(--shadow-3);
  }
}
/* --- Artwork diptych --------------------------------------------------
   Client card 2026-07-30 flagged the corner print covering the RCN12 key
   art. The second image is NOT dropped (Rick, 2026-07-31: "find a new
   creative way to use it instead") - it is promoted out of the overlap into
   a companion panel beside the poster.

   The pairing is deliberate rather than incidental: the poster is a tall
   portrait composition and this photograph is a 0.47 sliver of pink burnout
   smoke, so side by side they read as one wide-format diptych. The poster
   column keeps the artwork's own aspect and is never cropped; the photo
   column stretches to match its height and crops with object-fit, which is
   safe on a photograph and is the whole reason the two are treated
   differently in the first place.

   Single column below 640px - at phone width a 30% sliver would be a few
   dozen pixels wide and read as a rendering fault, so the poster stands
   alone and the photo is dropped rather than shown badly. */
@media (min-width: 640px) {
  /* Flex, not grid, and the two flex-grow factors ARE the two images' measured
     aspect ratios (--dip-a / --dip-b, set inline by the template from the
     manifest).

     That is the whole trick: with `flex-basis: 0`, each panel's width comes out
     proportional to its grow factor, so widths land in the ratio Aa : Ab. Two
     images whose widths are in the same ratio as their aspects render at
     IDENTICAL heights - which means neither needs cropping and neither
     letterboxes. Matching the heights by hand (stretch + object-fit) crops one
     of them; matching by aspect crops neither, and re-derives itself if the
     artwork is ever swapped. */
  .rcn-feature__media--diptych {
    display: flex;
    gap: var(--space-3);
    align-items: flex-start;
  }
  .rcn-feature__media--diptych .rcn-feature__media-primary {
    flex: var(--dip-a, 1) 1 0;
    min-width: 0;
  }
  /* Doubled with .rcn-photo for the same specificity reason documented on
     the overlap rule above - and additionally to beat it. */
  .rcn-feature__media--diptych .rcn-feature__media-secondary.rcn-photo,
  .rcn-feature__media--diptych .rcn-feature__media-secondary {
    position: static;
    display: block;
    flex: var(--dip-b, 1) 1 0;
    min-width: 0;
    width: auto;
    max-width: none;
    inset: auto;
    border: 0;
    border-radius: var(--radius-md);
    overflow: hidden;
    box-shadow: var(--shadow-2);
  }
  .rcn-feature__media--diptych .rcn-feature__media-secondary img {
    display: block;
    width: 100%;
    height: auto;
  }
}
@media (max-width: 639px) {
  .rcn-feature__media--diptych .rcn-feature__media-secondary { display: none; }
}

.rcn-feature__extra {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-4);
}
.rcn-feature__extra img {
  width: 4.5rem;
  height: 4.5rem;
  object-fit: cover;
  border-radius: var(--radius-sm);
}

.rcn-feature__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 60ch;
}
.rcn-feature__text { color: var(--ink-2); max-width: 60ch; }


/* ---------------------------------------------------------------------------
   8.16 GALLERY - featured image + grid, halftone-treated uniformly (§9.7) so
   photography from different years/cameras reads as one system. No
   lightbox dependency: grid items link to their own largest variant.
   --------------------------------------------------------------------------- */
.rcn-gallery__header { max-width: 70ch; }

.rcn-gallery__featured {
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-2);
}
.rcn-gallery__featured img { width: 100%; height: auto; }

.rcn-gallery__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4);
}
@media (min-width: 640px) {
  .rcn-gallery__grid { grid-template-columns: repeat(4, 1fr); }
}
.rcn-gallery__item {
  position: relative;
  display: block;
  aspect-ratio: 1;
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.rcn-gallery__item .rcn-photo { position: absolute; inset: 0; }
.rcn-gallery__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-fast) var(--ease-throttle);
}
.rcn-gallery__item:hover img,
.rcn-gallery__item:focus-visible img { transform: scale(1.05); }


/* ---------------------------------------------------------------------------
   8.17 EMBED / NATIVE FORM - a real <form> (contact enquiry today; the shape
   generalises to any native form). Field primitives (.rcn-field etc.)
   already exist in §7 - this is just the two-column form/aside layout, the
   required-field marker and the inline success banner.
   --------------------------------------------------------------------------- */
.rcn-form-section {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-12);
}
@media (min-width: 960px) {
  .rcn-form-section { grid-template-columns: 3fr 2fr; }
}

.rcn-form__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}
@media (min-width: 640px) {
  .rcn-form__grid { grid-template-columns: 1fr 1fr; }
  .rcn-form__grid .rcn-field--full { grid-column: 1 / -1; }
}

.rcn-field__required-mark {
  color: var(--color-ochre-300);
  font-weight: 500;
  text-transform: none;
  letter-spacing: normal;
}

.rcn-form__aside {
  background: var(--surface-card);
  border-radius: var(--radius-md);
  padding: var(--space-8) var(--space-6);
}

/* Success banner - colour is never the only signal (§I): the check icon and
   the confirmation text both carry the message. Hidden until a future
   enhancement (once /api/enquiry exists) removes the `hidden` attribute. */
.rcn-form__banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  border-inline-start: 3px solid var(--color-success-500);
  color: var(--ink-1);
  margin-block-end: var(--space-6);
}
.rcn-form__banner[hidden] { display: none; }
.rcn-form__banner .rcn-icon-check {
  color: var(--color-success-500);
  flex: none;
  width: 1.25em;
  height: 1.25em;
  margin-block-start: 0.15em;
}

/* Sponsor-wall addendum (§8.9 above): every logo link meets the 44px target
   size regardless of how short the logo itself is (§I), without stretching
   the logo image to reach it. */
.rcn-sponsors__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--target-min);
  padding-block: var(--space-2);
}


/* ---------------------------------------------------------------------------
   8.18 CTA BAND (views/blocks/ctaBand.ejs)

   Added for the build: a single full-width conversion band ({label, href}) -
   not one of the named components in §H (which covers the two-path split and
   ticket cards but not this simpler single-link band). Numbered to continue
   after the concurrently-built 8.13-8.17 block above rather than interleave
   with it - see that section's own header comment for the shared convention.
   The whole band is one link (not a heading plus a separately-labelled
   button) since the content shape is just a label and an href.
   --------------------------------------------------------------------------- */
.rcn-cta-band {
  background: var(--gradient-red);
}
.rcn-cta-band__link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  min-height: var(--target-min);
  padding: var(--space-10) var(--gutter);
  color: var(--ink-1);
  font-family: var(--font-display);
  font-weight: 800;
  font-variation-settings: 'opsz' var(--opsz-heading), 'wght' 800;
  font-size: var(--text-display-3);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  text-transform: uppercase;
  text-align: center;
  text-wrap: balance;
}
.rcn-cta-band__arrow {
  display: inline-block;
  transition: transform var(--duration-fast) var(--ease-throttle);
}
.rcn-cta-band__link:hover .rcn-cta-band__arrow,
.rcn-cta-band__link:focus-visible .rcn-cta-band__arrow {
  transform: translateX(var(--space-2));
}


/* ===========================================================================
   9. SIGNATURE GRAPHIC DEVICES (§F)
   =========================================================================== */

/* ---------------------------------------------------------------------------
   9.1 SMOKE PLUME

   DEVIATION FROM BRIEF (documented per build instructions): §F device 1 calls
   for reusing the live site's 42-frame smoke__sprite.png. That asset is not
   obtainable - the live frontend sits behind a Vercel bot challenge that
   returns 403 for every static asset (verified directly). Built instead from
   real burnout photography already in this repo (e.g.
   burnout-red-centrenats-2023-lynchy-3840x2560-1-scaled.jpg, genuine
   3840x2560 tyre smoke): 2-3 layered instances at different scales/
   opacities/drift speeds, mix-blend-mode:screen over the dark hero, slow
   transform drift. The image is set via --smoke-img so the template can
   swap the source per page without touching this file.
   --------------------------------------------------------------------------- */

.rcn-smoke {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.rcn-smoke__layer {
  position: absolute;
  inset: -20% -10%; /* oversized so the drift never reveals a hard edge */
  /* Must name a real DERIVATIVE, not the master's filename. The image pipeline
     archives masters to _assets/masters/ and leaves only sized variants in
     public/media/, so `...-scaled.jpg` 404s silently and the smoke never shows. */
  background-image: var(--smoke-img, url('/media/photos/burnout-red-centrenats-2023-lynchy-3840x2560-1-scaled-1920.webp'));
  background-size: cover;
  background-position: center;
  mix-blend-mode: screen; /* reads as smoke against the dark hero rather than a grey box */
  will-change: transform;
  animation-name: rcn-smoke-drift;
  animation-timing-function: var(--ease-drift);
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
/* Ambient - it never stops, per §G ("atmosphere, not an entrance effect") -
   only the global reduced-motion rule in tokens.css freezes it.

   OPACITIES ARE DELIBERATELY LOW, and that is a correction worth understanding
   before anyone "restores" them. These layers use mix-blend-mode: screen, which
   can only ever lighten. The device was specified against a dark night hero,
   where screen-blended smoke reads as smoke. The hero photograph is now a real
   daytime shot that is ITSELF a wall of sunlit tyre smoke - and three screen
   layers at 0.55/0.40/0.28 over that flooded the entire hero to a flat pale
   grey-beige fog, destroying the photograph and most of its contrast.

   --smoke-strength scales all three together, so a darker photo can dial it back
   up per-instance without touching these rules. */
/* SMOKE OFF WHILE THE VIDEO PLAYS (Rick, 6 Aug 2026 - reported twice).

   This is the actual cause of the "I can see the image underneath the video"
   report, and it was the opposite way round to how it looked: the smoke device
   is not a gradient, it is a REAL PHOTOGRAPH (the 2023 Lynchy burnout frame,
   see --smoke-img above) painted at 20/13/8% with mix-blend-mode: screen - and
   it sits ON TOP of the video, not underneath it. Over a still hero it reads as
   atmosphere. Over full-motion footage the two images never agree, so the
   stationary photo ghosts through the moving one and looks exactly like a
   half-transparent video with a picture showing through.

   Only suppressed while a video is actually playing. With JS off, autoplay
   refused, or reduced motion the hero falls back to the still photograph, which
   is the surface this device was designed for - and there it stays untouched. */
.rcn-hero:has(.rcn-hero__video.is-playing) .rcn-smoke { display: none; }

.rcn-smoke__layer--1 {
  opacity: calc(0.20 * var(--smoke-strength, 1));
  --smoke-scale: 1.15;
  animation-duration: calc(var(--duration-ambient) * 1.4);
}
.rcn-smoke__layer--2 {
  opacity: calc(0.13 * var(--smoke-strength, 1));
  --smoke-scale: 1.35;
  animation-duration: var(--duration-ambient);
  animation-delay: -2666ms; /* negative delay staggers the loop's starting phase without a JS timer */
}
.rcn-smoke__layer--3 {
  opacity: calc(0.08 * var(--smoke-strength, 1));
  --smoke-scale: 1.6;
  animation-duration: calc(var(--duration-ambient) * 0.75);
  animation-delay: -5000ms;
}
/* Fastest scroll-parallax layer of the hero's three (§G): background ridge
   slowest, mid-ground photo mid, smoke fastest. Full amplitude here. */
@supports (animation-timeline: scroll()) {
  .rcn-smoke__layer {
    animation-name: rcn-smoke-drift, rcn-parallax-shift;
    animation-timeline: auto, scroll(root);
    animation-range: auto, 0 100vh;
    --parallax-shift: var(--parallax-max);
  }
}

@keyframes rcn-smoke-drift {
  from { transform: translate3d(-3%, 2%, 0) scale(var(--smoke-scale, 1.2)); }
  to   { transform: translate3d(3%, -2%, 0) scale(var(--smoke-scale, 1.2)); }
}
/* Shared scroll-linked shift keyframe, used by the hero photo, smoke layers
   and the contour ridge (§9.8) - each sets its own --parallax-shift first. */
@keyframes rcn-parallax-shift {
  from { transform: translateY(0); }
  to   { transform: translateY(var(--parallax-shift, 0px)); }
}


/* ---------------------------------------------------------------------------
   9.2 TACHO REDLINE ARC

   Inline-SVG contract this component expects (documented so the template
   author can match it exactly):
     <svg class="rcn-tacho" viewBox="0 0 200 200" style="--tacho-target:0.82">
       <circle class="rcn-tacho__track"  cx="100" cy="100" r="84"/>
       <circle class="rcn-tacho__danger" cx="100" cy="100" r="84"/>
       <circle class="rcn-tacho__value"  cx="100" cy="100" r="84"/>
       <line   class="rcn-tacho__needle" x1="100" y1="100" x2="100" y2="24"/>
       <text   class="rcn-tacho__label"  x="100" y="108">$130k+</text>
     </svg>
   r=84 gives a circumference of 2 * pi * 84 = 527.79 (--tacho-c below). If a
   template ever needs a different radius, override --tacho-c inline to match
   - it is not derived automatically, since CSS cannot compute 2*pi*r itself.
   --tacho-target (0-1) is set inline per instance - it's the one thing that
   is genuinely per-instance data, not a design token.
   --------------------------------------------------------------------------- */

.rcn-tacho {
  --tacho-c: 527.79;
  width: 100%;
  max-width: 16rem;
  overflow: visible;
}

.rcn-tacho__track {
  fill: none;
  stroke: var(--border-subtle);
  stroke-width: 12;
}

/* Static red danger-zone segment - the last 20% of the sweep, fixed, not
   animated - orientation marker for "redline", independent of the value. */
.rcn-tacho__danger {
  fill: none;
  stroke: var(--color-red-500);
  stroke-width: 12;
  stroke-dasharray: calc(var(--tacho-c) * 0.2) var(--tacho-c);
  stroke-dashoffset: calc(var(--tacho-c) * -0.8);
}

.rcn-tacho__value {
  fill: none;
  stroke: var(--color-ochre-300);
  stroke-width: 12;
  stroke-linecap: round;
  stroke-dasharray: var(--tacho-c);
  stroke-dashoffset: var(--tacho-c); /* static fallback: empty gauge until reveal/support */
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
}

.rcn-tacho__needle {
  stroke: var(--ink-1);
  stroke-width: 3;
  stroke-linecap: round;
  transform-origin: 100px 100px; /* matches the documented viewBox contract above */
  transform: rotate(calc(-130deg + (var(--tacho-target, 0.75) * 260deg))); /* 260deg total sweep, classic gauge geometry */
  transition: transform var(--duration-slow) var(--ease-drift);
}

.rcn-tacho__label {
  fill: var(--ink-1);
  font-family: var(--font-body);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  text-anchor: middle;
}

@keyframes rcn-tacho-sweep {
  from { stroke-dashoffset: var(--tacho-c); }
  to   { stroke-dashoffset: calc(var(--tacho-c) * (1 - var(--tacho-target, 0.75))); }
}

/* Scroll-driven sweep, no JS - progressively enhances the static fallback. */
@supports (animation-timeline: view()) {
  .rcn-tacho__value {
    animation: rcn-tacho-sweep linear both;
    animation-timeline: view();
    animation-range: entry 10% cover 50%;
  }
}
/* Fallback for engines without view(): drive off the same [data-reveal]
   convention as everything else, if the template wraps the svg with it. */
@supports not (animation-timeline: view()) {
  [data-reveal] .rcn-tacho__value {
    transition: stroke-dashoffset var(--duration-slow) var(--ease-drift);
  }
  [data-reveal].is-visible .rcn-tacho__value {
    stroke-dashoffset: calc(var(--tacho-c) * (1 - var(--tacho-target, 0.75)));
  }
}


/* ---------------------------------------------------------------------------
   9.3 STAGING LIGHTS

   The drag-strip "Christmas tree": pre-stage, stage, three ambers, green -
   six lights, stacked vertically (horizontal on wider screens via modifier).
   Reused as: countdown chrome, an async-loading indicator (checkout submit),
   and a page-transition amber sweep. The specific class the JS toggles to
   trigger the loading/sweep sequences is documented per rule below.
   --------------------------------------------------------------------------- */

.rcn-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
}
.rcn-stage--horizontal {
  flex-direction: row;
}

.rcn-stage__light {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--surface-2);
  border: 2px solid var(--border-strong);
  box-shadow: var(--shadow-1);
}

/* Loading state - motion.js adds .rcn-stage--loading while an async action
   (e.g. checkout submit) is in flight. Lights pulse in sequence, looping. */
@keyframes rcn-stage-pulse {
  0%, 100% { background-color: var(--surface-2); box-shadow: var(--shadow-1); }
  50% { background-color: var(--light-on, var(--color-ochre-500)); box-shadow: var(--glow-red); }
}
.rcn-stage--loading .rcn-stage__light {
  animation: rcn-stage-pulse var(--duration-slow) var(--ease-throttle) infinite;
}
.rcn-stage--loading .rcn-stage__light:nth-child(1) { --light-on: var(--color-ochre-700); animation-delay: calc(var(--duration-instant) * 0); }
.rcn-stage--loading .rcn-stage__light:nth-child(2) { --light-on: var(--color-ochre-500); animation-delay: calc(var(--duration-instant) * 1); }
.rcn-stage--loading .rcn-stage__light:nth-child(3) { --light-on: var(--color-hot-500);   animation-delay: calc(var(--duration-instant) * 2); }
.rcn-stage--loading .rcn-stage__light:nth-child(4) { --light-on: var(--color-hot-500);   animation-delay: calc(var(--duration-instant) * 3); }
.rcn-stage--loading .rcn-stage__light:nth-child(5) { --light-on: var(--color-hot-500);   animation-delay: calc(var(--duration-instant) * 4); }
.rcn-stage--loading .rcn-stage__light:nth-child(6) { --light-on: var(--color-success-500); animation-delay: calc(var(--duration-instant) * 5); }

/* Page-transition sweep - motion.js adds .is-active for one run on route change
   (§G: total under ~400ms, one-shot).

   The base rule below is REQUIRED and was missing: without it the overlay is an
   ordinary in-flow flex column sitting at the end of <body>, so its three
   staging lights rendered as permanent decorative circles at the bottom of every
   single page. It must be fixed-position, centred, non-interactive, and
   invisible until activated. */
.rcn-stage--sweep {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  background: var(--scrim-soft);
  transition: opacity var(--duration-fast) var(--ease-throttle),
              visibility var(--duration-fast) step-end;
}
.rcn-stage--sweep.is-active {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--duration-instant) var(--ease-throttle),
              visibility 0s;
}

@keyframes rcn-stage-flash {
  0%   { background-color: var(--surface-2); box-shadow: var(--shadow-1); }
  40%  { background-color: var(--light-on, var(--color-ochre-500)); box-shadow: var(--glow-red); }
  100% { background-color: var(--surface-2); box-shadow: var(--shadow-1); }
}
.rcn-stage--sweep.is-active .rcn-stage__light {
  animation: rcn-stage-flash var(--duration-fast) var(--ease-throttle) 1 both;
}
.rcn-stage--sweep.is-active .rcn-stage__light:nth-child(1) { --light-on: var(--color-ochre-700); animation-delay: 0ms; }
.rcn-stage--sweep.is-active .rcn-stage__light:nth-child(2) { --light-on: var(--color-ochre-500); animation-delay: 30ms; }
.rcn-stage--sweep.is-active .rcn-stage__light:nth-child(3) { --light-on: var(--color-hot-500);   animation-delay: 60ms; }
.rcn-stage--sweep.is-active .rcn-stage__light:nth-child(4) { --light-on: var(--color-hot-500);   animation-delay: 90ms; }
.rcn-stage--sweep.is-active .rcn-stage__light:nth-child(5) { --light-on: var(--color-hot-500);   animation-delay: 120ms; }
.rcn-stage--sweep.is-active .rcn-stage__light:nth-child(6) { --light-on: var(--color-success-500); animation-delay: 150ms; }

/* Countdown staging run - the tree finally DOES something on the countdown
   (29 Jul, Rick: "what was the intended purpose of the 6 circles? ... keep
   them but make them do something"). motion.js runs a real drag-tree launch
   sequence synced to the clock's own seconds: each second one more light
   holds steady - pre-stage, stage, three ambers - and on the sixth the green
   drops, then the tree resets and re-arms. Colour per POSITION (same mapping
   the loading/sweep states use); JS only ever toggles .is-lit. Steady-state
   colour changes at 1Hz, no keyframes - and under reduced motion the JS
   never arms the sequence at all, leaving the lights as static chrome. */
.rcn-stage--tree .rcn-stage__light {
  transition: background-color var(--duration-fast) var(--ease-throttle),
              box-shadow var(--duration-fast) var(--ease-throttle);
}
.rcn-stage--tree .rcn-stage__light.is-lit {
  background-color: var(--light-on, var(--color-ochre-500));
  box-shadow: var(--glow-red);
}
.rcn-stage--tree .rcn-stage__light:nth-child(1) { --light-on: var(--color-ochre-700); }
.rcn-stage--tree .rcn-stage__light:nth-child(2) { --light-on: var(--color-ochre-500); }
.rcn-stage--tree .rcn-stage__light:nth-child(3) { --light-on: var(--color-hot-500); }
.rcn-stage--tree .rcn-stage__light:nth-child(4) { --light-on: var(--color-hot-500); }
.rcn-stage--tree .rcn-stage__light:nth-child(5) { --light-on: var(--color-hot-500); }
.rcn-stage--tree .rcn-stage__light:nth-child(6) { --light-on: var(--color-success-500); }

/* Day-marker variant - added for views/blocks/program.ejs, using the same six
   lights as a static "how far into the event" read (day 1 = one lit light,
   day 5 = five) rather than the loading-pulse or transition-sweep states
   above. Smaller and horizontal since it sits inline next to a day heading,
   not standalone chrome; lit/unlit only, no animation. */
.rcn-stage--marker { flex-direction: row; gap: var(--space-1); }
.rcn-stage--marker .rcn-stage__light { width: var(--space-3); height: var(--space-3); }
.rcn-stage--marker .rcn-stage__light.is-lit {
  background: var(--color-red-500);
  border-color: var(--color-red-500);
  box-shadow: var(--glow-red);
}


/* ---------------------------------------------------------------------------
   9.4 LICENCE-PLATE FRAME

   Reusable card/quote/stat framing device. --radius-plate matched to real AU
   plate corner-radius proportions (tokens.css). Two circular pseudo-element
   "bolts" in the top corners only, per the brief - a real plate has four,
   but only two pseudo-elements exist per box without extra markup, and two
   is what the brief specifies.
   --------------------------------------------------------------------------- */

.rcn-plate {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-6);
  background: var(--surface-paper);
  color: var(--ink-on-paper);
  border: 2px solid var(--ink-on-paper);
  border-radius: var(--radius-plate);
  font-family: var(--font-body);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: var(--tracking-plate);
  font-variant-numeric: tabular-nums;
}
.rcn-plate::before,
.rcn-plate::after {
  content: '';
  position: absolute;
  top: var(--space-2);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink-on-paper);
  opacity: 0.35;
}
.rcn-plate::before { left: var(--space-2); }
.rcn-plate::after { right: var(--space-2); }

/* Block variant for testimonial/quote callouts and "Est. [year]" stat chips
   that need more than one line - same frame, full-width, left-aligned. */
.rcn-plate--block {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  text-align: left;
  padding: var(--space-6) var(--space-8);
}
.rcn-plate--block .rcn-plate__quote {
  font-family: var(--font-body);
  font-weight: 400;
  font-style: italic;
  text-transform: none;
  letter-spacing: normal;
}
.rcn-plate--block .rcn-plate__attribution {
  font-size: var(--text-small);
  font-weight: 600;
  letter-spacing: var(--tracking-ui);
}


/* ---------------------------------------------------------------------------
   9.5 SCRUTINEERING STICKER

   Irregular "torn/stamped" badge. The clip-path points below are hand-
   authored once (14-point jittered radius around a circle, computed offline
   for precision, not randomised per load - see the brief's explicit
   "authored once... not randomised" instruction, which applies here just as
   much as it does to the torn-decal-edge variants in §9.6). Three shape
   variants exist so repeated badges on one page (e.g. a grid of ticket
   tiers) don't look identically stamped; rotation is a separate axis, set
   per-instance via the inline --rotate custom property and varied by
   :nth-child, exactly as specified.
   --------------------------------------------------------------------------- */

.rcn-sticker {
  --rotate: -3deg;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-24);
  aspect-ratio: 1;
  padding: var(--space-3);
  background: var(--color-hot-500);
  color: var(--ink-on-paper);
  text-align: center;
  transform: rotate(var(--rotate));
  filter: drop-shadow(var(--shadow-2)); /* drop-shadow()'s offset/blur/color grammar matches the shadow tokens directly */
  clip-path: polygon(50.0% 3.0%, 67.8% 13.1%, 88.3% 19.4%, 92.9% 40.2%, 98.7% 61.1%, 82.8% 76.2%, 70.8% 93.2%, 50.0% 95.0%, 28.3% 95.0%, 18.7% 74.9%, 4.2% 60.5%, 8.1% 40.4%, 11.7% 19.4%, 30.9% 10.4%);
}
.rcn-sticker:nth-of-type(3n+2) {
  clip-path: polygon(50.0% 5.0%, 71.7% 5.0%, 82.8% 23.8%, 96.8% 39.3%, 89.0% 58.9%, 88.3% 80.6%, 69.1% 89.6%, 50.0% 97.0%, 32.2% 86.9%, 10.9% 81.2%, 8.1% 59.6%, 3.2% 39.3%, 14.8% 21.9%, 31.8% 12.2%);
}
.rcn-sticker:nth-of-type(3n+3) {
  clip-path: polygon(50.0% 1.0%, 68.7% 11.3%, 86.7% 20.7%, 89.0% 41.1%, 98.7% 61.1%, 84.4% 77.4%, 70.0% 91.4%, 50.0% 100.0%, 31.8% 87.8%, 12.5% 79.9%, 10.0% 59.1%, 2.2% 39.1%, 14.8% 21.9%, 31.3% 11.3%);
}
/* Rotation jitter varied by nth-child, per the brief's own example range. */
.rcn-sticker:nth-of-type(3n+1) { --rotate: -4deg; }
.rcn-sticker:nth-of-type(3n+2) { --rotate: 3deg; }
.rcn-sticker:nth-of-type(3n+3) { --rotate: 6deg; }

/* Small serrated inner ring - alternating dash/gap built from a conic
   gradient, masked down to a thin annulus. Pure CSS, no extra markup. */
.rcn-sticker::before {
  content: '';
  position: absolute;
  inset: var(--space-2);
  border-radius: 50%;
  background: repeating-conic-gradient(var(--ink-on-paper) 0deg 4deg, transparent 4deg 8deg);
  -webkit-mask-image: radial-gradient(circle, transparent 60%, black 62%, black 68%, transparent 70%);
  mask-image: radial-gradient(circle, transparent 60%, black 62%, black 68%, transparent 70%);
  opacity: 0.5;
}

.rcn-sticker__text {
  position: relative;
  font-family: var(--font-stencil);
  font-weight: 700;
  font-variation-settings: 'opsz' var(--opsz-label), 'wght' 700;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  line-height: 1.1;
}

/* Semantic colour variants - each still pairs with the text string inside
   .rcn-sticker__text (e.g. "SOLD OUT"), never colour alone (§I). */
.rcn-sticker--urgent { background: var(--color-hot-500); }
.rcn-sticker--sold-out { background: var(--ink-3); color: var(--surface-0); }
.rcn-sticker--winner { background: var(--color-red-500); color: var(--ink-1); }


/* ---------------------------------------------------------------------------
   9.6 TORN DECAL EDGE (section divider)

   Direct replacement for the old site's soft rounded-blob SVG dividers
   (curve-singlea.svg/curve-singleb.svg) - this brief's single clearest
   "generic template" tell. Three stable zigzag variants, hand-authored once
   (computed offline for precise, non-wobbly geometry), rotated/flipped via
   transform for variety between sections rather than re-authored per use.
   --------------------------------------------------------------------------- */

.rcn-torn-edge {
  display: block;
  width: 100%;
  height: var(--space-8);
  background: var(--surface-0); /* set per-instance to match the surface being transitioned INTO */
}
@media (min-width: 960px) {
  .rcn-torn-edge { height: var(--space-12); }
}

.rcn-torn-edge--a {
  clip-path: polygon(0% 100%, 0.0% 3.9%, 6.3% 42.4%, 12.5% 1.7%, 18.8% 40.5%, 25.0% 3.7%, 31.3% 42.0%, 37.5% 1.5%, 43.8% 44.3%, 50.0% 4.5%, 56.3% 41.5%, 62.5% 2.7%, 68.8% 43.7%, 75.0% 4.9%, 81.3% 39.5%, 87.5% 4.0%, 93.8% 40.4%, 100.0% 5.0%, 100% 100%);
}
.rcn-torn-edge--b {
  clip-path: polygon(0% 100%, 0.0% 8.8%, 8.3% 52.6%, 16.7% 7.0%, 25.0% 53.9%, 33.3% 7.8%, 41.7% 50.6%, 50.0% 5.6%, 58.3% 50.3%, 66.7% 4.4%, 75.0% 50.2%, 83.3% 4.7%, 91.7% 54.8%, 100.0% 6.3%, 100% 100%);
}
.rcn-torn-edge--c {
  clip-path: polygon(0% 100%, 0.0% 1.7%, 5.0% 34.7%, 10.0% 0.2%, 15.0% 33.4%, 20.0% 0.0%, 25.0% 31.2%, 30.0% 3.7%, 35.0% 34.4%, 40.0% 4.2%, 45.0% 36.9%, 50.0% 0.7%, 55.0% 31.9%, 60.0% 1.6%, 65.0% 35.4%, 70.0% 0.8%, 75.0% 34.3%, 80.0% 0.8%, 85.0% 35.3%, 90.0% 3.4%, 95.0% 33.6%, 100.0% 3.9%, 100% 100%);
}

/* The clip-path above always cuts teeth into the TOP edge. --flip and
   --rotate reuse the same three shapes for the bottom edge / mirrored reads,
   rather than authoring six variants. Both are transforms, so they never
   trigger layout (§I). */
.rcn-torn-edge--flip { transform: scaleY(-1); }
.rcn-torn-edge--rotate { transform: rotate(180deg); }


/* ---------------------------------------------------------------------------
   9.7 HALFTONE DUST WASH (photo treatment)

   Unifying treatment for photography of inconsistent vintage/quality. Three
   layers on a .rcn-photo wrapper:
     1. duotone - a `filter: url(#...)` HOOK on the <img> itself. The actual
        SVG feColorMatrix <filter> definitions live in a template partial
        (per the build brief) - reference IDs are #rcn-duotone-dusk and
        #rcn-duotone-day. Until that partial is wired into a given page, the
        url() reference simply fails to resolve and the fallback filter
        chain below (a plain CSS approximation) is what actually paints -
        this is why the fallback is applied unconditionally rather than only
        via @supports (there is no reliable @supports test for "this specific
        SVG filter ID exists in the document").
     2. dot halftone - pure CSS, no partial dependency: a repeating-radial-
        gradient sized down to a small tile via background-size, which is
        the standard way to fake a dot GRID with a gradient that is
        otherwise a single set of concentric rings.
     3. fine grain - built self-contained as an inline feTurbulence SVG
        data-URI rather than depending on the template partial, so the grain
        layer works today even before that partial exists (more robust than
        the duotone hook, which genuinely needs the partial's colour-mapping
        logic and can't be faked as cheaply).
   --------------------------------------------------------------------------- */

.rcn-photo {
  position: relative;
  overflow: hidden;
  /* REQUIRED, not cosmetic. The grain and halftone layers below use
     mix-blend-mode. Without an isolation boundary here they blend against
     whatever is behind this container in the page - so the same photo would
     grain differently over --surface-0 than over --surface-1, and over another
     image it would blend with THAT image. `isolation: isolate` forces the blend
     to composite against the photo itself, which is the only correct backdrop. */
  isolation: isolate;
}
/* REQUIRED for every fixed-aspect photo box on the site, not cosmetic.

   `picture()` in lib/media.js emits <picture><source><img></picture>. A
   <picture> is an INLINE wrapper with no box of its own, so an `img { height:
   100% }` inside it resolves against the PICTURE's auto height - not against
   the fixed `aspect-ratio` box the design intends (.rcn-card__media,
   .rcn-highlights__media, .rcn-gallery__item ...). The percentage silently
   fell back to `auto` and every image rendered at its own natural aspect
   inside a correctly-sized box: a 4:3 card box measured a uniform 188px while
   the images inside it came out 114px, 250px and 357px.

   That is the "random image heights ... not uniform at all" defect reported
   on 28 Jul. It was misdiagnosed once as a stale-cache artefact, because
   measuring `.rcn-card__media` shows a perfectly uniform grid - the container
   was never the problem, the image inside it was. Giving <picture> a block box
   that fills its parent makes the img's own height:100%/object-fit:cover
   resolve as written, everywhere at once.

   Safe on auto-height parents too: height:100% against an auto-height
   container simply resolves back to auto, which is the current behaviour. */
.rcn-photo picture {
  display: block;
  width: 100%;
  height: 100%;
}
.rcn-photo img {
  /* Fallback "hot, sun-bleached print" approximation - see note above. */
  filter: grayscale(0.25) contrast(1.08) brightness(0.96) saturate(1.15);
}
/* The hero photograph is the one image on the site that should NOT be pulled
   toward the house treatment. It is the first three seconds and the whole point
   is that it looks like a real, vivid, sunlit event - the site-wide 25%
   desaturation reads as washed-out there rather than as a print treatment.
   Slightly warmer and more saturated instead, with a touch more contrast to hold
   up under the scrim. */
.rcn-hero .rcn-photo img,
.rcn-hero__media img {
  filter: contrast(1.12) saturate(1.18) brightness(0.98);
}
/* Filter ids must exist in views/partials/svg-defs.ejs. That partial defines
   exactly three: #rcn-duotone-dusk, #rcn-duotone-desert and #rcn-duotone-heat.
   The daytime modifier previously pointed at #rcn-duotone-day, which is not
   defined anywhere - an unresolvable filter reference makes the element render
   with NO filter at all in Chrome and Safari, silently, so the treatment simply
   never applied. Keep these two lists in sync. */
.rcn-photo--duotone-dusk img { filter: url(#rcn-duotone-dusk); }
.rcn-photo--duotone-day img,
.rcn-photo--duotone-desert img { filter: url(#rcn-duotone-desert); }
.rcn-photo--duotone-heat img { filter: url(#rcn-duotone-heat); }

/* Fine grain - feTurbulence baseFrequency 0.9 per §F, tiled at 140x140,
   applied at ~5% opacity (brief's 4-6% range). */
.rcn-photo::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='rcnGrain'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch' result='n'/%3E%3CfeColorMatrix in='n' type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23rcnGrain)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  mix-blend-mode: overlay;
  opacity: 0.05;
  pointer-events: none;
}

/* Dot halftone - repeating-radial-gradient rendered into an 8px tile
   (--space-2) and repeated, per §F's explicit build technique. Opacity in
   the brief's 6-10% range. */
.rcn-photo::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image: repeating-radial-gradient(circle at center, var(--ink-1) 0, var(--ink-1) 1px, transparent 1.4px, transparent 100%);
  background-size: var(--space-2) var(--space-2);
  mix-blend-mode: overlay;
  opacity: 0.08;
  pointer-events: none;
}


/* ---------------------------------------------------------------------------
   9.8 CONTOUR HORIZON

   Low-opacity ridge-line background layer - a geographic/landscape
   reference only, explicitly not any Aboriginal art motif or pattern (§I).
   The polyline embedded below is a PLACEHOLDER silhouette (same jagged-
   ridge technique the brief specifies) authored to ship a working device
   today; the final brief calls for hand-tracing the actual MacDonnell
   Ranges skyline from the brand's own photography, which is an asset/
   content task, not something this CSS file can do on its own - swap the
   data-URI (or point --contour-svg at a real traced SVG) once that trace
   exists. Built as a mask-image rather than a baked-in stroke colour so
   --contour-svg's colour/opacity stay fully token-driven via `color` and
   the element's own opacity, not hardcoded into the SVG itself.
   --------------------------------------------------------------------------- */

.rcn-contour {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: clamp(5rem, 12vw, 10rem);
  background-color: currentColor;
  color: var(--color-ochre-500);
  opacity: 0.2;
  pointer-events: none;
  -webkit-mask-image: var(--contour-svg, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 200' preserveAspectRatio='none'%3E%3Cpolyline points='0,126 32,123 64,113 96,116 128,122 160,126 192,120 224,135 256,128 288,120 320,112 352,114 384,107 416,92 448,99 480,97 512,111 544,110 576,128 608,136 640,139 672,143 704,163 736,164 768,148 800,151' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"));
  mask-image: var(--contour-svg, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 200' preserveAspectRatio='none'%3E%3Cpolyline points='0,126 32,123 64,113 96,116 128,122 160,126 192,120 224,135 256,128 288,120 320,112 352,114 384,107 416,92 448,99 480,97 512,111 544,110 576,128 608,136 640,139 672,143 704,163 736,164 768,148 800,151' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"));
  -webkit-mask-repeat: repeat-x;
  mask-repeat: repeat-x;
  -webkit-mask-size: auto 100%;
  mask-size: auto 100%;
  -webkit-mask-position: bottom left;
  mask-position: bottom left;
}
/* Slowest of the hero's three parallax layers (§G/§9.1). */
@supports (animation-timeline: scroll()) {
  .rcn-contour {
    animation: rcn-parallax-shift linear both;
    animation-timeline: scroll(root);
    animation-range: 0 100vh;
    --parallax-shift: calc(var(--parallax-max) * 0.4);
  }
}
.rcn-footer .rcn-contour { color: var(--ink-3); opacity: 0.15; }


/* ===========================================================================
   10. ACKNOWLEDGEMENT OF COUNTRY - DELIBERATE, PERMANENT EXCEPTION (§I)

   This section is a guardrail, not a design note. It is plain, high-contrast
   typography on a static, undecorated surface-0 - no motion, no photographic
   background, no §F device of any kind layered behind it. The !important
   declarations below are the ONE deliberate use of !important in this file,
   used precisely because this section must not be "improved" by a future
   utility class or device leaking in underneath it. The content itself is
   not this build team's to finalise - it is signed off by the client's
   community/cultural liaison and the Arrernte Traditional Owners via NTMEC.
   =========================================================================== */

.rcn-acknowledgement {
  position: static !important;
  background: var(--surface-0) !important;
  background-image: none !important;
  color: var(--ink-1);
  padding-block: var(--section-y);
  overflow: visible !important;
}
.rcn-acknowledgement,
.rcn-acknowledgement * {
  animation: none !important;
  transition: none !important;
  transform: none !important;
  clip-path: none !important;
  filter: none !important;
  mix-blend-mode: normal !important;
}
.rcn-acknowledgement::before,
.rcn-acknowledgement::after {
  content: none !important;
}
/* Belt-and-suspenders: if a §F device class is ever nested inside by
   mistake, it renders nothing rather than partially. */
.rcn-acknowledgement .rcn-smoke,
.rcn-acknowledgement .rcn-tacho,
.rcn-acknowledgement .rcn-stage,
.rcn-acknowledgement .rcn-sticker,
.rcn-acknowledgement .rcn-torn-edge,
.rcn-acknowledgement .rcn-contour,
.rcn-acknowledgement .rcn-photo::before,
.rcn-acknowledgement .rcn-photo::after {
  display: none !important;
}

.rcn-acknowledgement__body {
  max-width: 68ch;
}
.rcn-acknowledgement__body p {
  font-size: var(--text-body-lg);
  line-height: var(--leading-body-lg);
  color: var(--ink-1);
}


/* ===========================================================================
   11. INTER-SECTION RHYTHM FIXES  (added in the nav/rhythm pass)

   THE BUG: every top-level section block (`.rcn-section` / `.rcn-section--
   tight`, plus `.rcn-acknowledgement`, `.rcn-footer` and the `.rcn-ticker`
   marquee band) sets its OWN padding-block on both edges, written so the
   block looks correct dropped in on its own. But every page here actually
   stacks these as plain adjacent siblings with zero margin between them
   (confirmed with a Playwright DOM walk, not a guess), so the real on-page
   gap at every seam is the SUM of both blocks' padding - not the larger of
   the two. That sum is what read as "big unexplained voids" - e.g. the
   homepage's Acknowledgement-to-countdown seam measured 128px + 64px = 192px
   of dead space before this fix.

   THE FIX: at every seam, only the LARGER of the two declared paddings
   should survive - that's the rhythm the page was actually designed at,
   just applied twice. Three tiers exist, and the ordering below is stable at
   every breakpoint (checked against tokens.css's 960/1280 steps):
     ticker (12px, fixed)  <  "tight" (--section-y-tight: 48/64/64px)
                           <  "normal" (--section-y: 64/96/128px)
   Default: zero the FOLLOWING block's top padding, so the seam is carried by
   the PRECEDING block's bottom padding. That's correct whenever the
   preceding block's tier is >= the following block's tier. The one case
   it's wrong is a smaller block immediately followed by a larger one (tight
   before normal, or ticker before tight/normal) - there, zeroing the
   follower's top would under-space a block that deliberately asked for MORE
   room than what came before it, so instead the smaller PRECEDING block's
   bottom is zeroed and the larger follower keeps its own top padding.

   Deliberately does NOT touch any component's own internal CSS (ticket
   cards, card grids, forms, gallery, sponsor wall stay untouched per the
   build split) - every rule here only ever sets `padding-block-start` or
   `padding-block-end` on the generic section-level classes above.

   NOTE ON SPECIFICITY: these rules use `:is()`, not `:where()`. `:where()`
   always contributes zero specificity, which would make every rule below
   LOSE to the plain single-class base rules (`.rcn-section { padding-block:
   ... }` etc.) regardless of source order - specificity is compared before
   source order ever comes into it. `:is()` groups selectors the same way but
   keeps the specificity of whichever branch matches, so these rules (each at
   least two classes deep, since they're always "A + B") safely and
   predictably beat the one-class base rules. `.rcn-acknowledgement`'s own
   padding-block is `!important` (SS10, deliberate) and stays completely
   untouched either way - these rules only ever adjust the ADJACENT block's
   padding, never reach into the guarded section itself.
   =========================================================================== */

/* --- default: zero the follower's top padding -------------------------- */
:is(.rcn-section, .rcn-acknowledgement, .rcn-footer, .rcn-ticker)
+ :is(.rcn-section, .rcn-acknowledgement, .rcn-footer, .rcn-ticker) {
  padding-block-start: 0;
}

/* --- override: a smaller block immediately followed by a larger one ----
   Zero the smaller PRECEDING block's bottom instead, so the larger
   follower's own top padding (re-asserted below) is what the seam is
   measured in. */
:is(.rcn-ticker, .rcn-section--tight):has(
  + :is(.rcn-acknowledgement, .rcn-footer, .rcn-section:not(.rcn-section--tight))
) {
  padding-block-end: 0;
}
.rcn-ticker:has(+ .rcn-section--tight) {
  padding-block-end: 0;
}

/* Re-assert the follower's top padding for the two overridden pairings
   above - the default rule already zeroed it, this restores it since these
   are exactly the cases where the follower's own (larger) value must win. */
:is(.rcn-section--tight, .rcn-ticker)
+ :is(.rcn-acknowledgement, .rcn-footer, .rcn-section:not(.rcn-section--tight)) {
  padding-block-start: var(--section-y);
}
.rcn-ticker + .rcn-section--tight {
  padding-block-start: var(--section-y-tight);
}


/* ===========================================================================
   11. CLIENT GLOBALS (NitroBoards cards, 2026-07-27)

   Everything below implements the GLOBAL MENU NAVIGATION, GLOBAL FOOTER
   NAVIGATION, GLOBAL SPONSOR LOGOS and General Build rules cards. Values
   come from tokens.css only - if a value you need isn't there, add it THERE
   with a comment, don't inline it (CLAUDE.md rule 6).
   =========================================================================== */

/* --- 11.1 Header: presented-by lockup + search ------------------------- */

/* LMCT+ rides next to the RCN12 mark ("LMCT+ - HEADER AND FOOTER"). Hidden
   below the desktop breakpoint - at phone widths the bar has room for the
   logo, CTA and hamburger only, and LMCT+ keeps its contractual footer spot
   on every viewport. */
.rcn-header__presented {
  display: none;
  align-items: center;
  gap: var(--space-2);
  padding-inline: var(--space-3);
  border-inline-start: 1px solid var(--border-subtle);
  text-decoration: none;
  /* Card "Header- Desktop", 5 Aug: the mark had gone missing. It was never
     missing from the DOM or broken as an asset - measured render was
     1.15 x 0.23px. This is the exact .rcn-header__logo bug (SS8, "the
     logo's flex item is a <picture>, an inline element with no intrinsic
     width, so flexbox is free to shrink it to nothing") except that fix was
     only ever applied to the RCN mark. This item never got the same
     protection and had nothing stopping it collapsing under the header's
     flex layout. Same fix, same reasoning, this element only. */
  flex: 0 0 auto;
}
@media (min-width: 1280px) {
  .rcn-header__presented { display: flex; }
}
.rcn-header__presented-label {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--ink-3);
  white-space: nowrap;
}
.rcn-header__presented picture { flex: 0 0 auto; }
.rcn-header__presented img { width: var(--lmct-logo-w); height: auto; }

.rcn-header__search {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--target-min);
  height: var(--target-min);
  color: var(--ink-2);
  transition: color var(--duration-fast) var(--ease-throttle);
}
.rcn-header__search:hover,
.rcn-header__search:focus-visible,
.rcn-header__search[aria-current="page"] { color: var(--ink-1); }

/* --- 11.1b Header cart (Rick, 6 Aug 2026) -------------------------------
   Sits immediately right of the search icon, same 44px target box and same
   ink treatment, so the two read as one utility cluster rather than two
   unrelated glyphs. The count bubble only exists in the markup when the cart
   holds something - an empty badge showing "0" is noise, and the icon alone
   already says "cart". */

/* flex: 0 0 auto on BOTH icons. Adding a second control to the bar gave the
   flex row one more thing to shrink, and at 390px it took search and cart down
   to 32px - under the 44px WCAG 2.2 target minimum these boxes exist to meet.
   The row still fits at 320px with both pinned (measured), so nothing overflows
   in exchange. Search is pinned here rather than left as it was because this
   change is what shrank it. */
.rcn-header__search,
.rcn-header__cart,
.rcn-hamburger { flex: 0 0 auto; }

/* Search + cart as one unit (Sopphia, 6 Aug 2026). As separate children of
   .rcn-header__inner they were subject to its `justify-content: space-between`
   and sat ~60px apart; grouped, the row spaces the GROUP and this gap governs
   what's inside it.

   --space-4 (16px) rather than a literal 15px: the request was "15px or right
   next to each other", and the spacing scale's nearest step is one pixel off
   and indistinguishable. A raw 15px would be the only hard-coded spacing value
   in the stylesheet (§6 of CLAUDE.md) for no visible gain. */
.rcn-header__utils {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex: 0 0 auto;
}

.rcn-header__cart {
  position: relative; /* the badge is positioned against this box */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--target-min);
  height: var(--target-min);
  color: var(--ink-2);
  transition: color var(--duration-fast) var(--ease-throttle);
}
.rcn-header__cart:hover,
.rcn-header__cart:focus-visible,
.rcn-header__cart[aria-current="page"] { color: var(--ink-1); }

/* red-600, not red-500. The palette's own note is explicit that red-600 is the
   functional red for solid fills carrying body-size text: ink-1 on red-500
   measures 3.7:1, which fails AA at this size (the badge is ~12px bold, well
   under the 18.66px large-text threshold). On red-600 it measures 5.1:1. */
.rcn-header__cart-count {
  position: absolute;
  /* Pulled in from the corners of the 44px box: the icon artwork is only
     ~20px, so an unindented badge would float in empty space away from it. */
  inset-block-start: var(--space-1);
  inset-inline-end: var(--space-1);
  min-width: var(--badge-size);
  height: var(--badge-size);
  padding-inline: calc(var(--space-1) / 2);
  border-radius: var(--badge-size); /* pill, so a 2-digit count stays enclosed */
  background: var(--color-red-600);
  color: var(--ink-1);
  font-family: var(--font-body);
  font-weight: 800;
  font-size: var(--text-micro);
  line-height: var(--badge-size);
  text-align: center;
  /* Separates the bubble from the icon strokes it overlaps. Matches whatever
     the header bar currently is - it swaps to surface-2 once scrolled, and a
     surface-1 ring on a surface-2 bar reads as a halo. */
  box-shadow: 0 0 0 2px var(--surface-1);
}
.rcn-header.is-scrolled .rcn-header__cart-count {
  box-shadow: 0 0 0 2px var(--surface-2);
}

/* --- 11.2 Hero meta kicker (BANNER - TOP LINE INFO) --------------------- */

.rcn-hero__meta {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  /* Ink, not brand red: the landing heroes sit on photography that is
     frequently red (burnout smoke, desert rock), and red-300 text over red
     smoke measured illegible in review. Size/weight already read as kicker. */
  color: var(--ink-1);
  margin-block-end: var(--space-2);
}

/* --- 11.3 Sponsor carousel (footer band) -------------------------------- */

.rcn-partner-band {
  background: var(--surface-1);
  border-block-start: 1px solid var(--border-subtle);
  padding-block: var(--space-10) 0;
}
.rcn-partner-band__eyebrow {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  font-style: italic;
  color: var(--ink-3);
  margin: 0;
}
.rcn-partner-band__heading {
  font-family: var(--font-display);
  font-size: var(--text-h4);
  text-transform: uppercase;
  color: var(--ink-1);
  margin-block: var(--space-1) var(--space-6);
}

/* Same [data-marquee] machinery as the stat ticker (SS8.3): authored once,
   duplicated by motion.js, animation gated on .is-marquee-active so the
   no-JS/pre-JS/reduced-motion state is a static wrapped grid - movement
   removed, never information (the logos and links are all still there). */
.rcn-partner-marquee {
  overflow: hidden;
  padding-block: var(--space-8);
}
.rcn-partner-marquee__track {
  display: flex;
  flex-wrap: wrap;            /* static fallback state: wrap and show all */
  align-items: center;
  justify-content: center;
  gap: var(--space-10);
  padding: 0;
  margin: 0;
  list-style: none;
}
.rcn-partner-marquee.is-marquee-active .rcn-partner-marquee__track {
  flex-wrap: nowrap;
  width: max-content;
  justify-content: flex-start;
  animation: rcn-ticker-scroll var(--marquee-duration, var(--duration-marquee)) linear infinite;
}
.rcn-partner-marquee:hover .rcn-partner-marquee__track,
.rcn-partner-marquee:focus-within .rcn-partner-marquee__track {
  animation-play-state: paused;   /* logos are links - let people aim */
}
.rcn-partner-marquee__item { flex: 0 0 auto; }
.rcn-partner-marquee__link {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--target-min);
}
/* FULL COLOUR (client card "Sponsor Logos need to be in full colour", Ella,
   11 Aug 2026: "Despite the fact that all sponsor logos on the previous
   website were mono (white), we need to ensure all sponsor logos are in full
   colour.")

   This reverses the 28 Jul direction, which was implemented here as
   `filter: brightness(0) invert(1)` - flatten each mark to its alpha
   silhouette, then lift it to white. That filter is now gone, along with the
   per-mark --logo-filter escape hatch that existed only to stop it destroying
   Aeroflow and Lasseters. The `opacity: .92` went with it: it was there to
   settle a wall of identical white marks into the band, and dimming a
   sponsor's own colour artwork is not ours to do.

   Height scales per mark via --logo-scale (set inline by footer.ejs from the
   manifest aspect): one fixed cap height made squarish badges (Shannons,
   Lasseters) tiny in area next to long wordmarks, so each mark's height is
   normalised toward equal ink AREA instead, +/-30% around the base token -
   which is now 30% larger, per the same card. */
.rcn-partner-marquee__item img {
  height: calc(var(--partner-logo-h) * var(--logo-scale, 1));
  width: auto;
  max-width: 260px;
  object-fit: contain;
}

/* Paper chip, reinstated with the colour treatment. Three marks cannot carry
   themselves on the dark band: Arid Offroad and Dreamedia are
   dark-on-transparent, and RCTP was supplied as a CMYK JPEG with an opaque
   white background baked in. A light chip is the ONE adjustment available,
   because sponsor marks are never recoloured or knocked out.

   Sized off the same logo height so the padding stays proportional as the
   band scales, and given a hair of extra height so a wordmark with
   descenders is not pinched against the chip's edge. */
.rcn-partner-marquee__item--chip {
  background: var(--surface-paper);
  border-radius: var(--radius-plate);
  padding: var(--space-2) var(--space-3);
}

/* --- 11.4 Footer event-family row --------------------------------------- */

/* Stacked, matched-width (28 Jul client direction, repeated - "stack them
   vertically and make them the same width as each other"). The previous rule
   fixed a shared HEIGHT and let width float with each mark's native aspect,
   which is what made Tourism NT ("Why is the northern territory logo so
   small?") read as tiny next to Summernats' much wider wordmark at the same
   height - a squarer badge at equal height is a lot less wide. Fixing the
   width instead and letting height float the same way LMCT+ already worked
   (SS11.12, now folded into this shared rule) makes all three occupy the same
   footprint regardless of shape. */
.rcn-footer__family {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
  padding: 0;
  margin: 0;
  list-style: none;
}
.rcn-footer__family-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--target-min);
}
.rcn-footer__family img {
  width: var(--footer-family-logo-w);
  height: auto;
  object-fit: contain;
  opacity: 0.85;
  transition: opacity var(--duration-fast) var(--ease-throttle);
}
.rcn-footer__family-link:hover img,
.rcn-footer__family-link:focus-visible img { opacity: 1; }

/* --- 11.5 Pop-ups (subscribe / contact - ActiveCampaign) ---------------- */

.rcn-popup {
  width: min(560px, calc(100vw - var(--space-8)));
  max-height: min(85vh, 720px);
  background: var(--surface-paper);   /* AC forms are built for light UIs */
  color: var(--ink-on-paper);
  border: none;
  border-radius: var(--radius-md);
  padding: 0;
  box-shadow: var(--shadow-modal);
}
.rcn-popup::backdrop {
  background: rgba(13, 11, 10, 0.72); /* --surface-0 at depth; ::backdrop can't read custom props in all engines */
}
.rcn-popup__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-6) var(--space-2);
}
.rcn-popup__title {
  font-family: var(--font-display);
  font-size: var(--text-h4);
  text-transform: uppercase;
  color: var(--ink-on-paper);
  margin: 0;
}
.rcn-popup__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--target-min);
  height: var(--target-min);
  color: var(--ink-on-paper);
  border-radius: var(--radius-sm);
}
.rcn-popup__close:focus-visible { box-shadow: var(--focus-ring-paper); outline: none; }
.rcn-popup__body {
  padding: var(--space-2) var(--space-6) var(--space-6);
  overflow-y: auto;
}
/* Until the AC embed script has rendered the form, the hosted-form link is
   the body's honest content; once loaded it becomes a quiet escape hatch. */
.rcn-popup.is-form-loaded .rcn-popup__fallback {
  font-size: var(--text-micro);
  opacity: 0.7;
}

/* --- 11.6 Program table + filters ---------------------------------------- */

.rcn-program-table__controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-block-end: var(--space-8);
}
.rcn-program-table__controls .rcn-field { flex: 0 1 220px; }
.rcn-program-table__controls .rcn-field--grow { flex: 1 1 260px; }

.rcn-program-table__day { margin-block-end: var(--space-8); }
.rcn-program-table__day[hidden] { display: none; }

.rcn-program-table__table {
  width: 100%;
  border-collapse: collapse;
}
.rcn-program-table__caption {
  text-align: left;
  font-family: var(--font-display);
  font-size: var(--text-h4);
  text-transform: uppercase;
  color: var(--color-red-300);
  padding-block-end: var(--space-3);
}
.rcn-program-table__table th {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-micro);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--ink-3);
  text-align: left;
  padding: var(--space-2) var(--space-4) var(--space-2) 0;
  border-bottom: 1px solid var(--border-strong);
}
.rcn-program-table__table td {
  padding: var(--space-3) var(--space-4) var(--space-3) 0;
  border-bottom: 1px solid var(--border-subtle);
  font-size: var(--text-small);
  color: var(--ink-2);
  vertical-align: top;
}
.rcn-program-table__time {
  white-space: nowrap;
  font-variant-numeric: tabular-nums; /* Archivo's verified tnum - columns align */
  color: var(--ink-1);
}
.rcn-program-table__name { color: var(--ink-1); font-weight: 600; }
.rcn-program-table__table tr[hidden] { display: none; }
.rcn-program-table__empty { color: var(--ink-2); margin-block-end: var(--space-8); }

/* The programme name is a link to its own event page where one exists
   (client card "10-5"). Underlined rather than colour-only: this sits inside a
   dense table of similar-looking text, and colour alone is not a reliable
   signal there - especially for anyone who cannot separate the two hues. */
.rcn-program-table__link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--color-red-400);
  text-underline-offset: 3px;
}
.rcn-program-table__link:hover,
.rcn-program-table__link:focus-visible { color: var(--color-red-300); }

/* PHONES: the programme gets the room.

   Client card "10-4", 3 Aug 2026: "Can you please resize table's text for
   mobile view? The Time and Venue are visible, but not the actual program
   element and we want to minimise scrolling left to right."

   She is describing the 560px minimum below, which forced the table wider than
   a 393px screen. Time and Venue held the visible part and the Programme
   column - the event name, the only reason anyone opens this page - sat off to
   the right until you dragged.

   The minimum width is gone and the columns are proportioned instead: the
   programme name takes half the row, the two supporting facts share the rest.
   Time stays `nowrap` so a time never breaks across lines; the venue is
   allowed to wrap, which costs a little height and saves all of the sideways
   scrolling. Nothing is hidden - every column still carries its data.

   The container keeps `overflow-x: auto` as a safety net for a freakishly long
   venue name, but it should now never be needed. */
@media (max-width: 639px) {
  .rcn-program-table__day { overflow-x: auto; }
  .rcn-program-table__table { table-layout: fixed; width: 100%; }
  .rcn-program-table__table th,
  .rcn-program-table__table td { font-size: var(--text-micro); }
  .rcn-program-table__time { width: 27%; padding-inline-end: var(--space-2); }
  .rcn-program-table__venue { width: 26%; padding-inline-end: var(--space-2); }
  .rcn-program-table__name { width: 47%; font-size: var(--text-small); }
}

/* --- 11.7 Campaign panels (MREC/banner rotator) -------------------------- */

/* No-JS/pre-JS state: all panels stacked and visible (server render).
   Only once JS is definitely running (html.js-ready, the same gate as every
   reveal) does the stage collapse to a single-panel rotator - a failed
   script must never leave creative unreachable. */
html.js-ready .rcn-campaign__stage { position: relative; }
html.js-ready .rcn-campaign__panel { display: none; }
html.js-ready .rcn-campaign__panel[data-campaign-active] { display: block; }

.rcn-campaign__panel {
  display: block;
  border-radius: var(--radius-md);
  overflow: hidden;
}
.rcn-campaign__panel + .rcn-campaign__panel { margin-block-start: var(--space-4); }
html.js-ready .rcn-campaign__panel + .rcn-campaign__panel { margin-block-start: 0; }

.rcn-campaign__dots {
  display: flex;
  justify-content: center;
  gap: var(--space-2);
  margin-block-start: var(--space-4);
}
.rcn-campaign__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--border-strong);
  transition: background var(--duration-fast) var(--ease-throttle), transform var(--duration-fast) var(--ease-throttle);
}
.rcn-campaign__dot[aria-current="true"] {
  background: var(--color-red-500);
  transform: scale(1.2);
}
/* Dots are tiny by nature; pad the hit area up to target size invisibly. */
.rcn-campaign__dot { position: relative; }
.rcn-campaign__dot::before {
  content: '';
  position: absolute;
  inset: calc((var(--target-min) - 10px) / -2);
}

/* --- 11.8 Search page ----------------------------------------------------- */

.rcn-search__form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-end: var(--space-8);
}
.rcn-search__input { flex: 1 1 280px; }
.rcn-search__count { color: var(--ink-2); margin-block-end: var(--space-6); }
.rcn-search__results {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--space-6);
}
.rcn-search__result-title {
  font-family: var(--font-display);
  font-size: var(--text-h5);
  text-transform: uppercase;
  margin: 0;
}
.rcn-search__result-title a { color: var(--ink-1); }
.rcn-search__result-title a:hover,
.rcn-search__result-title a:focus-visible { color: var(--color-red-300); }
.rcn-search__result-path {
  font-size: var(--text-micro);
  color: var(--ink-3);
  margin-block: var(--space-1);
}
.rcn-search__result-snippet {
  color: var(--ink-2);
  font-size: var(--text-small);
  margin: 0;
  max-width: 70ch;
}

/* --- 11.9 Card grid: headings, blurbs, coming-soon ----------------------- */

.rcn-card-grid__heading { margin-block-end: var(--space-6); }
.rcn-card__text {
  color: var(--ink-2);
  font-size: var(--text-small);
  margin-block-start: var(--space-2);
}
/* The 3-line title reservation (SS8.14) exists to stop RAGGED ROWS in the
   image discipline grid, where the title is the only text. A hub card's
   height is driven by its blurb, so reserving three empty title lines just
   opens a void between title and blurb. */
.rcn-card--no-image .rcn-card__title { min-height: 0; }
.rcn-card__tag {
  display: inline-block;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-micro);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--ink-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: 2px var(--space-2);
  margin-block-start: var(--space-3);
}
.rcn-card--pending { opacity: 0.75; }
.rcn-card__link--pending { cursor: default; }

/* --- 11.10 FAQ groups: anchored headings + hand-off link ------------------ */

.rcn-faq__group-heading {
  margin-block-end: var(--space-6);
  /* Anchored from landing pages (/faq#spectators) - keep the sticky header
     from sitting on top of the heading it just scrolled to. */
  scroll-margin-top: calc(var(--header-h) + var(--space-6));
}
.rcn-faq__more { margin-block-start: var(--space-6); }

.rcn-arrow-link {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--color-red-300);
}
.rcn-arrow-link::after { content: ' \2192'; }
.rcn-arrow-link:hover,
.rcn-arrow-link:focus-visible { color: var(--ink-1); }

/* --- 11.11 Ticker heading (FOUR MASSIVE DAYS band) ------------------------ */

.rcn-ticker-heading { padding-block: var(--section-y-tight) var(--space-4); }
.rcn-ticker-heading .rcn-h4 { margin: 0; }

/* --- 11.12 LMCT+ presenting-partner sizing (client direction 2026-07-27) ---
   LMCT+ appears in three places: the header lockup, the footer "Brought to you
   by" row, and the sponsor carousel. In the header it is sized from its own
   token so it can be more prominent there without inflating the other 14
   sponsor logos elsewhere. In the footer it now shares SS11.5's width with
   Summernats and Tourism NT (28 Jul direction: match their widths) rather than
   getting its own - `--lmct-logo-w-footer` is no longer read here. In the
   carousel it stays a peer of the others - that strip is a level playing field
   and one logo towering over the rest would misrepresent the sponsorship
   tiers. */

/* --- 11.13 HERO VIDEO (client card "Homepage- Hero banner", 2026-07-27) ---
   Sits over the hero still inside the same parallax layer, so it inherits the
   layer's transform and needs no parallax wiring of its own.

   Hidden until motion.js has decided it may play AND the first frame is
   actually ready (`is-playing`). Without that second condition the video pops
   in as a black rectangle over a perfectly good photograph for however long
   the first frames take to arrive. Fading in also hides the seam at the loop
   point, which is a hard cut back to frame one.

   No `html.js-ready` gate is needed here: the element is opacity 0 by default
   and only ever revealed BY script, so a script that never runs simply leaves
   the still showing - the correct outcome, not a blank hero. */
.rcn-hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  /* NO TRANSITION (Rick, 6 Aug 2026: "I can see the image underneath the
     video... we definitely cannot see the fallback image").

     This used to cross-fade over --duration-slow. Two stacked opaque layers
     cannot be cross-faded without both being visible at once - for those
     600ms the video was genuinely semi-transparent and the still underneath
     genuinely showed through. That IS the reported transparency; the video's
     own opacity was already 1 and was never the cause. A hard cut is the only
     way to guarantee the still is never seen through the video, and it is
     what was asked for. */
  pointer-events: none;
}
.rcn-hero__video.is-playing { opacity: 1; }

/* Belt and braces on the same instruction: once the video is up, take the
   still out of the paint entirely, so no future change to opacity, blend mode
   or stacking can put it back on screen through the video.

   `visibility`, not `display`: the <img> keeps its box, so it goes on holding
   the hero's height and there is no reflow at the swap. Scoped to the playing
   state, so with JS off, autoplay refused, or reduced motion (where the video
   is display:none) the still is untouched and still carries the hero. */
.rcn-hero__media:has(.rcn-hero__video.is-playing) img { visibility: hidden; }

@media (prefers-reduced-motion: reduce) {
  /* Belt and braces - motion.js already refuses to start it. */
  .rcn-hero__video { display: none; }
}

/* --- 11.14 Countdown heading + intro copy -------------------------------
   The briefing places three paragraphs under the clock; they were missing
   from the first build (client card, 2026-07-27). Centred to match the clock
   above them rather than inheriting the now-full-width prose default, because
   a centred numeral row above a hard-left paragraph reads as two unrelated
   sections rather than one. */
.rcn-countdown__heading { text-align: center; margin-block-end: var(--space-6); }
.rcn-countdown__intro {
  margin-block-start: var(--space-8);
  margin-inline: auto;
  max-width: 76ch;
  text-align: center;
  /* SportsCLOUD feedback 2026-07-30: this copy was too low-contrast to read.
     It inherited .rcn-prose's --ink-2 (secondary), which is the right weight
     for a wall of prose on a plain surface but not for three short paragraphs
     sitting under the odometer, where the section's warm background bruises
     lift the local luminance and eat the difference. --ink-1 is the site's
     white, so this stays a token, not a one-off #fff. */
  color: var(--ink-1);
}
/* .rcn-prose p sets --ink-2 on the paragraphs themselves, so recolouring only
   the container above changes nothing visible. */
.rcn-countdown__intro p { color: var(--ink-1); }

/* --- 11.15 NATIVE CONTACT FORM (footer pop-up) --------------------------
   Replaces the ActiveCampaign embed the client rejected as "not styled
   properly... looking funky". Built in our own markup so it inherits the
   site's own field treatment instead of a third party's. */

/* The <dialog> element is not centred by default in every engine once it has
   an explicit width - it was pinning to the left edge. `inset: 0` + auto
   margins centres it in the viewport reliably. */
.rcn-popup {
  inset: 0;
  margin: auto;
}

/* Honeypot. MUST be invisible to people and still submitted by a bot, so it
   cannot use display:none or hidden - some bots skip those, and some browsers
   skip them on autofill. Pull it out of flow and out of the a11y tree instead.
   It was rendering as a visible "Company" field, which is exactly the sort of
   thing that makes a form look broken. */
.rcn-form__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.rcn-form { display: grid; gap: var(--space-4); }
.rcn-form__row { display: grid; gap: var(--space-4); }
@media (min-width: 560px) {
  .rcn-form__row { grid-template-columns: 1fr 1fr; }
}

/* Field treatment for a PAPER surface. The base .rcn-field__input is built for
   the dark site and renders as a near-black box on this light panel. */
.rcn-popup .rcn-field__label { color: var(--ink-on-paper); font-weight: 700; }
.rcn-popup .rcn-field__input,
.rcn-popup .rcn-field__textarea {
  background: #fff;
  color: var(--ink-on-paper);
  border: 1.5px solid var(--border-paper);
}
.rcn-popup .rcn-field__input::placeholder,
.rcn-popup .rcn-field__textarea::placeholder { color: rgba(26, 21, 18, 0.45); }
.rcn-popup .rcn-field__input:focus-visible,
.rcn-popup .rcn-field__textarea:focus-visible {
  outline: none;
  border-color: var(--color-red-600);
  box-shadow: var(--focus-ring-paper);
}
.rcn-popup .rcn-field__message { color: rgba(26, 21, 18, 0.62); }

.rcn-form__error {
  color: var(--color-red-700);
  background: rgba(231, 62, 41, 0.08);
  border: 1px solid rgba(231, 62, 41, 0.3);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  font-weight: 600;
}

.rcn-form__done { display: grid; gap: var(--space-5); justify-items: start; }
.rcn-form__done p { color: var(--ink-on-paper); }

/* Any author `display` declaration beats the UA stylesheet's
   `[hidden] { display: none }`, so `.rcn-form__done { display: grid }` was
   showing the confirmation message alongside the form it is meant to replace.
   Re-assert it. This is a general trap, so it is declared for the whole site
   rather than just this one component. */
[hidden] { display: none !important; }

/* --- 11.16 Prize pool (block: prizePool) --------------------------------
   Full prize-money breakdown, views/blocks/prizePool.ejs. Reuses §8.5's
   .rcn-prize__header (heading + intro framing, never a hero number here -
   see the template comment) and .rcn-prize__table/.rcn-prize__amount (the
   same results-board table look and tabular-nums treatment prizeTable.ejs
   uses) rather than duplicating them. Everything below is new: the group
   wrapper, the per-subgroup table caption, the list-layout chip grid, the
   group total stat and the PDF hand-off link.

   Merge note: written to a standalone file per instructions, to be folded
   into site.css by hand - do not treat this file itself as something the
   server serves.
   -------------------------------------------------------------------------- */

.rcn-prize-pool__groups { --stack-gap: var(--space-16); }

.rcn-prize-pool__group-head { margin-block-end: var(--space-2); }

.rcn-prize-pool__group-name { margin: 0; }

/* The one-line stat under a group's name (e.g. "$12,000 prize pool") - an
   eyebrow-weight tag rather than a second heading, so it reads as a caption
   on the h3 rather than competing with it. */
.rcn-prize-pool__group-subtitle {
  margin: var(--space-1) 0 0;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--color-ochre-300);
}

/* A group-level caveat (e.g. "Presented under Motorsport Australia permit
   ..."). Sits between the heading and the breakdown itself, so it reads as
   framing for the figures that follow rather than a footnote nobody
   scrolls back up for. */
.rcn-prize-pool__note {
  margin: var(--space-2) 0 var(--space-6);
  font-size: var(--text-small);
  color: var(--ink-3);
}

/* Multiple subgroup tables inside one group (Dirt Drags ships four) get
   normal stack rhythm between them; the caption below is what separates
   them visually, this is just the vertical gap. */
.rcn-prize-pool__group > .rcn-prize-pool__table-wrap + .rcn-prize-pool__table-wrap,
.rcn-prize-pool__group > .rcn-prize-pool__table-wrap ~ .rcn-prize-pool__chips {
  margin-block-start: var(--space-6);
}

.rcn-prize-pool__table-wrap { overflow-x: auto; } /* keyboard/touch scroll instead of squeezed columns - matches §8.5 */
.rcn-prize-pool__table-wrap + .rcn-prize-pool__table-wrap { margin-block-start: var(--space-6); }

/* Visible subgroup caption (Dirt Drags' "Standard Four Wheel Drive" etc).
   When a subgroup has no name the template gives the <caption> .sr-only
   instead of this class, so the group's own h3 is the only visible label -
   no duplicate heading stacked directly above a single table. */
.rcn-prize-pool__table-caption {
  caption-side: top;
  text-align: left;
  padding-block-end: var(--space-3);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--ink-2);
}

/* Secondary line under an amount cell (e.g. "+ Summernats 39 Pro Burnout
   Series Grand Final Qualifier Ticket"). Block-level so it wraps onto its
   own line under the figure rather than crowding the tabular-nums amount;
   never bold, never tabular-nums - it is prose, not a number, and must not
   be mistaken for a second dollar figure. */
.rcn-prize-pool__row-note {
  display: block;
  margin-block-start: var(--space-1);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--text-micro);
  line-height: var(--leading-small);
  color: var(--ink-3);
  text-align: end; /* follows the amount column's own right-alignment (§8.5 :last-child rule) */
}

/* --- List-layout groups (award names only, e.g. Bike Awards "Top 10") --- */

.rcn-prize-pool__subgroup-heading {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--ink-2);
  margin: 0 0 var(--space-3);
}

.rcn-prize-pool__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: 0;
  margin: 0;
  list-style: none;
}

.rcn-prize-pool__chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--surface-1);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  color: var(--ink-1);
}

.rcn-prize-pool__chip-note {
  font-weight: 400;
  font-size: var(--text-micro);
  color: var(--ink-3);
}

/* --- Per-group total ------------------------------------------------------
   A standalone stat below the group's table(s)/chips rather than a table
   footer row - total_amount is one figure for the whole group (Dirt Drags'
   four subgroup tables share one total), and list-layout groups have no
   table to attach a <tfoot> to at all. The left accent reuses the same
   device as .rcn-split__path's entrant/spectator border (§8.6), scaled down
   to a stat card rather than a full-bleed photo panel. */
.rcn-prize-pool__total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-block-start: var(--space-6);
  padding: var(--space-4) var(--space-6);
  background: var(--surface-2);
  border-inline-start: 4px solid var(--color-red-500);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-1);
}

.rcn-prize-pool__total-label {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--ink-3);
}

/* .rcn-odometer (§4) sets the display face/size; this only trims its
   line-height back down to 1 inside the flex row and keeps it right-set
   against the label, matching the table amount column's own alignment. */
.rcn-prize-pool__total-amount {
  font-size: var(--text-h4);
  color: var(--color-red-300);
}

/* --- PDF hand-off link ----------------------------------------------------
   Deliberately plain, not styled as a button - this is a secondary path
   alongside the interactive breakdown above it, not a competing CTA. */
.rcn-prize-pool__doc-link {
  margin-block-start: var(--space-10);
  text-align: center;
}
.rcn-prize-pool__doc-link a {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--color-red-300);
}
.rcn-prize-pool__doc-link a::before { content: '\2913 '; } /* download glyph, decorative - aria-hidden not needed on a ::before pseudo-element */
.rcn-prize-pool__doc-link a:hover,
.rcn-prize-pool__doc-link a:focus-visible { color: var(--ink-1); }

/* --- 11.17 Interactive checklist --- */
/* views/blocks/checklist.ejs + motion.js SECTION 7b [data-checklist].
   Scrutineering-queue checklist: entrant, one hand, phone, outdoor sun,
   replacing a 4-page PDF. Every rule below either serves that (big tap
   targets, tight scan-list rhythm) or the no-JS/print requirements from the
   brief - see the inline comments for which.

   No .js-ready gating needed anywhere here: nothing in this component starts
   hidden or offset pending JS (the outer data-reveal wrapper reuses the
   site-wide reveal-on-enter rules, which already own their own
   html.js-ready scoping elsewhere in this file - see SECTION 3). */

.rcn-checklist__header .rcn-prose { margin-block-start: var(--space-3); }

/* Toolbar: live progress text + Reset. Wraps on narrow phones rather than
   compressing either control below --target-min. */
.rcn-checklist__toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding-block: var(--space-3);
  border-block: 1px solid var(--border-subtle);
}
.rcn-checklist__progress {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-body);
  color: var(--ink-1);
  font-variant-numeric: tabular-nums; /* "7 of 24" - digits shouldn't reflow the row as the count changes */
}
.rcn-checklist__reset { flex-shrink: 0; }

/* One list per section, tight top border so the first row's tap target
   doesn't read as floating in space; each item gets its own bottom rule so a
   long list still scans as discrete rows, not one grey wall. */
.rcn-checklist__section-heading { margin-block: var(--space-8) var(--space-2); }
.rcn-checklist__list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-block-start: 1px solid var(--border-subtle);
}
.rcn-checklist__item { border-block-end: 1px solid var(--border-subtle); }

/* The <label> IS the tap target (wraps the checkbox - see the template
   comment on why for+id are also set). rcn-tap-target on it guarantees the
   WCAG 44px minimum even though the visual checkbox glyph is smaller - the
   whole row is clickable, not just the little square, which matters one-
   handed in direct sun. */
.rcn-checklist__row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding-block: var(--space-3);
  cursor: pointer;
}

.rcn-checklist__checkbox {
  flex-shrink: 0;
  width: var(--space-6);
  height: var(--space-6);
  margin-block-start: 2px; /* optical alignment with the first line of label text, not layout-affecting */
  accent-color: var(--color-success-500); /* ticking an item off is a completion state, not a brand-red action */
  cursor: pointer;
}

.rcn-checklist__label-text {
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: var(--leading-body);
  color: var(--ink-1);
  transition: color var(--duration-fast) var(--ease-throttle);
}
/* Checked-row treatment: dims + strikes the label so a glance down the list
   shows what's left, not just what's done. Progressive - browsers without
   :has() simply keep the label at full weight, which is still a fully
   correct (if less scannable) checklist, so no fallback branch is needed. */
.rcn-checklist__row:has(.rcn-checklist__checkbox:checked) .rcn-checklist__label-text {
  color: var(--ink-3);
  text-decoration: line-through;
  text-decoration-color: var(--border-strong);
}

/* Per-item detail: native <details>/<summary>, indented to sit under the
   label text rather than under the checkbox, so the row's tap target and the
   disclosure control never visually overlap. */
.rcn-checklist__details {
  padding-inline-start: calc(var(--space-6) + var(--space-3));
  padding-block-end: var(--space-3);
}
.rcn-checklist__summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--target-min); /* the disclosure control is its own interactive target */
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--color-red-300);
  cursor: pointer;
  list-style: none; /* kill the default triangle; a custom one is drawn below so its rotation can be animated as a transform, never a layout property */
}
.rcn-checklist__summary::-webkit-details-marker { display: none; }
.rcn-checklist__summary::before {
  content: '';
  width: 0;
  height: 0;
  border-inline-start: 5px solid currentColor;
  border-block: 4px solid transparent;
  transition: transform var(--duration-fast) var(--ease-throttle);
}
.rcn-checklist__details[open] > .rcn-checklist__summary::before {
  transform: rotate(90deg);
}
.rcn-checklist__summary:hover,
.rcn-checklist__summary:focus-visible { color: var(--ink-1); }

.rcn-checklist__detail-inner {
  padding-block-start: var(--space-2);
  max-width: none; /* .rcn-prose defaults to a reading measure; here it must fill the already-indented column instead */
}
.rcn-checklist__detail-inner :first-child { margin-block-start: 0; }

.rcn-checklist__pdf { margin-block-start: var(--space-2); }

/* -----------------------------------------------------------------------
   Print. An entrant may want this on paper instead of (or as well as) the
   interactive version - both must work, per the brief. Chrome that only
   makes sense on a screen (live counter, Reset, the checkbox accent colour
   burning ink) is removed; every requirement's detail is forced open so
   nothing is missing from the printed page just because it wasn't tapped
   open first. Colours switch to the paper/ink-on-paper pair site-wide
   "printed-look" tokens already define, rather than inventing new hex
   values for this one media query.
   ----------------------------------------------------------------------- */
@media print {
  .rcn-checklist { background: var(--surface-paper); color: var(--ink-on-paper); }
  .rcn-checklist__toolbar { display: none; } /* live count + Reset are screen-only concepts */

  .rcn-checklist__label-text { color: var(--ink-on-paper) !important; }
  .rcn-checklist__row:has(.rcn-checklist__checkbox:checked) .rcn-checklist__label-text {
    text-decoration-color: var(--ink-on-paper);
  }

  /* Force every detail open and drop its own toggle chrome - on paper there
     is no interaction, so "collapsed" would just mean "missing text". */
  .rcn-checklist__summary { display: none; }
  .rcn-checklist__details,
  .rcn-checklist__detail-inner { display: block !important; }
  .rcn-checklist__detail-inner,
  .rcn-checklist__detail-inner :is(p, li) { color: var(--ink-on-paper); }

  .rcn-checklist__item { break-inside: avoid; } /* a row's checkbox and its requirement text never split across a page break */
}

/* --- 11.18 Prize pool headline ---------------------------------------------
   The client's own cover-page figure. It is the single most persuasive number
   on the site for an entrant deciding whether to make the drive to Alice
   Springs, so it gets the odometer treatment and the display face. The amount
   is NOT computed from the tables below it - see views/blocks/prizePool.ejs. */
.rcn-prize-pool__headline {
  text-align: center;
  margin-block-end: var(--section-y-tight);
}
.rcn-prize-pool__headline-amount {
  font-family: var(--font-display);
  font-size: var(--text-odometer);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  color: var(--color-red-500);
  margin: 0;
  font-variant-numeric: tabular-nums;
}
.rcn-prize-pool__headline-label {
  font-family: var(--font-stencil);
  font-size: var(--text-h4);
  letter-spacing: var(--tracking-h4);
  text-transform: uppercase;
  color: var(--ink-1);
  /* margin-inline:auto is load-bearing, not tidiness. The global reading-measure
     rule (p { max-width: 68ch }) applies here too, and at this size 68ch is
     narrower than the container - so text-align:center centres the text inside a
     476px box that itself sits hard left, and the label reads as misaligned
     under a centred figure. Centring the BOX is the fix. */
  margin: 0 auto;
}
.rcn-prize-pool__headline-disciplines {
  font-size: var(--text-small);
  color: var(--ink-2);
  margin-block-start: var(--space-4);
  margin-inline: auto;
  max-width: 60ch;
}

/* --- 11.19 Ticket tier cards: dark-card restyle + note hierarchy ------------
   History: 28 Jul client feedback ("don't feel properly styled or in keeping
   with the theming") produced the paper ticket-stub treatment - cream surface
   with a perforated tear masked out of the card's top edge. 29 Jul Rick
   killed the paper entirely: the light cards "do not feel like they belong to
   the rest of the website at all". The cards now sit on --surface-1 (defined
   in SS8.7) like every other card, and the perforation mask went with the
   paper - a torn stub edge was a PAPER metaphor, and its solid top strip also
   framed the OZTIX banner, which now bleeds flush to the card's own edges
   with no border ("no card borders at all"). With the mask gone the card can
   paint its own box-shadow again, so the SS8.7 hover lift applies as-is and
   no cell-level workaround is needed. */

/* Tier name: the display face, with the red keyline the rest of the site uses
   to open a section. This is what makes it read as a headline rather than the
   first row of the benefits list. */
.rcn-ticket__name {
  font-family: var(--font-display);
  font-variation-settings: 'opsz' var(--opsz-heading), 'wght' 800;
  font-size: var(--text-display-3);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  text-transform: uppercase;
  padding-block-end: var(--space-3);
  border-block-end: 2px solid var(--color-red-500);
}

/* Benefits read as a checklist of what you get, not an unlabelled table. */
.rcn-ticket__list li {
  padding-inline-start: var(--space-6);
  position: relative;
}
.rcn-ticket__list li::before {
  content: '';
  position: absolute;
  inset-block-start: 0.75em;
  inset-inline-start: 0;
  width: var(--space-3);
  height: var(--space-2);
  border-inline-start: 2px solid var(--color-red-500);
  border-block-end: 2px solid var(--color-red-500);
  transform: rotate(-45deg) translateY(-2px);
}

/* Notes are pricing facts, not purchasable tiers. They share the tier cards'
   surface so they still read as part of the ticket family, but at display-3
   in a full-height slab they were shouting as loudly as the real tiers.
   Compact strip, label-scale type. */
.rcn-ticket-note { padding: var(--space-5) var(--space-6); }
.rcn-ticket-note__name {
  font-size: var(--text-h5);
  letter-spacing: var(--tracking-plate);
}

/* --- 11.20 Feature stat stack (homepage prize figures, 28 Jul) --------------
   The five prize figures used to run as one unbroken sentence inside the
   feature paragraph - the strongest numbers on the page, invisible at a scan.
   Now a payout-board stack: amount in the display face and the brand red,
   label quiet and technical beside it, hairline rules between rows. Same
   visual language as the prize-board tables (SS8.5), scaled into a feature
   column. */
.rcn-feature__stats {
  list-style: none;
  margin: var(--space-6) 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.rcn-feature__stats li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
  padding-block: var(--space-3);
  border-block-end: 1px solid var(--border-subtle);
}
.rcn-feature__stats li:first-child { border-block-start: 1px solid var(--border-subtle); }
.rcn-feature__stat-amount {
  font-family: var(--font-display);
  font-variation-settings: 'opsz' var(--opsz-heading), 'wght' 800;
  font-size: var(--text-display-3);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  text-transform: uppercase;
  color: var(--color-red-500);
  font-variant-numeric: tabular-nums;
  /* Wide enough for the longest real amount ("OVER $55,000") so every label
     starts on one shared column line instead of ragging per-row. */
  min-width: 13ch;
}
.rcn-feature__stat-label {
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  /* Ochre-300, not grey: same eyebrow-mixin role as .rcn-eyebrow itself
     ("quiet" doesn't mean uncoloured - it's still the site's kicker accent). */
  color: var(--color-ochre-300);
  /* Shrinkable flex item: without this the longest label ("Red CentreNATS
     Champion, plus more!") refuses to compress, trips flex-wrap, and drops
     under its amount while the other four rows sit two-up. Let the TEXT wrap
     inside the label instead of the label wrapping under the figure. */
  flex: 1 1 10rem;
  min-width: 0;
}

/* --- 11.21 Highlights row (block: highlightsSlider) -------------------------
   v2, 29 Jul. Was a scroll-snap carousel with prev/next buttons; now the same
   [data-marquee] continuous strip as the footer sponsor carousel, per Rick:
   "the same scrolling rig we use on the sponsor row, but in a 4 col with
   H4's where the whole grid side scrolls together."

   Same three-state model as .rcn-partner-marquee (SS11.3):
   - no JS / reduced motion: the track flex-wraps into a static grid showing
     every card - movement removed, never information;
   - .is-marquee-active: nowrap max-content strip driven by the measured
     rcn-ticker-scroll keyframes;
   - hover / focus-within: paused, because every card is a link and a target
     that runs away from the pointer is hostile. Same rule as the sponsors.

   Column count is a card WIDTH, not a grid property - a marquee track has no
   columns, so "4 col" means "a quarter of the viewport per card" at desktop,
   stepping down at the same breakpoints the picture() sizes attribute in the
   template declares. Capped so ultrawide screens get more cards, not bigger
   ones. */

.rcn-highlights__heading { margin-block-end: var(--space-6); }

.rcn-highlights-marquee {
  overflow: hidden;
  /* Breathing room for the hover lift + shadow, which would otherwise clip
     against the overflow boundary. */
  padding-block: var(--space-2) var(--space-3);
  --hl-cols: 1;
}
@media (min-width: 640px)  { .rcn-highlights-marquee { --hl-cols: 2; } }
@media (min-width: 960px)  { .rcn-highlights-marquee { --hl-cols: 3; } }
@media (min-width: 1280px) { .rcn-highlights-marquee { --hl-cols: 4; } }

.rcn-highlights-marquee__track {
  display: flex;
  flex-wrap: wrap;            /* static fallback state: wrap and show all */
  align-items: stretch;
  gap: var(--gutter);
  padding: 0;
  /* In the fallback grid state, keep the wrapped cards on the page grid. */
  margin: 0;
  padding-inline: max(var(--gutter), calc((100vw - var(--content-max)) / 2));
  list-style: none;
}
.rcn-highlights-marquee.is-marquee-active .rcn-highlights-marquee__track {
  flex-wrap: nowrap;
  width: max-content;
  padding-inline: 0;
  animation: rcn-ticker-scroll var(--marquee-duration, var(--duration-marquee)) linear infinite;
}
.rcn-highlights-marquee:hover .rcn-highlights-marquee__track,
.rcn-highlights-marquee:focus-within .rcn-highlights-marquee__track {
  animation-play-state: paused;   /* cards are links - let people aim */
}

.rcn-highlights__card {
  position: relative;
  flex: 0 0 auto;
  width: min(calc((100vw - (var(--hl-cols) + 1) * var(--gutter)) / var(--hl-cols)), 24rem);
  display: flex;
  flex-direction: column;
  background: var(--surface-card);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-1);
  transition: transform var(--duration-fast) var(--ease-throttle),
              box-shadow var(--duration-fast) var(--ease-throttle);
}
.rcn-highlights__card:hover,
.rcn-highlights__card:focus-within {
  transform: translateY(calc(var(--space-1) * -1));
  box-shadow: var(--shadow-3);
}

.rcn-highlights__media {
  position: relative;
  aspect-ratio: var(--ratio-card);
  background: var(--surface-2);
}
.rcn-highlights__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-base) var(--ease-throttle);
}
.rcn-highlights__card:hover .rcn-highlights__media img,
.rcn-highlights__card:focus-within .rcn-highlights__media img { transform: scale(1.04); } /* same lift as .rcn-card */

.rcn-highlights__body {
  padding: var(--space-4) var(--space-5) var(--space-5);
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* h3 elements at the h3 display size again (Rick, later 29 Jul: "change
   its titles back to H3's if they fit") - the condensed display face wraps
   the longest title ("Yeperenye Street Parade Form Up") to two lines on a
   quarter-width card, which fits. Falls back to --text-h4 if a third line
   ever appears in content: revisit then, not before. */
.rcn-highlights__title {
  margin: 0;
  font-size: var(--text-display-3);
  line-height: var(--leading-h4);
}
.rcn-highlights__link {
  color: var(--ink-1);
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-throttle);
}
.rcn-highlights__link:hover,
.rcn-highlights__link:focus-visible { color: var(--color-red-300); }
/* The one link, stretched over the whole card - SS8.14's pattern. */
.rcn-highlights__link::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}

.rcn-highlights__text {
  margin: 0;
  font-size: var(--text-small);
  /* Ochre-300 accent, not grey: the site's standard treatment for a subtitle/
     catch-phrase riding under a bold heading (matches .rcn-eyebrow,
     .rcn-precinct__drawer-text, .rcn-prize-pool__group-subtitle). */
  color: var(--color-ochre-300);
  /* Two lines, then cut: excerpts come from each page's own opening line and
     one long outlier must not set every card's height in the strip. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .rcn-highlights__card,
  .rcn-highlights__card:hover,
  .rcn-highlights__card:focus-within { transform: none; }
  .rcn-highlights__card:hover .rcn-highlights__media img,
  .rcn-highlights__card:focus-within .rcn-highlights__media img { transform: none; }
}

/* Grab affordance (data-marquee-drag): the strip can be seized and thrown.
   pan-y keeps vertical page scrolling native on touch - only horizontal
   gestures belong to the strip. */
.rcn-highlights-marquee.is-marquee-active .rcn-highlights-marquee__track {
  cursor: grab;
  touch-action: pan-y;
  user-select: none;
}
.rcn-highlights-marquee.is-grabbing .rcn-highlights-marquee__track { cursor: grabbing; }

/* Loop-position rail (Rick, 29 Jul v2): the full line is the whole loop of
   all 25 cards; the red segment is the WINDOW YOU ARE LOOKING AT - its width
   is the visible fraction of the loop (about four cards at desktop, about
   one at phone widths, computed not assumed), and it traverses the line
   exactly in step with the strip. Scrollbar semantics, in other words.
   motion.js supplies the geometry as --thumb-travel (loop length / viewport)
   and mirrors the track's duration/delay onto the segment, so it runs on
   the same animation clock and cannot drift; during a drag or a flick it is
   driven directly. The ::after twin one rail-length behind makes the wrap
   seamless - as the segment slides off the right end, its twin enters from
   the left, exactly like the strip itself. Hidden until the marquee runs -
   the static no-JS / reduced-motion grid has no position to indicate. */
.rcn-highlights-marquee__rail {
  position: relative;
  display: none;
  height: 3px;
  margin-block-start: var(--space-5);
  margin-inline: max(var(--gutter), calc((100vw - var(--content-max)) / 2));
  background: var(--border-subtle);
  overflow: hidden;
}
.rcn-highlights-marquee.is-marquee-active .rcn-highlights-marquee__rail { display: block; }
.rcn-highlights-marquee__progress {
  position: absolute;
  inset-block: 0;
  left: 0;
  width: calc(100% / var(--thumb-travel, 6));
  background: var(--color-red-500);
}
.rcn-highlights-marquee__progress::after {
  content: '';
  position: absolute;
  inset-block: 0;
  left: calc(-100% * var(--thumb-travel, 6));
  width: 100%;
  background: inherit;
}
.rcn-highlights-marquee.is-marquee-active .rcn-highlights-marquee__progress {
  animation: rcn-marquee-thumb var(--marquee-duration, var(--duration-marquee)) linear infinite;
}
.rcn-highlights-marquee:hover .rcn-highlights-marquee__progress,
.rcn-highlights-marquee:focus-within .rcn-highlights-marquee__progress {
  animation-play-state: paused;
}
@keyframes rcn-marquee-thumb {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(100% * var(--thumb-travel, 6))); }
}

/* --- 11.22 Event info band (feedback doc 28 Jul, homepage item 2) ---------
   "A section called Event Info was not seen on the site." Three headline
   facts, given room to read as a statement rather than a caption. A <dl>, so
   the terms are announced but not drawn (see views/blocks/eventInfo.ejs).
   Centred, because it sits between the hero and the centred countdown. */
.rcn-event-info {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: var(--space-3) var(--space-8);
  margin: 0;
  text-align: center;
}
.rcn-event-info__item { margin: 0; }
.rcn-event-info__value {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-h4);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--ink-1);
}
/* The first item is the event's own name, so it carries the accent; the dates
   and location are supporting facts and stay quiet. */
.rcn-event-info__item:first-child .rcn-event-info__value { color: var(--color-red-300); }

/* --- 11.23 Centred card grid + grid buttons ------------------------------
   Feedback doc 28 Jul, homepage items 3 and 4. Opt-in per block via
   `centered` — the Festival Info hub's grids stay left-aligned. Centring the
   TEXT inside each card as well is deliberate: a centred heading over
   left-aligned card copy reads as a mistake, which is the same "really funky
   to look at" mismatch the client reported between the countdown and this
   grid. Only applied to text-only cards (the Who Are You boxes have no
   image); a card with a photograph keeps its left-aligned body. */
.rcn-card-grid-section--centered .rcn-card-grid__heading { text-align: center; }
.rcn-card-grid-section--centered .rcn-card--no-image .rcn-card__body { text-align: center; }
/* Three boxes on a wide screen would otherwise stretch to fill 4+ columns and
   stop looking like a centred set. Cap the run and centre what remains. */
.rcn-card-grid-section--centered .rcn-card-grid {
  justify-content: center;
  max-width: 62rem;
  margin-inline: auto;
}

/* THE GATES (29 Jul, Rick: the three Who-are-you boxes were "plain and
   boring"; then "apply that styling to all plain cards without images
   sitewide if they are clickable"; then "same to the venue maps card on
   festival-info / entrants ... that will be clickable soon and I don't mind
   it appearing as a UI element now in advance"). SITEWIDE: any `.rcn-card`
   with no photograph gets the treatment, in every cardGrid instance (centred
   or not) - /home/plan-your-trip, /festival-info, /entrants, /spectators,
   and the homepage.

   EXCLUDED by default: `.rcn-card--pending` ("Coming soon" cards with no real
   href - implying a destination on something you can't click would be worse
   than the plain box it replaces). A specific pending card can opt back IN
   via the card's own `previewGate: true` flag (blocks-schema.js SS KIND.CARDS),
   which cardGrid.ejs turns into `.rcn-card--preview-gate` - a per-card
   editorial choice for a card whose link is genuinely coming, not a class of
   card exempted wholesale. Set today on Venue Maps on /festival-info and
   /entrants.

   - an IGNITION KEYLINE across the top: the brand red cooling into ochre
     (same heat language as the footer seam), which brightens and glows when
     the gate is armed (hover/focus);
   - a GATE NUMBER (pure CSS counter - decorative numbering, not content)
     stamped as a skewed plate chip, the licence-plate device at card scale.
     `counter-reset` on the grid means numbering restarts per grid, so a
     6-card hub page reads 01-06, not carrying on from a card list above it;
   - a faint red bruise pooling in the card's top corner, answering the page
     field's own bruises, deepening on hover;
   - the existing lift + a red glow when armed.

   No template change to invent copy - the numbering is a counter, and the
   preview-gate opt-in reuses the same "coming soon" content already there. */
.rcn-card-grid { counter-reset: rcn-gate; }
.rcn-card--no-image:not(.rcn-card--pending),
.rcn-card--preview-gate {
  counter-increment: rcn-gate;
  background:
    radial-gradient(120% 90% at 88% -12%, var(--fx-glow-red-soft), transparent 62%),
    var(--surface-card);
  transition: transform var(--duration-fast) var(--ease-throttle),
              box-shadow var(--duration-fast) var(--ease-throttle),
              background-color var(--duration-fast) var(--ease-throttle);
}
.rcn-card--no-image:not(.rcn-card--pending)::before,
.rcn-card--preview-gate::before {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--color-red-500), var(--color-ochre-500) 72%, transparent);
  opacity: 0.5;
  transition: opacity var(--duration-fast) var(--ease-throttle),
              box-shadow var(--duration-fast) var(--ease-throttle);
}
.rcn-card--no-image:not(.rcn-card--pending):hover,
.rcn-card--no-image:not(.rcn-card--pending):focus-within,
.rcn-card--preview-gate:hover,
.rcn-card--preview-gate:focus-within {
  box-shadow: var(--shadow-3), var(--glow-red);
}
.rcn-card--no-image:not(.rcn-card--pending):hover::before,
.rcn-card--no-image:not(.rcn-card--pending):focus-within::before,
.rcn-card--preview-gate:hover::before,
.rcn-card--preview-gate:focus-within::before {
  opacity: 1;
  box-shadow: var(--glow-red);
}
.rcn-card--no-image:not(.rcn-card--pending) .rcn-card__body::before,
.rcn-card--preview-gate .rcn-card__body::before {
  /* decimal-leading-zero, not a hardcoded '0' prefix: the manual prefix
     rendered question 10 as "010" (spotted on /faq#entrants). This pads
     1-9 and leaves 10+ alone. */
  content: counter(rcn-gate, decimal-leading-zero);
  display: inline-block;
  font-family: var(--font-body);
  font-weight: 800;
  font-size: var(--text-small);
  letter-spacing: var(--tracking-plate);
  color: var(--color-ochre-300);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-3);
  margin-block-end: var(--space-4);
  transform: skewX(var(--skew-emphasis));
  transition: color var(--duration-fast) var(--ease-throttle),
              border-color var(--duration-fast) var(--ease-throttle);
}
.rcn-card--no-image:not(.rcn-card--pending):hover .rcn-card__body::before,
.rcn-card--no-image:not(.rcn-card--pending):focus-within .rcn-card__body::before,
.rcn-card--preview-gate:hover .rcn-card__body::before,
.rcn-card--preview-gate:focus-within .rcn-card__body::before {
  color: var(--color-red-300);
  border-color: var(--color-red-500);
}
/* .rcn-card--pending sets opacity:0.75 - keep a preview-gated pending card at
   full strength, since the whole point is showing it as a real UI element. */
.rcn-card--preview-gate.rcn-card--pending { opacity: 1; }

.rcn-card-grid-section--centered .rcn-card--no-image .rcn-card__body {
  align-items: center;
  /* Restores the vertical centring the base rule dropped (above): this is
     the one set where it's still correct - three short, length-matched
     cards where centring is a deliberate look, not a grid of unpredictable
     hub-page copy lengths. */
  justify-content: center;
}
/* These titles are one line each; the hub grids' 3-line reservation would
   leave a void under them. Left-aligned hub grids keep the reservation -
   their titles genuinely run 1-3 lines and rows would go ragged without it. */
.rcn-card-grid-section--centered .rcn-card--no-image .rcn-card__title { min-height: 0; }

/* The section's next step, not a fourth card - so it sits below the grid with
   the same rhythm the grid has to the heading above it. */
.rcn-card-grid__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-block-start: var(--space-8);
}
.rcn-card-grid-section--centered .rcn-card-grid__ctas { justify-content: center; }

/* --- 11.24 Newsletter form field alignment (feedback doc item 8) ----------
   "CTA section's Email and Name field are not leveled."

   Real bug, and it only appeared below ~620px. The form was a flex row of
   [Name][Email][Sign up] with `flex: 1 1 16rem`: at narrow widths the button
   still fitted beside Email, so Name wrapped onto its own line at FULL width
   while Email kept only the space the button left it - two fields of visibly
   different widths on different lines.

   A grid fixes it at the cause rather than nudging the breakpoint: the two
   fields share equal columns and can never disagree, and the button either
   ends the row or takes a full-width row of its own.

   REPORTED A FOURTH TIME, 31 Jul, and the earlier fixes had all been aimed at
   the wrong thing. Measured off Rick's own screenshot, the Email label sat 6px
   below the Name label, and his crucial detail was that the page "loads
   aligned then un-aligns in less than 1 second". Nothing in our CSS or JS
   moves it, and it does not reproduce in a clean browser at any width or pixel
   ratio - but his screenshot shows a password-manager icon injected inside the
   Name field, which is exactly the kind of thing that lands about a second
   after load. A form-filler extension injecting a wrapper or sibling changes
   ONE field's internal height.

   So the cause is not ours, but the FRAGILITY is: the old grid made each
   .rcn-field a single grid item containing its own label and input, which
   means the two fields only line up while their internal heights happen to
   match. Anything that changes one - an extension, a font swapping in, a label
   wrapping at some width we didn't test - tilts it.

   SUBGRID removes the assumption instead of patching another symptom. The form
   owns two rows, "labels" and "controls", and each field opts into them rather
   than owning its own internal stack. Every label is then in one shared row and
   every input in another, so they are aligned by the layout itself. A field can
   grow internally all it likes and the row it does not belong to cannot move.
   The two fields can no longer disagree, whatever an extension does inside one
   of them. */
.rcn-newsletter__form {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: var(--space-3);
  align-items: end;
}
@media (max-width: 620px) {
  .rcn-newsletter__form { grid-template-columns: 1fr; }
}

@supports (grid-template-rows: subgrid) {
  @media (min-width: 621px) {
    .rcn-newsletter__form {
      /* Named for what they are; the field below spans both. */
      grid-template-rows: [labels] auto [controls] auto;
    }
    .rcn-newsletter__form .rcn-field {
      display: grid;
      grid-row: labels / span 2;
      grid-template-rows: subgrid;
      /* `normal`, NOT `0`, and not simply left out (Sopphia QA, 11 Aug 2026).
         A subgrid inherits the parent's gap ONLY while it declares none of its
         own - and `.rcn-field` declares `gap` for its flex layout, which still
         counts. The old `row-gap: 0` therefore did not cancel that gap, it
         replaced it: the field spaced its input 12px tighter than the parent's
         `controls` track and the two inputs sat 6px ABOVE the Sign up button.
         Deleting the line outright only got it to 2px, because `.rcn-field`'s
         own gap then applied. `normal` is the one value that hands track
         spacing back to the parent grid, which is what aligns them. Measured
         at 1440px: 6px -> 2px -> 0px. */
      row-gap: normal;
    }
    /* Bottom of the label row, so a label that wraps to two lines grows UPWARDS
       and the control row underneath it never shifts. */
    .rcn-newsletter__form .rcn-field__label { align-self: end; }
    .rcn-newsletter__form .rcn-field__input { align-self: stretch; }
    /* The button belongs beside the inputs, not beside the labels. */
    .rcn-newsletter__form .rcn-btn { grid-row: controls; }
  }
}

/* The button is a shade shorter than an input at the same font size; matching
   it to the field height squares off the end of the row. */
.rcn-newsletter__form .rcn-btn { min-height: var(--field-h); }

/* --- 11.25 Festival Info mega menu (29 Jul) -------------------------------
   The header's Festival Info item opens a full-width panel of every
   discipline - thumb + title - fed from the /entrant-guide hub page's own
   card grid via store.getFestivalMenu(), so the admin edits one list.

   CSS-ONLY DISCLOSURE. Opens on :hover and :focus-within; no JavaScript is
   involved in opening or closing (motion.js adds Escape-to-close as a
   courtesy, nothing more). The hidden state uses visibility, which also
   removes the panel's 25 links from the tab order and the accessibility
   tree while closed - a keyboard user tabs onto FESTIVAL INFO (the panel
   appears via :focus-within), then either tabs INTO it or straight past to
   Entrants. The top-level item remains a real link to the landing page, so
   touch and no-hover users lose nothing - they just take the page route.

   Desktop only: below the 1280px nav breakpoint the hamburger panel is the
   navigation, so the mega panel is display:none entirely. */

.rcn-nav__item--mega { /* anchor is the sticky header itself, so the panel can be full-width */ }

.rcn-mega {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  z-index: var(--z-header);
  display: none;
  background: var(--surface-2);
  border-block-start: 1px solid var(--border-subtle);
  /* The brand's red rule along the bottom edge - the same accent weight the
     nav links' underline uses, scaled to the panel. */
  border-block-end: 2px solid var(--color-red-500);
  box-shadow: var(--shadow-3);
  /* Never taller than the space under the sticky header. */
  max-height: calc(100vh - var(--header-h));
  overflow-y: auto;
  /* Closed: invisible AND unfocusable. visibility flips with zero duration,
     DELAYED until the fade-out finishes on close, instantly on open. */
  visibility: hidden;
  opacity: 0;
  transform: translateY(calc(var(--space-2) * -1));
  transition: opacity var(--duration-fast) var(--ease-throttle),
              transform var(--duration-fast) var(--ease-throttle),
              visibility 0s linear var(--duration-fast);
}
@media (min-width: 1280px) {
  .rcn-mega { display: block; }
}
.rcn-nav__item--mega:hover .rcn-mega,
.rcn-nav__item--mega:focus-within .rcn-mega {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
  transition-delay: 0s;
}
/* Escape pressed (motion.js 6c): stay closed even though focus is still on
   the top-level link, until pointer or focus leaves the item. */
.rcn-nav__item--mega.is-mega-closed .rcn-mega {
  visibility: hidden;
  opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
  .rcn-mega { transform: none; transition: none; }
}

.rcn-mega__inner {
  display: grid;
  grid-template-columns: 14rem 1fr;
  gap: var(--space-8);
  padding-block: var(--space-8);
}

/* Left rail: the section's identity plus the two catch-all routes. */
.rcn-mega__eyebrow {
  margin: 0 0 var(--space-2);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-micro);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  color: var(--color-ochre-300);
}
.rcn-mega__title {
  margin: 0 0 var(--space-5);
  font-family: var(--font-display);
  font-size: var(--text-display-3);
  line-height: var(--leading-display);
  text-transform: uppercase;
  color: var(--ink-1);
}
.rcn-mega__rail-links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.rcn-mega__rail-links a {
  display: inline-flex;
  align-items: center;
  min-height: var(--target-min);
  color: var(--color-red-300);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  text-decoration: none;
}
.rcn-mega__rail-links a:hover,
.rcn-mega__rail-links a:focus-visible { color: var(--ink-1); }

/* The discipline grid: compact thumb + title rows, five-ish columns. */
.rcn-mega__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* Sized so 25 items land as 4 columns x 7 rows inside a 1200px container
     next to the rail - 3 columns made the panel a 9-row tower. */
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: var(--space-1) var(--space-3);
  align-content: start;
}
.rcn-mega__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  border-radius: var(--radius-md);
  text-decoration: none;
  transition: background-color var(--duration-fast) var(--ease-throttle);
}
.rcn-mega__item:hover,
.rcn-mega__item:focus-visible { background: var(--surface-3); }

.rcn-mega__thumb {
  flex: 0 0 auto;
  width: 64px;
  aspect-ratio: var(--ratio-card);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface-1);
}
.rcn-mega__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-base) var(--ease-throttle);
}
.rcn-mega__item:hover .rcn-mega__thumb img { transform: scale(1.06); } /* same lift family as .rcn-card */
@media (prefers-reduced-motion: reduce) {
  .rcn-mega__item:hover .rcn-mega__thumb img { transform: none; }
}

.rcn-mega__label {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  color: var(--ink-2);
  transition: color var(--duration-fast) var(--ease-throttle);
}
.rcn-mega__item:hover .rcn-mega__label,
.rcn-mega__item:focus-visible .rcn-mega__label { color: var(--ink-1); }

/* --- 11.25b Nav submenu (any nav item with children, first used for
   Spectators - card "8", 3 Aug 2026 client + Rick 5 Aug) -------------------
   A small compact dropdown, deliberately NOT the Festival Info mega menu's
   full-width thumbnail grid - there is no card-grid data source behind these
   links, they are two plain labelled links, and a full-bleed panel for two
   rows of text would be an empty-feeling wall. Anchored to the nav item
   itself (`position: relative` on the li), not to the header, so it sits
   directly under the word that opened it.

   Same CSS-only :hover/:focus-within disclosure as the mega menu, same
   visibility+opacity+transform pattern, same reduced-motion override -
   deliberately identical mechanics so a future submenu never needs its own
   set of interaction bugs worked out from scratch. */
.rcn-nav__item--submenu { position: relative; }

.rcn-submenu {
  position: absolute;
  left: 0;
  top: 100%;
  /* Above .rcn-header__ctas (also var(--z-header)), not just equal to it.
     Found by verification, 5 Aug: at equal z-index the CTAs win on source
     order alone (they come later in the header markup) and, now that they
     straddle the boundary below the bar, physically sit on top of this
     panel for any nav item near the right edge - Spectators, here. Same
     stacking context (both descend from .rcn-header__inner's new
     position:relative), so one extra step is enough and nothing else
     needs touching. */
  z-index: calc(var(--z-header) + 1);
  display: none;
  min-width: 14rem;
  margin: 0;
  padding: var(--space-2);
  list-style: none;
  background: var(--surface-2);
  border-block-start: 1px solid var(--border-subtle);
  border-block-end: 2px solid var(--color-red-500);
  box-shadow: var(--shadow-3);
  visibility: hidden;
  opacity: 0;
  transform: translateY(calc(var(--space-2) * -1));
  transition: opacity var(--duration-fast) var(--ease-throttle),
              transform var(--duration-fast) var(--ease-throttle),
              visibility 0s linear var(--duration-fast);
}
@media (min-width: 1280px) {
  .rcn-submenu { display: block; }
}
.rcn-nav__item--submenu:hover .rcn-submenu,
.rcn-nav__item--submenu:focus-within .rcn-submenu {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
  transition-delay: 0s;
}
@media (prefers-reduced-motion: reduce) {
  .rcn-submenu { transform: none; transition: none; }
}
.rcn-submenu__link {
  display: flex;
  align-items: center;
  min-height: var(--target-min);
  padding-inline: var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--duration-fast) var(--ease-throttle),
              color var(--duration-fast) var(--ease-throttle);
}
.rcn-submenu__link:hover,
.rcn-submenu__link:focus-visible { background: var(--surface-3); color: var(--ink-1); }

/* Mobile takeover panel: children render as an indented sub-list directly
   under their parent link - same list, no separate disclosure state, since
   the whole panel is already the disclosure (opened by the hamburger). */
.rcn-nav-panel__sublist {
  list-style: none;
  margin: 0;
  padding-inline-start: var(--space-5);
}
.rcn-nav-panel__sublink {
  display: block;
  padding-block: var(--space-2);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-body);
  color: var(--ink-3);
  text-decoration: none;
}
.rcn-nav-panel__sublink:hover,
.rcn-nav-panel__sublink:focus-visible,
.rcn-nav-panel__sublink[aria-current="page"] { color: var(--ink-1); }

/* --- 11.26 Precinct tiles (block: precinctTiles, 29 Jul) ------------------
   The festival told as four places - the client's own precinct structure
   from their content document. Photograph with the house duotone, precinct
   name in the display face over a scrim, event count, then the precinct's
   events as a plain link list. See views/blocks/precinctTiles.ejs for why
   there is deliberately NO stretched-link device on these tiles. */

.rcn-precincts__heading { margin-block-end: var(--space-8); }

.rcn-precincts__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--gutter);
  align-items: start;
}
@media (max-width: 1279px) { .rcn-precincts__grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 639px)  { .rcn-precincts__grid { grid-template-columns: 1fr; } }

.rcn-precinct {
  background: var(--surface-card);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-1);
}

.rcn-precinct__media {
  position: relative;
  /* Taller than the card thumbnails on purpose - these are place portraits,
     not list entries, and the name sits inside the image. */
  aspect-ratio: 4 / 5;
  background: var(--surface-2);
}
.rcn-precinct__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Legibility scrim behind the name - sits above the photo and the .rcn-photo
   grain/halftone overlays, below the type. */
.rcn-precinct__media::after {
  content: '';
  position: absolute;
  inset: 40% 0 0 0;
  z-index: 2;
  background: linear-gradient(to bottom, transparent, rgb(0 0 0 / 0.78));
  pointer-events: none;
}

.rcn-precinct__count {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 3;
  margin: 0;
  padding: var(--space-1) var(--space-3);
  border-radius: 999px;
  background: var(--color-red-500);
  color: var(--ink-1);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-micro);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
}

.rcn-precinct__id {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 3;
  padding: var(--space-5);
}
.rcn-precinct__name {
  margin: 0;
  font-size: var(--text-display-3);
  line-height: var(--leading-display);
  color: var(--ink-1);
}
.rcn-precinct__venue {
  margin: var(--space-1) 0 0;
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  color: var(--color-ochre-300);
}

.rcn-precinct__events {
  list-style: none;
  margin: 0;
  padding: var(--space-2) var(--space-5) var(--space-4);
}
.rcn-precinct__events li + li { border-block-start: 1px solid var(--border-subtle); }
.rcn-precinct__event {
  position: relative;
  display: flex;
  align-items: center;
  min-height: var(--target-min);
  padding-inline-end: var(--space-6);
  color: var(--ink-2);
  font-size: var(--text-small);
  font-weight: 600;
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-throttle);
}
/* The follow-through arrow: parked just off the text, slides in on hover.
   transform/opacity only, per the motion contract. */
.rcn-precinct__event::after {
  content: '\2192';
  position: absolute;
  right: 0;
  color: var(--color-red-300);
  opacity: 0;
  transform: translateX(calc(var(--space-2) * -1));
  transition: opacity var(--duration-fast) var(--ease-throttle),
              transform var(--duration-fast) var(--ease-throttle);
}
.rcn-precinct__event:hover,
.rcn-precinct__event:focus-visible { color: var(--ink-1); }
.rcn-precinct__event:hover::after,
.rcn-precinct__event:focus-visible::after {
  opacity: 1;
  transform: translateX(0);
}
@media (prefers-reduced-motion: reduce) {
  .rcn-precinct__event::after { transform: none; transition: none; }
}

/* --- 11.26b Precinct row hover drawer (29 Jul v3) --------------------------
   Replaces the v2 portrait-swap. Hovering (or keyboard-focusing) an event
   row slides a drawer up OUT OF THE ROW ITSELF: the event's image in FULL
   COLOUR - deliberately unfiltered, so it pops against the tile's duotone
   portrait - plus title and tagline.

   THE ENVELOPE IS ASYMMETRIC, and that asymmetry is the requested "organic"
   feel: attack is quick and decelerating (--duration-base, --ease-drift - it
   arrives eagerly and settles), decay is half-speed and even (--duration-slow,
   --ease-throttle - it exhales rather than snaps shut). Skimming down the
   list reads as drawers blooming and melting behind the pointer.

   transform / opacity / clip-path only, per the motion contract. The rise is
   a translate; the clip-path's top inset shrinking makes the drawer GROW
   upward as it rises, which is what sells "sliding up out of the button".
   pointer-events: none - the drawer is a preview, never a hover target, so
   it can never trap the pointer or occlude the neighbouring rows' hits.
   aria-hidden - its text duplicates the row link and the destination page.
   The tile's own overflow:hidden clips any drawer taller than the space
   above its row (only theoretically possible on the single-link Dirt tile
   at extreme narrow widths). */
.rcn-precinct__row { position: relative; }

.rcn-precinct__drawer {
  position: absolute;
  /* Full width of the parent CARD, not of the row: the rows are inset by the
     list's --space-5 inline padding, so the drawer reaches back out past it
     on both sides. The tile's own overflow:hidden + radius crops the drawer
     flush to the card edges, which is why it carries no radius of its own -
     the card's corners do that job. */
  left: calc(-1 * var(--space-5));
  right: calc(-1 * var(--space-5));
  bottom: calc(100% + var(--space-1));
  z-index: 4;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4) var(--space-4);
  background: var(--surface-3);
  box-shadow: var(--shadow-3);
  pointer-events: none;
  opacity: 0;
  transform: translateY(var(--space-6));
  clip-path: inset(45% 0 0 0);
  transition: opacity var(--duration-slow) var(--ease-throttle),
              transform var(--duration-slow) var(--ease-throttle),
              clip-path var(--duration-slow) var(--ease-throttle);
}
.rcn-precinct__row:hover .rcn-precinct__drawer,
.rcn-precinct__row:focus-within .rcn-precinct__drawer {
  opacity: 1;
  transform: translateY(0);
  clip-path: inset(0 0 0 0);
  transition-duration: var(--duration-base), var(--duration-base), var(--duration-base);
  transition-timing-function: var(--ease-drift), var(--ease-drift), var(--ease-drift);
}
@media (prefers-reduced-motion: reduce) {
  .rcn-precinct__drawer { transition: none; transform: none; clip-path: inset(0 0 0 0); }
}

.rcn-precinct__drawer-media {
  display: block;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface-1);
}
.rcn-precinct__drawer-media picture { display: block; width: 100%; height: 100%; }
.rcn-precinct__drawer-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.rcn-precinct__drawer-title {
  font-family: var(--font-display);
  font-size: var(--text-h4);
  line-height: var(--leading-h4);
  text-transform: uppercase;
  color: var(--ink-1);
  padding-inline: var(--space-1);
}
.rcn-precinct__drawer-text {
  font-family: var(--font-body);
  font-size: var(--text-small);
  line-height: var(--leading-small);
  color: var(--color-ochre-300);
  padding-inline: var(--space-1);
}

/* --- 11.27 Program Highlights (block: programHighlights, 29 Jul) ------------
   The festival-info body, redesigned from four bare richText blocks (Rick:
   "a massive body of boring text"). Same verbatim copy, now presented as the
   client's own structure: four precinct chapters, each opening with the
   precinct's portrait banner (the SAME artwork its homepage tile uses) and
   its events flowing beneath in magazine columns.

   The columns are CSS multi-column, not grid: event write-ups run from one
   line to five paragraphs, and column flow packs that unevenness tightly
   while keeping document reading order. break-inside guards stop an event
   splitting across columns mid-paragraph. */

.rcn-prog-hl__heading {
  font-size: var(--text-h4);
  margin-block-end: var(--space-10);
}

.rcn-prog-hl__group { margin-block-end: var(--space-16); }
.rcn-prog-hl__group:last-child { margin-block-end: 0; }

/* Precinct banner: wide, short, photographic - the chapter plate. The scrim
   and duotone come from .rcn-photo; the id block sits on the scrim. */
.rcn-prog-hl__banner {
  position: relative;
  aspect-ratio: 4 / 1;
  min-height: 10rem;
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-block-end: var(--space-8);
}
.rcn-prog-hl__banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.rcn-prog-hl__banner::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  background: var(--scrim-gradient-up);
  pointer-events: none;
}
/* No-photo fallback: the ignition-keyline card surface, so a future precinct
   without artwork still gets a designed chapter plate, not a bare heading. */
.rcn-prog-hl__banner--flat {
  aspect-ratio: auto;
  min-height: 0;
  background:
    radial-gradient(120% 90% at 88% -12%, var(--fx-glow-red-soft), transparent 62%),
    var(--surface-card);
  padding-block: var(--space-8);
}
.rcn-prog-hl__banner--flat::after {
  inset: auto 0 auto 0;
  top: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--color-red-500), var(--color-ochre-500) 72%, transparent);
  opacity: 0.5;
}

.rcn-prog-hl__id {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 3;
  padding: var(--space-6) var(--space-6) var(--space-5);
}
.rcn-prog-hl__banner--flat .rcn-prog-hl__id { position: static; padding-block: 0; }

.rcn-prog-hl__precinct-no {
  margin: 0 0 var(--space-1);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-micro);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  color: var(--color-ochre-300);
}
.rcn-prog-hl__name {
  margin: 0;
  font-size: var(--text-display-3);
  line-height: var(--leading-display);
  color: var(--ink-1);
}
.rcn-prog-hl__venue {
  margin: var(--space-1) 0 0;
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  color: var(--color-ochre-300);
}

/* Events in magazine columns. */
.rcn-prog-hl__events {
  column-count: 1;
  column-gap: var(--space-12);
}
@media (min-width: 960px) { .rcn-prog-hl__events { column-count: 2; } }
.rcn-prog-hl__event {
  break-inside: avoid;
  margin-block-end: var(--space-8);
}
.rcn-prog-hl__event-name {
  margin: 0 0 var(--space-3);
  font-size: var(--text-h4);
  line-height: var(--leading-h4);
  color: var(--ink-1);
  /* The section-opener red keyline at event scale - short, not full-width,
     so 25 of them read as accents rather than a page of rules. */
  padding-block-end: var(--space-2);
  position: relative;
}
.rcn-prog-hl__event-name::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: var(--space-10);
  height: 2px;
  background: var(--color-red-500);
}
.rcn-prog-hl__copy { max-width: none; }
.rcn-prog-hl__copy p { max-width: none; }


/* --- 11.31 VALUE PROPOSITION PANELS --------------------------------------
   Client cards ENTRANTS PAGE / SPECTATOR PAGE, 2026-07-30. The brief's
   "scrolling value proposition bar" had been built as a scrolling text
   marquee; the client asked for it to be clickable and to look like the
   Program Highlights section on Festival Info.

   So this is deliberately the SAME banner language as .rcn-prog-hl__banner
   above - duotone photograph, numbered eyebrow, heavy display label pinned
   to the bottom over a scrim - laid out four-up instead of full width. Same
   idea, same visual family, one less invented treatment.

   The panels are PORTRAIT (3/4) rather than the 4/1 letterbox the precinct
   banners use: four of those side by side would be four thin slots with
   unreadable photographs in them. */
.rcn-valueprops__heading {
  margin-block-end: var(--space-6);
  font-size: var(--text-display-3);
  line-height: var(--leading-display);
}
.rcn-valueprops__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  padding: 0;
  margin: 0;
  list-style: none;
}
@media (min-width: 640px) {
  .rcn-valueprops__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 960px) {
  .rcn-valueprops__grid { grid-template-columns: repeat(4, 1fr); }
}

.rcn-valueprops__panel {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-md);
  overflow: hidden;
  transition: transform var(--duration-fast) var(--ease-throttle),
              box-shadow var(--duration-fast) var(--ease-throttle);
}
.rcn-valueprops__panel img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.rcn-valueprops__panel::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  background: var(--scrim-gradient-up);
  pointer-events: none;
}
/* No-photo fallback, matching the precinct banners' own flat state. */
.rcn-valueprops__panel--flat {
  aspect-ratio: auto;
  background:
    radial-gradient(120% 90% at 88% -12%, var(--fx-glow-red-soft), transparent 62%),
    var(--surface-card);
  padding-block: var(--space-8);
}
.rcn-valueprops__panel--flat::after {
  inset: auto 0 auto 0;
  top: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--color-red-500), var(--color-ochre-500) 72%, transparent);
  opacity: 0.5;
}

.rcn-valueprops__id {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 3;
  padding: var(--space-5) var(--space-5) var(--space-4);
}
.rcn-valueprops__panel--flat .rcn-valueprops__id { position: static; padding-block: 0; }

.rcn-valueprops__no {
  margin: 0 0 var(--space-1);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-micro);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  color: var(--color-ochre-300);
}
.rcn-valueprops__label {
  margin: 0;
  /* One step below the precinct banners' display-3: four panels in a row are
     each a quarter of the width the full-bleed precinct banner had. */
  font-size: var(--text-h4);
  line-height: var(--leading-display);
  color: var(--ink-1);
  /* Two lines are RESERVED whether or not this label needs them. The labels
     are the client's own phrases and they are not the same length - "Four
     huge days" sits on one line, "The nights light up - heavy hitters" takes
     two. Without a reservation the taller label pushes its own number chip up
     and the row of chips stops being a row. Standing rule from Rick, 29 Jul:
     chips line up, titles start at the same height. */
  min-height: calc(var(--leading-display) * 2em);
}
.rcn-valueprops__link { color: inherit; text-decoration: none; }
/* One stretched link over the whole panel - the same device cardGrid uses, so
   the big target is ONE link rather than two adjacent links to one place. */
.rcn-valueprops__link::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 4;
}
.rcn-valueprops__panel:hover,
.rcn-valueprops__panel:focus-within {
  transform: translateY(calc(var(--space-1) * -1));
  box-shadow: var(--shadow-3);
}
/* The stretched ::after is what the pointer hits, so the focus ring has to be
   drawn on the panel - a ring on the zero-size <a> itself would appear in the
   text, not around the target the user is actually on. */
.rcn-valueprops__panel:focus-within {
  box-shadow: var(--shadow-3), var(--focus-ring);
}
@media (prefers-reduced-motion: reduce) {
  .rcn-valueprops__panel { transition: none; }
  .rcn-valueprops__panel:hover,
  .rcn-valueprops__panel:focus-within { transform: none; }
}


/* --- 11.32 NUMBERED STEPS ------------------------------------------------
   Client card ENTRANTS PAGE, 2026-07-30: a "HOW TO ENTER YOUR VEHICLE"
   section as four instruction tiles, referencing the equivalent block on
   motorex.com.au/enter.

   Reuses the gate-card device from 11.23 (numbered chip, red-to-ochre top
   keyline, card surface) rather than inventing a fourth card treatment - a
   step tile and a gate card are the same object doing the same job.

   The number is a CSS counter on the <ol>, so it is never stored in the
   content: reorder or delete a step in the CMS and the rest renumber
   themselves. It cannot end up saying "Step 3" in second place. */
.rcn-steps__heading { margin-block-end: var(--space-4); }
.rcn-steps__intro {
  margin-block-end: var(--space-8);
  max-width: var(--prose-max);
  color: var(--ink-2);
}
.rcn-steps__list {
  counter-reset: rcn-step;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  padding: 0;
  margin: 0;
  list-style: none;
}
@media (min-width: 640px) {
  .rcn-steps__list { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1100px) {
  .rcn-steps__list { grid-template-columns: repeat(4, 1fr); }
}

.rcn-steps__item {
  counter-increment: rcn-step;
  position: relative;
  display: flex;
  flex-direction: column;
  /* Top-anchored, NOT centred. Standing rule from Rick, 29 Jul: equal-height
     tiles with different amounts of copy cannot be centred and still line up -
     every chip, every title and every paragraph must start at the same
     height. */
  justify-content: flex-start;
  padding: var(--space-6);
  border-radius: var(--radius-md);
  background:
    radial-gradient(120% 90% at 88% -12%, var(--fx-glow-red-soft), transparent 62%),
    var(--surface-card);
  overflow: hidden;
}
.rcn-steps__item::before {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--color-red-500), var(--color-ochre-500) 72%, transparent);
  opacity: 0.5;
}
/* The numbered chip, matching the gate cards and the FAQ rows. */
.rcn-steps__item::after {
  content: counter(rcn-step, decimal-leading-zero);
  align-self: flex-start;
  order: -1;
  font-family: var(--font-body);
  font-weight: 800;
  font-size: var(--text-small);
  letter-spacing: var(--tracking-plate);
  color: var(--color-ochre-300);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-3);
  margin-block-end: var(--space-4);
  transform: skewX(var(--skew-emphasis));
}
.rcn-steps__title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-h4);
  line-height: var(--leading-display);
  color: var(--ink-1);
  /* Two lines reserved so the body copy underneath starts at the same height
     in every tile - "Log in" is one line, "Complete the application form" is
     two. Same rule as the chips above: aligning the chips but letting the
     titles push the paragraphs to four different heights only moves the
     problem down the tile. */
  min-height: calc(var(--leading-display) * 2em);
}
.rcn-steps__text {
  margin: 0;
  color: var(--ink-2);
}
.rcn-steps__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: var(--space-8);
}

/* Step artwork (client card "Entrants Page - How to enter your vehicle
   section", 2026-07-31 - four supplied Street Machine Shed graphics, one per
   tile).

   `order: -2` puts it above the numbered chip, which already sits at -1. The
   negative margins pull it out to the tile's edges so it reads as the tile's
   own header rather than a picture pasted inside it; the tile's existing
   `overflow: hidden` and radius clip the top corners, and the 3px red keyline
   is absolutely positioned so it still draws across the top of the artwork.

   `aspect-ratio: 1` is not a crop decision imposed on the client's artwork -
   the four supplied files are all 1920x1920, so the box matches them exactly
   and nothing is cut. It is declared so the row keeps its height before the
   images decode, which is what stops the tiles jumping on load. */
.rcn-steps__media {
  order: -2;
  margin: calc(var(--space-6) * -1);
  margin-block-end: var(--space-5);
  aspect-ratio: 1 / 1;
  overflow: hidden;
}
.rcn-steps__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}


/* --- 11.33 CAMPAIGN PANELS - GRID LAYOUT ---------------------------------
   Client card SPECTATOR PAGE, 2026-07-30: "Insert CAMPAIGN PANELS SECTION...
   This should be a 3x3 grid", with a screenshot of the three ticket-release
   creatives side by side.

   The rotator rules in 11.x hide all but the active panel behind
   html.js-ready. A grid must undo exactly that, and only for itself - hence
   the html.js-ready qualifier here too, matching the specificity of the rules
   it is overriding rather than reaching for !important. */
html.js-ready .rcn-campaign--grid .rcn-campaign__stage { position: static; }
html.js-ready .rcn-campaign--grid .rcn-campaign__panel { display: block; }

.rcn-campaign--grid .rcn-campaign__stage {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
@media (min-width: 640px) {
  /* Two across before three: the creatives are square and text-heavy, and a
     third of a tablet width renders their smallest line unreadable. */
  .rcn-campaign--grid .rcn-campaign__stage { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 960px) {
  .rcn-campaign--grid .rcn-campaign__stage { grid-template-columns: repeat(3, 1fr); }
}
/* The stacking margin the no-JS rotator fallback adds would double the grid
   gap between rows. */
.rcn-campaign--grid .rcn-campaign__panel + .rcn-campaign__panel { margin-block-start: 0; }


/* --- 11.34 CHAMPIONS ROLL OF HONOUR --------------------------------------
   ABOUT US card, 2026-07-30. Eleven Grand Champions, 2015 to 2025.

   The client suggested a photo slider; we have no photograph of any of these
   cars, and captioning a stock event photo with a named person's car would be
   asserting something untrue. This is the honest version until the
   photography arrives - see views/blocks/champions.ejs. */
.rcn-champions__heading { margin-block-end: var(--space-4); }
.rcn-champions__intro {
  margin-block-end: var(--space-8);
  max-width: var(--prose-max);
  color: var(--ink-2);
}
.rcn-champions__list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  padding: 0;
  margin: 0;
  list-style: none;
  counter-reset: none;
}
@media (min-width: 640px) {
  .rcn-champions__list { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1100px) {
  .rcn-champions__list { grid-template-columns: repeat(3, 1fr); }
}
.rcn-champions__item {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  border-inline-start: 3px solid var(--color-ochre-500);
}
/* The year as a licence plate - the device already in the system, not a new
   one. flex:none so a long car name can never squeeze the year. */
.rcn-champions__year {
  flex: none;
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-h4);
  letter-spacing: var(--tracking-plate);
  color: var(--color-ochre-300);
}
.rcn-champions__who {
  margin: 0;
  color: var(--ink-1);
}


/* --- 11.35 FORM CONFIRMATION DIALOG --------------------------------------
   CONTACT US card, 2026-07-30: "Popup Confirmation on screen", with several
   paragraphs of copy. Inherits .rcn-popup's surface and centring; the only
   difference is that it has no header bar (there is nothing to close back TO -
   the message has already sent) and it needs room for real prose. */
.rcn-popup--confirm { max-width: 42rem; }
.rcn-popup--confirm .rcn-popup__title { margin-block-end: var(--space-4); }
.rcn-popup--confirm .rcn-prose { margin-block-end: var(--space-6); }

/* LEGIBILITY. Client card "CONTACT US PAGE- popup confirmation", 2026-07-31:
   "The confirmation message is not legible. It's currently presenting as white
   text on white screen."

   Measured: #C9BFB6 on #F2EAE0 = 1.52:1. That grey is --ink-2, the site's
   secondary ink for DARK surfaces, arriving via `.rcn-prose p` - and this
   dialog is the one light-background component on the site.

   SS 3 already has the right value under `[data-surface="paper"] p`, and the
   dialog now carries that attribute, but the two selectors have identical
   specificity and `.rcn-prose p` is declared later in the file, so it won. One
   class deeper settles it, and the value is not restated - it is the same
   colour-mix SS 3 defines, so the paper ink still has a single owner.

   Kept scoped to this dialog rather than raising the SS 3 rule's specificity:
   that block governs every paper surface the site might ever have, and this
   card is about one popup. */
.rcn-popup--confirm .rcn-prose p,
.rcn-popup--confirm .rcn-prose li {
  color: color-mix(in srgb, var(--ink-on-paper) 78%, var(--surface-paper));
}
.rcn-popup--confirm .rcn-prose strong,
.rcn-popup--confirm .rcn-prose a {
  color: var(--ink-on-paper);
}


/* --- 11.36 ANCHOR-NAV SECTION TARGET -------------------------------------
   Client card "Plan your trip page - scroll to issue", 2026-07-31. motion.js
   gives the heading it scrolled to `tabindex="-1"` and focuses it, so keyboard
   and screen-reader users carry on from the section rather than from the card
   they clicked. That focus is programmatic and bookkeeping only - the visible
   result of the click is the scroll - so the ring is suppressed here, the same
   way skip-link targets are handled everywhere.

   Scoped to the class motion.js adds, NOT to `[tabindex="-1"]:focus`: the
   latter would silently strip focus rings from any element that ever gets a
   negative tabindex, including ones where the ring is doing real work.

   BOTH properties, and `:focus-visible` as well as `:focus`. The site's focus
   ring is drawn with box-shadow (SS 3), not outline, so clearing the outline
   alone changed nothing visible - and Chrome counts a programmatic .focus() on
   a tabindex="-1" element as focus-VISIBLE, so the plain :focus selector never
   matched it either. Nothing is lost: an element with a negative tabindex is
   not in the tab order, so no keyboard user can arrive here by tabbing and
   find the ring missing. */
.rcn-anchor-target:focus,
.rcn-anchor-target:focus-visible {
  outline: none;
  box-shadow: none;
}


/* --- 11.37 SKYFIELD ------------------------------------------------------
   The three.js MacDonnell-Ranges-at-dusk backdrop (public/js/skyfield.js,
   31 Jul). The mount is empty markup until the script appends its canvas, so
   this rule costs nothing when the scene is off.

   z-index -1 with the body's background LEFT ALONE is the whole trick: the
   bruise wash propagates to the document canvas (html has no background of
   its own - do not give it one, that single change would paint the bruises
   over this layer), the opaque scene paints in front of that, and all
   content paints in front of the scene. Every failure mode - no JS, no
   WebGL, context lost - simply reveals the approved CSS background again. */
.rcn-skyfield {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
.rcn-skyfield canvas {
  display: block;
  width: 100%;
  height: 100%;
}


/* --- 11.38 EVENT PAGE DETAILS + BACK LINK --------------------------------
   Client cards "3a" and "3b", 3 Aug 2026, both about the 21 event pages under
   /entrant-guide/.

   3b: "Information like 'When', 'Where' and 'THIS IS A FREE EVENT' needs to be
   bold and larger." In the harvested copy those are ordinary paragraphs, so
   the practical facts - when, where, what it costs - were drawn at exactly the
   same weight as the prose around them. lib/view-helpers.js tags them at
   render time WITHOUT touching a character of the text; this sizes them.

   The label is the smaller, quieter half on purpose. "Venue" is not the
   information - the venue's NAME is - so the label is set as an uppercase
   micro-label and the answer under it gets the size. That is the same pattern
   the event-info band already uses, so the two agree. */
.rcn-prose .rcn-detail {
  font-size: var(--text-body-lg);
  font-weight: 600;
  color: var(--ink-1);
  line-height: 1.4;
}
.rcn-prose .rcn-detail__label {
  display: block;
  font-family: var(--font-body);
  font-size: var(--text-small);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--color-ochre-300);
  margin-block-end: var(--space-1);
}
/* The free-event notice is the one detail that is genuinely an announcement
   rather than a field, so it gets the display face and the brand red. */
.rcn-prose .rcn-detail--free {
  font-family: var(--font-display);
  font-size: var(--text-h4);
  color: var(--color-red-300);
  letter-spacing: var(--tracking-tight);
}

/* 3a: "a universal 'Back to Festival Info' link below all the event
   information". Deliberately a plain, obvious link rather than a button - it
   is a way back, not a call to action, and it must not compete with the entry
   and ticket buttons that live on these pages. */
.rcn-backlink {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--target-min);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: var(--tracking-ui);
  color: var(--color-red-300);
}
.rcn-backlink::before { content: '\2190'; }
.rcn-backlink:hover,
.rcn-backlink:focus-visible { color: var(--ink-1); }


/* --- 11.39 MOBILE FIXES FROM ELLA'S 3 AUG REVIEW -------------------------
   Three separate reports, all specific to phones, all measured on an iPhone-
   sized viewport (393px) before and after.

   Grouped here rather than scattered into each component's section because
   they share one cause: treatments designed at desktop size were carried onto
   a screen a quarter as wide without being re-judged there.

   The desktop rendering of all three is deliberately untouched. */

/* 10-1: "The Precinct images take up too much space on the screen... Would you
   be able to crop it down for mobile view?"

   Measured before: each precinct portrait was 431px tall on a 393px screen -
   more than half an iPhone - so the event list underneath, which is the useful
   part of the tile, started below the fold every time.

   4:5 is right on a desktop grid where four tiles sit side by side. Stacked
   one-per-row on a phone it is simply too tall. 16:10 keeps the same photo,
   cropped, not squashed - `object-fit: cover` is already in force, so the
   image keeps its proportions and loses the top and bottom instead. */
@media (max-width: 639px) {
  .rcn-precinct__media { aspect-ratio: 16 / 10; }
}

/* 10-2: "we need to make it more clear in mobile view that you can click on
   the cards... remove the animation from the cards to always show the orange
   arrow, so website viewers know they can find out more."

   She is right, and the reason is worth recording: the arrow is revealed on
   HOVER. A touch screen has no hover, so on a phone that cue never appears at
   all and the rows read as text rather than links.

   Keyed to `hover: none` rather than to a width. A device either has a hover
   pointer or it does not, and that - not how wide the screen is - is what
   decides whether a hover-revealed affordance can ever be seen. A touch laptop
   gets it right too, which a width query would get wrong. */
@media (hover: none) {
  .rcn-precinct__event::after {
    opacity: 1;
    transform: none;
  }
}

/* 10-6: "The 'Four Massive Days - taking over Alice Springs' section images
   are too large... would you be able to either a. crop them or b. format this
   section as a 2x2 grid?"

   Taking option b, which was hers. Two-up halves the height the section eats
   without touching anyone's artwork - cropping would mean deciding on their
   behalf which part of each photo survives, and these are people's cars.

   The panels are 3:4 portraits; at two per row on a phone they are narrow
   enough that the full-height portrait no longer dominates, so the aspect is
   left exactly as approved. One block, shown on BOTH /entrants and
   /spectators, so this one rule serves both pages - as does checking it. */
@media (max-width: 639px) {
  .rcn-valueprops__grid { grid-template-columns: repeat(2, 1fr); }
}

/* 2 (reopened, 5 Aug): "Can you please hyperlink all the titles within the
   cards as well for mobile view? I understand the images are linked but when
   you misclick and hit the title, the card disappears."

   Reproduced from the screenshot Rick supplied (a tap on the "Shannons Show
   'n' Shine" row): what she is describing is not an unlinked title, it is
   the SS11.26b hover drawer (views/blocks/precinctTiles.ejs) getting stuck
   open. That drawer is `position:absolute`, opaque and z-index:4 - it renders
   directly over the tile's own photo and heading. Its own file header already
   says "Touch devices never open drawers and simply follow the links", but
   the rule that opens it is keyed to `:hover, :focus-within` with no `hover`
   media guard, and tapping a link moves real focus to it on a touch screen -
   so the drawer DOES open, and can get left open (e.g. after using the
   browser back button), replacing the tile with itself. That reads exactly
   as "the card disappears" and "hit the title" - the drawer's own title/photo
   is what she is tapping trying to dismiss.

   Fix: hold the drawer at its closed rest state on any device that can't
   hover, restoring the file's own stated intent instead of adding new
   behaviour. Scoped to this one drawer - the arrow-affordance fix directly
   above already established `hover: none` (not a width) as how this file
   detects "can this device actually reach a hover state", for the same
   component. Desktop/hover-capable rendering is untouched. */
@media (hover: none) {
  .rcn-precinct__row:hover .rcn-precinct__drawer,
  .rcn-precinct__row:focus-within .rcn-precinct__drawer {
    opacity: 0;
    transform: translateY(var(--space-6));
    clip-path: inset(45% 0 0 0);
  }
}


/* --- reCAPTCHA v3 badge (11 Aug 2026) -----------------------------------
   Hidden deliberately. Google permits suppressing the floating badge PROVIDED
   the attribution notice appears in the form area or footer instead - that
   notice is in views/partials/footer.ejs. Do not remove one without the
   other, or we are in breach of their terms. */
.grecaptcha-badge { visibility: hidden; }
.rcn-recaptcha-note {
  font-size: var(--text-micro);
  color: var(--ink-3);
  margin-block-start: var(--space-4);
}
.rcn-recaptcha-note a { color: var(--ink-2); text-decoration: underline; }
