/* ================================================================
   GRAND FINAL FESTIVAL — Championship Programme aesthetic
   Editorial italic serif + mono captions on aubergine paper
   ================================================================ */

:root {
  /* Grand Final Festival palette — full GAL primary set */
  --blue:       #0aacff;
  --yellow:     #ffea1e;
  --orange:     #ff7c1f;
  --purple:     #7839c3;
  --pink:       #f2167c;

  /* base canvas — deep navy so the festival colors glow */
  --ink:        #02061f;
  --ink-deep:   #010313;
  --ink-2:      #050d2c;

  /* paper text */
  --cream:      #fff5e1;
  --cream-dim:  rgba(255, 245, 225, 0.62);
  --cream-mute: rgba(255, 245, 225, 0.32);
  --rule:       rgba(255, 245, 225, 0.18);

  /* legacy aliases (so existing CSS rules continue to resolve) */
  --aubergine:  var(--ink-2);
  --gold:       var(--yellow);
  --vermillion: var(--orange);
  --mauve:      var(--blue);

  --font-display: "Instrument Serif", "Bodoni Moda", Georgia, serif;
  --font-sans:    "Instrument Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --font-brand:   "Montserrat", system-ui, "Segoe UI", sans-serif;

  --header-height: 76px;
}

@media (min-width: 768px)  { :root { --header-height: 86px; } }
@media (min-width: 1024px) { :root { --header-height: 96px; } }

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

html {
  height: 100%;
  width: 100%;
  max-width: 100dvw;
  position: fixed;
  overflow: hidden;
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  background-color: var(--ink-deep);
}

@media (min-width: 768px)  { html { font-size: 17px; } }
@media (min-width: 1024px) { html { font-size: 18px; } }
@media (min-width: 1440px) { html { font-size: 19px; } }
@media (min-width: 1920px) { html { font-size: 20px; } }

body {
  font-family: var(--font-sans);
  color: var(--cream);
  height: 100dvh;
  width: 100%;
  max-width: 100dvw;
  position: fixed;
  overflow: hidden;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background:
    /* edge vignette */
    radial-gradient(140% 100% at 50% 50%, transparent 50%, rgba(0, 0, 0, 0.75) 100%),
    /* base wash */
    linear-gradient(160deg, var(--ink) 0%, var(--ink-2) 55%, var(--ink-deep) 100%);
}

/* ================================================================
   FESTIVAL ORBS — three soft glow orbs orbiting the viewport.
   Animated via transform: translate3d() only (GPU compositing,
   no layout/paint per frame). Each orb sits on its own GPU layer
   for smooth motion on iOS Safari. Centers stay inside
   [0vw, 100vw] x [0vh, 100vh] so the visible body stays in view.
   ================================================================ */

.orbs {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
  contain: layout style paint;
}

.orb {
  /* Anchored at viewport top-left; transform translates the centre into
     the viewport. Size in vmin so the orb fits any aspect ratio. */
  position: absolute;
  top: 0;
  left: 0;
  width: 50vmin;
  height: 50vmin;
  margin-top: -25vmin;
  margin-left: -25vmin;
  border-radius: 50%;
  /* Heavy soft glow ring; body stays fully opaque so overlaps never blend
     into a new colour — one orb sits over the other. */
  filter: blur(90px);
  will-change: transform;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

/* Each orb has a fully opaque radial gradient (alpha 1 throughout), with
   an off-centre highlight for a subtle 3-D-ball feel. No alpha fade in
   the body, so where orbs overlap the topmost completely covers what
   sits under it — no new mixed colour is ever produced. */

.orb--blue {
  background: radial-gradient(circle at 35% 35%,
    rgb(120, 215, 255) 0%,
    rgb(10, 172, 255)  70%);
  z-index: 3;
  animation: orb-blue 22s ease-in-out infinite alternate;
}

.orb--orange {
  background: radial-gradient(circle at 35% 35%,
    rgb(255, 175, 100) 0%,
    rgb(255, 124, 31)  70%);
  z-index: 2;
  animation: orb-orange 26s ease-in-out infinite alternate;
}

.orb--yellow {
  background: radial-gradient(circle at 35% 35%,
    rgb(255, 248, 130) 0%,
    rgb(255, 234, 30)  70%);
  z-index: 1;
  animation: orb-yellow 30s ease-in-out infinite alternate;
}

.orb--purple {
  background: radial-gradient(circle at 35% 35%,
    rgb(170, 110, 220) 0%,
    rgb(120, 57, 195)  70%);
  z-index: 4;
  animation: orb-purple 28s ease-in-out infinite alternate;
}

.orb--pink {
  background: radial-gradient(circle at 35% 35%,
    rgb(255, 105, 165) 0%,
    rgb(242, 22, 124)  70%);
  z-index: 5;
  animation: orb-pink 24s ease-in-out infinite alternate;
}

/* Independent Lissajous-style wandering for each orb. Each visits all
   corners and edges of the viewport — waypoints span 10–90 dvw and
   10–90 dvh so the orbs roam the entire dvh × dvw, not just the
   centre. The heavy blur softens any moment an orb peeks past an edge
   into a glowing fringe at the viewport rim. */

@keyframes orb-blue {
  0%   { transform: translate3d(15dvw, 20dvh, 0); }
  18%  { transform: translate3d(82dvw, 28dvh, 0); }
  37%  { transform: translate3d(38dvw, 80dvh, 0); }
  55%  { transform: translate3d(88dvw, 70dvh, 0); }
  72%  { transform: translate3d(12dvw, 50dvh, 0); }
  88%  { transform: translate3d(65dvw, 12dvh, 0); }
  100% { transform: translate3d(30dvw, 88dvh, 0); }
}

@keyframes orb-orange {
  0%   { transform: translate3d(85dvw, 18dvh, 0); }
  19%  { transform: translate3d(20dvw, 55dvh, 0); }
  39%  { transform: translate3d(75dvw, 85dvh, 0); }
  58%  { transform: translate3d(15dvw, 25dvh, 0); }
  76%  { transform: translate3d(60dvw, 45dvh, 0); }
  90%  { transform: translate3d(35dvw, 88dvh, 0); }
  100% { transform: translate3d(80dvw, 60dvh, 0); }
}

@keyframes orb-yellow {
  0%   { transform: translate3d(50dvw, 88dvh, 0); }
  21%  { transform: translate3d(85dvw, 50dvh, 0); }
  41%  { transform: translate3d(20dvw, 78dvh, 0); }
  60%  { transform: translate3d(72dvw, 15dvh, 0); }
  78%  { transform: translate3d(12dvw, 35dvh, 0); }
  92%  { transform: translate3d(55dvw, 65dvh, 0); }
  100% { transform: translate3d(88dvw, 22dvh, 0); }
}

@keyframes orb-purple {
  0%   { transform: translate3d(40dvw, 50dvh, 0); }
  17%  { transform: translate3d(80dvw, 80dvh, 0); }
  35%  { transform: translate3d(15dvw, 22dvh, 0); }
  54%  { transform: translate3d(70dvw, 18dvh, 0); }
  73%  { transform: translate3d(22dvw, 70dvh, 0); }
  90%  { transform: translate3d(85dvw, 38dvh, 0); }
  100% { transform: translate3d(45dvw, 88dvh, 0); }
}

@keyframes orb-pink {
  0%   { transform: translate3d(60dvw, 30dvh, 0); }
  16%  { transform: translate3d(20dvw, 65dvh, 0); }
  34%  { transform: translate3d(82dvw, 45dvh, 0); }
  53%  { transform: translate3d(38dvw, 85dvh, 0); }
  72%  { transform: translate3d(85dvw, 15dvh, 0); }
  90%  { transform: translate3d(15dvw, 40dvh, 0); }
  100% { transform: translate3d(55dvw, 75dvh, 0); }
}

/* ================================================================
   PAGE THEME — SMF: black background, amber orbs
   ================================================================ */

body.page-smf {
  background: #000;
}

body.page-smf .orb--blue,
body.page-smf .orb--orange,
body.page-smf .orb--yellow,
body.page-smf .orb--purple,
body.page-smf .orb--pink {
  background: radial-gradient(circle at 35% 35%,
    rgb(220, 160, 80) 0%,
    rgb(186, 123, 37) 70%);
}

/* ================================================================
   PAGE THEME — Vortex: white background, 4 distinct orb colors
   ================================================================ */

body.page-vortex {
  background: #0f0f0f;
}

body.page-vortex .orb--blue {
  background: radial-gradient(circle at 35% 35%, #9c9aa2 0%, #7a7880 70%);
}

body.page-vortex .orb--orange {
  background: radial-gradient(circle at 35% 35%, #ffffff 0%, #fbfdfd 70%);
}

body.page-vortex .orb--yellow {
  background: radial-gradient(circle at 35% 35%, #5a30ff 0%, #2b00e9 70%);
}

body.page-vortex .orb--purple {
  background: radial-gradient(circle at 35% 35%, #ff4a8d 0%, #ee0364 70%);
}

body.page-vortex .orb--pink {
  background: radial-gradient(circle at 35% 35%, #f566e3 0%, #e20dd6 70%);
}

/* ================================================================
   PAGE THEME — Mitch: espresso backdrop, gold + black orbs
   pulled from the Mitch Coffee Shop wordmark on the user's desktop.
   Gold = #f0ae21 (the "itch." letters), Black = the "m" outline +
   wordmark. Background is a deep coffee tone so the gold orbs glow
   and the dark orbs read as subtle shadow texture.
   ================================================================ */

body.page-mitch {
  background: #1a0f04;
}

body.page-mitch .orb--blue {
  /* bright headline gold · the logo's signature */
  background: radial-gradient(circle at 35% 35%,
    rgb(255, 205, 70) 0%,
    rgb(240, 174, 33) 70%);
}

body.page-mitch .orb--orange {
  /* deep amber · darker gold variant */
  background: radial-gradient(circle at 35% 35%,
    rgb(225, 155, 25) 0%,
    rgb(170, 108, 5)  70%);
}

body.page-mitch .orb--yellow {
  /* soft butterscotch · the light end of the gold */
  background: radial-gradient(circle at 35% 35%,
    rgb(255, 225, 135) 0%,
    rgb(245, 195, 75)  70%);
}

body.page-mitch .orb--purple {
  /* near-black coffee · the wordmark's black, kept barely visible
     against the espresso backdrop so it adds depth without competing */
  background: radial-gradient(circle at 35% 35%,
    rgb(55, 32, 12) 0%,
    rgb(15, 8, 3)   70%);
}

body.page-mitch .orb--pink {
  /* warm caramel · mid gold, ties the gold trio together */
  background: radial-gradient(circle at 35% 35%,
    rgb(245, 190, 75) 0%,
    rgb(215, 150, 25) 70%);
}

/* film-grain overlay — fixed to viewport.
   Uses normal blend (no mix-blend-mode) so iOS Safari doesn't pay
   per-pixel blend cost on every frame underneath. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  opacity: 0.06;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.55 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  will-change: auto;
}

.app-shell {
  position: fixed;
  /* Shell covers the full viewport; padding-top reserves space for the
     topbar so content starts visible below it but scrolls behind it. */
  inset: 0;
  padding-top: calc(var(--header-height) + env(safe-area-inset-top, 0px));
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  touch-action: pan-y;
  scroll-behavior: smooth;
  scrollbar-width: none;
  -ms-overflow-style: none;
  z-index: 2;
}

/* ================================================================
   TOPBAR — always-on header (back / title / logo)
   ================================================================ */

.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: calc(var(--header-height) + env(safe-area-inset-top, 0px));
  padding-top: env(safe-area-inset-top, 0px);
  display: grid;
  /* title takes its natural width; the two side slots split the rest */
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 0.5rem;
  padding-left: max(0.75rem, env(safe-area-inset-left, 0px));
  padding-right: max(0.75rem, env(safe-area-inset-right, 0px));
  z-index: 3;
  background: transparent;
  backdrop-filter: blur(24px) saturate(150%);
  -webkit-backdrop-filter: blur(24px) saturate(150%);
  border-bottom: none;
  /* Fade the blur (and the band itself) out at the bottom so there is
     no visible seam between header and scrolling content. */
  mask-image: linear-gradient(to bottom, black 0%, black 75%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black 75%, transparent 100%);
}

@media (min-width: 768px) {
  .topbar {
    padding-left: max(1.25rem, env(safe-area-inset-left, 0px));
    padding-right: max(1.25rem, env(safe-area-inset-right, 0px));
  }
}

.topbar__slot {
  display: flex;
  align-items: center;
  height: 100%;
}
.topbar__slot--right { justify-content: flex-end; }

/* Glass circle holding the gold back chevron (mirrors the '›' on GAL's
   festival CTA). The chevron pulses left/right inside the circle;
   overflow: hidden clips its travel so it never escapes the glass. */
.topbar__back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.22);
  box-shadow:
    0 6px 14px rgba(0, 0, 0, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.22);
  text-decoration: none;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
  transition: background 180ms ease, border-color 180ms ease;
}

.topbar__back:hover {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.36);
}

.topbar__back-chevron {
  font-family: 'Syne', Georgia, serif;
  font-weight: 800;
  font-size: 1.65rem;
  line-height: 1;
  color: #F5C72F;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
  display: inline-block;
  animation: topbar-back-pulse 1.5s ease-in-out infinite;
}

@keyframes topbar-back-pulse {
  0%, 100% { opacity: 1;    }
  50%      { opacity: 0.75; }
}

@media (prefers-reduced-motion: reduce) {
  .topbar__back-chevron { animation: none; }
}

.topbar__title {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.15rem, 2.2vw, 1.55rem);
  letter-spacing: -0.005em;
  line-height: 1;
  text-align: center;
  color: var(--cream);
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.topbar__logo {
  width: 43px;
  height: 43px;
  object-fit: contain;
  display: block;
  border-radius: 50%;
  background: var(--cream);
  padding: 5px;
}

/* Per-app topbar logo background tones (mirror the dashboard tiles) */
.topbar__logo--white  { background: #ffffff; }
.topbar__logo--black  { background: #000000; }
.topbar__logo--purple { background: #54125c; }
.topbar__logo--vortex { background: #e3e0e3; }
.topbar__logo--mitch  { background: #ffffff; }

/* Brand wordmark — Montserrat 900, three festival colours */
.brand {
  font-family: var(--font-brand);
  font-weight: 900;
  font-style: normal;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: clamp(1.2rem, 3.8vw, 2rem);
  line-height: 1;
  display: inline-flex;
  align-items: baseline;
  gap: clamp(0.28em, 1.2vw, 0.5em);
  white-space: nowrap;
}

/* Each word: smooth 3D extrusion built from dense half-pixel steps
   that interpolate from the letter colour to its deepest shade,
   with a small blur on the deepest step so the whole extrusion
   reads as one continuous slab rather than stacked tiers. */

.brand__word,
.brand__word--orange,
.brand__word--blue,
.brand__word--yellow {
  color: #ffffff;
  /* 2-layer 3-D drop — white face, one mid-gray step, one dark step. */
  text-shadow:
    1px 1px 0 #9a9a9a,
    2px 2px 1px #1f1f1f;
}

/* 10-second cycle on a shared timeline. Each solo gets an equal
   10 % window (1 s). The all-three plateau lands at 40 → 90 %
   which is exactly 5 s.
      5 % GRAND solo peak    (window 0–10 %)
     15 % FINAL solo peak    (window 10–20 %)
     25 % FESTIVAL solo peak (window 20–30 %)
     40 → 90 % plateau (5 s)
     0 / 10 / 20 / 30 / 100 % dim handoffs */

@keyframes glow-grand {
  0%, 10%, 30%, 100% { color: #ffffff; }
  5%                 { color: var(--orange); }
  40%, 90%           { color: var(--orange); }
}

@keyframes glow-final {
  0%, 10%, 20%, 30%, 100% { color: #ffffff; }
  15%                     { color: var(--blue); }
  40%, 90%                { color: var(--blue); }
}

@keyframes glow-festival {
  0%, 20%, 30%, 100% { color: #ffffff; }
  25%                { color: var(--yellow); }
  40%, 90%           { color: var(--yellow); }
}

.app-shell::-webkit-scrollbar { display: none; width: 0; height: 0; }

html, body {
  scrollbar-width: none;
  -ms-overflow-style: none;
}

html::-webkit-scrollbar,
body::-webkit-scrollbar { display: none; width: 0; height: 0; }

input, textarea, select, [contenteditable="true"], .selectable, .selectable * {
  -webkit-user-select: text;
  -moz-user-select: text;
  -ms-user-select: text;
  user-select: text;
}

input, textarea, select, button {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
  -webkit-appearance: none;
  appearance: none;
}

img { -webkit-user-drag: none; }

a {
  color: inherit;
  text-decoration: none;
  -webkit-touch-callout: none;
}

::selection { background: var(--gold); color: var(--ink-deep); }

/* ================================================================
   STAGE — the programme cover composition
   ================================================================ */

.stage {
  height: 100%;
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  padding:
    calc(1.5rem + env(safe-area-inset-top, 0px))
    calc(1.5rem + env(safe-area-inset-right, 0px))
    calc(3.5rem + env(safe-area-inset-bottom, 0px))
    calc(1.5rem + env(safe-area-inset-left, 0px));
}

/* ================================================================
   APP GRID — mobile-homescreen tiles
   ================================================================ */

.app-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: clamp(2.5rem, 8vw, 4rem);
  row-gap: clamp(4rem, 12vw, 6rem);
  width: 100%;
  max-width: 420px;
  justify-items: center;
}

@media (min-width: 720px) {
  .app-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    max-width: 900px;
    column-gap: clamp(3rem, 6vw, 5rem);
    row-gap: 4rem;
  }
}

.app-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  text-decoration: none;
  color: inherit;
  background: transparent;
  -webkit-tap-highlight-color: transparent;
  transition:
    transform 200ms cubic-bezier(0.22, 0.61, 0.36, 1),
    opacity 140ms ease;
}

.app-tile,
.app-tile:hover,
.app-tile:focus,
.app-tile:active,
.app-tile:focus-visible,
.app-tile:focus-within {
  background: transparent;
  outline: none;
}

/* Instant tap feedback: scale + grey-out. Driven by :active (painted
   synchronously on touch — works on iOS now that nav.js registers a
   touchstart listener) so the tile reacts BEFORE navigation, matching the
   Android behaviour the click-added .nav-pending gave. */
.app-tile:active { transform: scale(0.94); opacity: 0.55; }

.app-tile__icon {
  /* Tile dimensions are LOCKED — never derived from contents. */
  width: clamp(64px, 18vw, 86px);
  aspect-ratio: 1 / 1;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2%;
  box-sizing: border-box;
  border-radius: 22%;
  background: rgba(255, 255, 255, 0.10);
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.22);
  box-shadow:
    0 8px 22px rgba(0, 0, 0, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.25);
  position: relative;
  overflow: hidden;
  transition: background 200ms ease, border-color 200ms ease;
}

.app-tile:hover .app-tile__icon {
  border-color: rgba(255, 255, 255, 0.40);
}

/* Per-app tile background tones — mostly opaque with just enough
   translucency for the backdrop blur to show through as glass. */
.app-tile__icon--white   { background: rgba(255, 255, 255, 0.78); }
.app-tile__icon--black   { background: rgba(0, 0, 0, 0.78); }
.app-tile__icon--purple  { background: rgba(84, 18, 92, 0.80); }
.app-tile__icon--vortex  { background: rgba(179, 179, 183, 0.78); }
.app-tile__icon--emerald { background: rgba(6, 78, 59, 0.80); }   /* Maps */
.app-tile__icon--azure   { background: rgba(17, 24, 80, 0.80); }  /* Info */
.app-tile__icon--mitch   { background: rgba(255, 255, 255, 0.92); }  /* Mitch · matches logo bg */
.app-tile__icon--store   { background: rgba(244, 241, 234, 0.95); }  /* GAL Store · cream shop facade */

.app-tile__icon img {
  /* Explicit 100% × 100% of the tile's padded content area, with
     object-fit:contain handling aspect ratio. Deterministic across
     Safari + mobile (no `auto` intrinsic-size math that varies). */
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  position: relative;
  z-index: 1;
}

.app-tile__label {
  font-family: var(--font-sans);
  font-size: clamp(0.78rem, 1.8vw, 0.92rem);
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--cream);
  text-align: center;
  min-height: 1.2em;
  line-height: 1.2;
  max-width: 12ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* TOP RAIL — eyebrow text */
.rail {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--cream-dim);
  opacity: 0;
  animation: fadeIn 700ms ease 80ms forwards;
}

.rail__mark {
  color: var(--gold);
}

.rail__dot {
  display: inline-block;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--vermillion);
  margin: 0 0.5em;
  vertical-align: middle;
  transform: translateY(-1px);
}

.rail--bottom {
  align-items: end;
  font-size: 0.65rem;
  color: var(--cream-mute);
}

/* CENTRE — programme title */
.programme {
  align-self: center;
  text-align: center;
  position: relative;
  max-width: min(960px, 100%);
  margin: 0 auto;
}

.programme__edition {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 1.25rem;
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  opacity: 0;
  animation: fadeIn 700ms ease 220ms forwards;
}

.programme__edition::before,
.programme__edition::after {
  content: "";
  width: 28px;
  height: 1px;
  background: var(--gold);
  opacity: 0.7;
}

.title {
  font-family: var(--font-display);
  font-weight: 400;
  font-style: italic;
  line-height: 0.86;
  letter-spacing: -0.02em;
  margin: 0;
  color: var(--cream);
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
}

.title__line {
  display: block;
  font-size: clamp(3.5rem, 12vw, 9rem);
  opacity: 0;
  transform: translateY(28px);
  animation: rise 900ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}

.title__line--1 { animation-delay: 280ms; }
.title__line--2 {
  animation-delay: 420ms;
  color: var(--gold);
  font-style: italic;
}

.title__amp {
  display: block;
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(2rem, 5vw, 3.5rem);
  color: var(--vermillion);
  margin: 0.1em 0;
  opacity: 0;
  animation: fadeIn 700ms ease 360ms forwards;
}

.tagline {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(1.1rem, 2vw, 1.5rem);
  line-height: 1.45;
  margin: 1.75rem auto 0;
  max-width: 38ch;
  color: var(--cream-dim);
  opacity: 0;
  animation: fadeIn 800ms ease 620ms forwards;
}

.tagline em {
  font-style: italic;
  color: var(--cream);
  border-bottom: 1px solid var(--rule);
  padding-bottom: 0.05em;
}

/* hairline divider used between title and tagline (optional ornament) */
.programme__rule {
  width: 60px;
  height: 1px;
  background: var(--rule);
  margin: 1.5rem auto 0;
  transform: scaleX(0);
  transform-origin: center;
  animation: stretch 700ms ease 520ms forwards;
}

/* BOTTOM RAIL — meta + status */
.meta {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--cream-dim);
}

.meta__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #4ade80;
  box-shadow: 0 0 14px #4ade80;
  animation: blink 2.2s ease-in-out infinite;
}

/* ================================================================
   EXIT PORTAL — floating circle (every page)
   ================================================================ */

.exit-portal {
  position: fixed;
  right: calc(1.5rem + env(safe-area-inset-right, 0px));
  bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px));
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.22);
  display: grid;
  place-items: center;
  box-shadow:
    0 8px 18px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.25);
  transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1),
              border-color 220ms ease,
              background 220ms ease;
  z-index: 9999;
  overflow: hidden;
  opacity: 0;
  animation: fadeIn 300ms ease 400ms forwards;
}

.exit-portal:hover {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.36);
}

/* Rotating tri-color halo — blue → orange → yellow.
   Tight ring just outside the button edge; mask keeps the
   cream centre fully visible. */
/* Tri-color conic gradient sealed inside the glass button —
   heavy blur for soft diffusion, saturate + brightness for punch. */
.exit-portal::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(from 0deg,
    var(--blue)   0%,
    var(--orange) 33%,
    var(--yellow) 66%,
    var(--blue)   100%);
  filter: blur(14px) saturate(1.8) brightness(1.15);
  animation: portal-spin 6s linear infinite;
  pointer-events: none;
}

.exit-portal:hover {
  transform: translateY(-2px) scale(1.05);
}

.exit-portal:hover::before {
  inset: 0;
  filter: blur(16px) saturate(2) brightness(1.2);
}

.exit-portal:active { transform: scale(0.96); }

.exit-portal img {
  width: 62%;
  height: 62%;
  object-fit: contain;
  display: block;
  position: relative;
  z-index: 1;
}

@keyframes portal-spin {
  to { transform: rotate(360deg); }
}

/* ================================================================
   ANIMATIONS
   ================================================================ */

@keyframes rise {
  from { opacity: 0; transform: translateY(28px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes stretch {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@keyframes blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ================================================================
   Top nav progress bar + click-lock — mirrors GAL's main.js UX.
   Slim 3px bar above the page slides while the next page loads.
   .nav-pending dims a clicked link; .nav-locked blocks double-taps.
   ================================================================ */
.nav-progress {
  position: fixed;
  top: env(safe-area-inset-top, 0px);
  left: 0;
  right: 0;
  height: 3px;
  z-index: 99999;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
  background-image: linear-gradient(90deg,
    rgba(120, 57, 195, 0)   0%,
    rgba(120, 57, 195, 0)   25%,
    var(--purple)           50%,
    rgba(120, 57, 195, 0)   75%,
    rgba(120, 57, 195, 0)   100%);
  background-color: rgba(120, 57, 195, 0.18);
  background-size: 200% 100%;
  background-repeat: no-repeat;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  will-change: background-position, opacity;
}
.nav-progress.active {
  opacity: 1;
  -webkit-animation: nav-progress-slide 1s linear infinite;
          animation: nav-progress-slide 1s linear infinite;
}
@-webkit-keyframes nav-progress-slide {
  0%   { background-position: 200% 0; }
  100% { background-position: -100% 0; }
}
@keyframes nav-progress-slide {
  0%   { background-position: 200% 0; }
  100% { background-position: -100% 0; }
}
.nav-pending { opacity: 0.55; pointer-events: none; }
.nav-locked  { pointer-events: none; }

/* ================================================================
   FINALS LIST — championship match cards (GAL Finals & Ignite Cup)
   Broadcast-graphic feel: dark glass, gold accents, Anton condensed
   scoreboard, JetBrains Mono captions, Instrument Serif italic 'vs'.
   ================================================================ */

.finals {
  --gold:        #F5C72F;
  --gold-soft:   rgba(245, 199, 47, 0.85);
  --gold-line:   rgba(245, 199, 47, 0.22);
  --gold-glow:   rgba(245, 199, 47, 0.45);
  --ink-card:    rgba(8, 4, 24, 0.62);
  --ink-rail:    rgba(0, 0, 0, 0.35);
  --paper:       #fff5e1;
  --paper-soft:  rgba(255, 245, 225, 0.72);
  --paper-mute:  rgba(255, 245, 225, 0.48);

  width: 100%;
  max-width: 880px;
  margin: 0 auto;
  padding: 1.5rem clamp(0.75rem, 3vw, 1.75rem) 6rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

@media (min-width: 720px) { .finals { gap: 1.5rem; } }

.finals__empty {
  margin: 4rem auto;
  text-align: center;
  color: rgba(255, 245, 225, 0.72);
  max-width: 28rem;
}
.finals__empty-hint {
  font-family: 'Instrument Serif', serif;
  font-style: italic;
  font-size: 1.6rem;
  margin: 0 0 0.4rem;
  color: #fff5e1;
}
.finals__empty-sub {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(255, 245, 225, 0.48);
  line-height: 1.6;
}

.finals__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: inherit;
}

/* Anchor wrapping the entire card so the whole tile is tappable. */
.match-card__link {
  display: block;
  color: inherit;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.match-card__link:focus { outline: none; }

.match-card {
  position: relative;
  background: var(--ink-card);
  backdrop-filter: blur(22px) saturate(160%);
  -webkit-backdrop-filter: blur(22px) saturate(160%);
  border: 1px solid var(--gold-line);
  border-radius: 14px;
  overflow: hidden;
  box-shadow:
    0 14px 38px rgba(0, 0, 0, 0.50),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
  opacity: 0;
  transform: translateY(14px);
  animation: card-rise 600ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
  animation-delay: var(--card-delay, 0ms);
  transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1),
              border-color 220ms ease,
              box-shadow 220ms ease;
}

.match-card:hover {
  transform: translateY(-2px);
  border-color: var(--gold-glow);
  box-shadow:
    0 20px 50px rgba(0, 0, 0, 0.55),
    0 0 0 1px var(--gold-glow),
    inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

.match-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 60%;
  background: radial-gradient(120% 80% at 50% -20%,
    rgba(245, 199, 47, 0.10),
    transparent 65%);
  pointer-events: none;
  z-index: 0;
}

@keyframes card-rise {
  to { opacity: 1; transform: translateY(0); }
}

.match-card__rail {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.55rem 0.9rem;
  background: var(--ink-rail);
  border-bottom: 1px solid var(--gold-line);
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.match-card__cup {
  text-align: center;
  color: var(--gold);
  font-weight: 500;
  font-size: 0.62rem;
}

.match-card__body {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  align-items: center;
  gap: clamp(0.5rem, 2vw, 1.25rem);
  padding: clamp(1rem, 3vw, 1.75rem) clamp(1.25rem, 4.5vw, 2.5rem);
}

.match-card__side {
  display: flex;
  align-items: center;
  /* space-between pushes team to the outer edge and the score to the
     inner edge of the side — score lands at the midpoint between the
     team card and the centre cup. When the team isn't decided yet
     there's no score, so the side falls back to packing the team toward
     the centre (inner edge). */
  justify-content: space-between;
  gap: clamp(1rem, 4vw, 2.2rem);
  min-width: 0;
  text-align: center;
}
.match-card__side--tbd { justify-content: center; }

.match-card__teamcol {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  text-align: center;
}

.match-card__logo {
  width: clamp(56px, 13vw, 84px);
  height: clamp(56px, 13vw, 84px);
  object-fit: contain;
  border-radius: 50%;
  background: rgba(255, 245, 225, 0.06);
  padding: 5px;
  border: 1px solid rgba(255, 245, 225, 0.12);
  display: block;
}
.match-card__logo--blank {
  background: rgba(255, 245, 225, 0.03);
  border: 1px dashed rgba(255, 245, 225, 0.18);
}
/* TBD variant — same circle as the regular logo but carries a '?'
   glyph so the placeholder reads as "team not yet decided" without
   needing a separate seed label. */
.match-card__logo--unknown {
  display: grid;
  place-items: center;
  font-family: 'Anton', 'Bebas Neue', sans-serif;
  font-size: clamp(28px, 6vw, 42px);
  line-height: 1;
  color: rgba(255, 245, 225, 0.45);
  background: rgba(255, 245, 225, 0.04);
  border: 1px dashed rgba(255, 245, 225, 0.22);
  padding: 0;
}

.match-card__team {
  font-family: 'Anton', 'Oswald', sans-serif;
  font-weight: 400;
  font-size: clamp(1.05rem, 3.4vw, 1.5rem);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--paper);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.match-card__team--seed {
  font-family: 'Instrument Serif', serif;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(0.9rem, 2.8vw, 1.1rem);
  text-transform: none;
  letter-spacing: 0;
  color: var(--paper-soft);
}
.match-card__sub {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper-mute);
  white-space: nowrap;
}

.match-card__centre {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding: 0 0.25rem;
  min-width: 0;
}

.match-card__digit {
  font-family: 'Anton', 'Oswald', sans-serif;
  font-weight: 400;
  font-size: clamp(2.8rem, 9.5vw, 4.2rem);
  color: var(--paper);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
  line-height: 0.9;
}
.match-card__digit--win {
  color: var(--gold);
  text-shadow:
    0 0 18px rgba(245, 199, 47, 0.5),
    0 2px 12px rgba(0, 0, 0, 0.5);
}
.match-card__vs {
  font-family: 'Instrument Serif', serif;
  font-style: italic;
  font-size: clamp(1.6rem, 5vw, 2.2rem);
  color: var(--gold-soft);
  line-height: 1;
  letter-spacing: -0.01em;
}

.match-card__cup-icon {
  width: clamp(44px, 10vw, 64px);
  height: clamp(44px, 10vw, 64px);
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 4px 10px rgba(245, 199, 47, 0.32))
          drop-shadow(0 0 14px rgba(245, 199, 47, 0.20));
}
.match-card__pens {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--paper-soft);
}

.match-card__status {
  margin-top: 0.2rem;
  padding: 0.32em 0.7em;
  border-radius: 999px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.58rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid rgba(255, 245, 225, 0.18);
  background: rgba(255, 245, 225, 0.06);
  color: var(--paper-soft);
}
/* GAL match-status palette (mirrors source-v6/static/css/style.css:4295) */
.match-card--unscheduled .match-card__status {
  color: rgba(255, 245, 225, 0.55);
  background: rgba(128, 128, 128, 0.16);
  border-color: rgba(128, 128, 128, 0.32);
}
.match-card--scheduled .match-card__status {
  color: #0aacff;
  background: rgba(10, 172, 255, 0.16);
  border-color: rgba(10, 172, 255, 0.42);
}
.match-card--full-time .match-card__status {
  color: #c8a4ff;
  background: rgba(120, 57, 195, 0.18);
  border-color: rgba(120, 57, 195, 0.50);
  box-shadow: 0 0 0 3px rgba(120, 57, 195, 0.08);
}
.match-card--first-half .match-card__status,
.match-card--halftime .match-card__status,
.match-card--second-half .match-card__status,
.match-card--penalties .match-card__status {
  color: #fff;
  background: #10b981;
  border-color: rgba(16, 185, 129, 0.85);
  animation: live-throb 1.6s ease-in-out infinite;
}
.match-card--suspended .match-card__status {
  color: #fff;
  background: #ef4444;
  border-color: rgba(239, 68, 68, 0.85);
}
.match-card--postponed .match-card__status {
  color: #ff7c1f;
  background: rgba(255, 124, 31, 0.20);
  border-color: rgba(255, 124, 31, 0.50);
}
.match-card--cancelled .match-card__status,
.match-card--walkover .match-card__status {
  color: #f2167c;
  background: rgba(242, 22, 124, 0.14);
  border-color: rgba(242, 22, 124, 0.42);
}
@keyframes live-throb {
  0%, 100% { opacity: 1;    transform: scale(1);    }
  50%      { opacity: 0.78; transform: scale(0.97); }
}

.match-card__foot {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.55rem 0.9rem;
  border-top: 1px solid var(--gold-line);
  background: var(--ink-rail);
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper-soft);
}
.match-card__foot .match-card__date {
  flex: 0 0 auto;
  text-align: left;
  color: var(--paper-soft);
}
.match-card__foot .match-card__time {
  flex: 0 0 auto;
  text-align: right;
  color: var(--paper);
}

@media (max-width: 420px) {
  .match-card__body { grid-template-columns: 1fr minmax(80px, auto) 1fr; }
  .match-card__sub { display: none; }
  .match-card__cup { font-size: 0.54rem; letter-spacing: 0.12em; }
}

@media (prefers-reduced-motion: reduce) {
  .match-card { animation: none; opacity: 1; transform: none; }
  .match-card--live .match-card__status { animation: none; }
}

/* ================================================================
   MATCH DETAIL — bigger scoreboard hero for /festival/match/<id>
   ================================================================ */

.match-detail {
  --gold:       #F5C72F;
  --gold-soft:  rgba(245, 199, 47, 0.85);
  --gold-line:  rgba(245, 199, 47, 0.24);
  --ink-card:   rgba(8, 4, 24, 0.62);
  --ink-rail:   rgba(0, 0, 0, 0.30);
  --paper:      #fff5e1;
  --paper-soft: rgba(255, 245, 225, 0.72);
  --paper-mute: rgba(255, 245, 225, 0.42);

  width: 100%;
  max-width: 900px;
  margin: 0 auto;
  padding: 1.5rem clamp(0.75rem, 3vw, 1.75rem) 6rem;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* ─── HEADER BAND ─────────────────────────────────────── */
.match-detail__top {
  background: var(--ink-card);
  backdrop-filter: blur(22px) saturate(160%);
  -webkit-backdrop-filter: blur(22px) saturate(160%);
  border: 1px solid var(--gold-line);
  border-radius: 14px;
  padding: 1rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  align-items: center;
  text-align: center;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.40);
}
.match-detail__cup {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.68rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
  font-weight: 500;
}
.match-detail__when {
  font-family: 'Anton', sans-serif;
  font-size: clamp(1.4rem, 4vw, 2.1rem);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--paper);
  line-height: 1;
  display: inline-flex;
  align-items: baseline;
  gap: 0.45rem;
}
.match-detail__dot {
  color: var(--gold-soft);
  font-size: 0.7em;
}
.match-detail__where {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--paper-soft);
}

/* ─── SCOREBOARD ──────────────────────────────────────── */
.match-detail__scoreboard {
  background: var(--ink-card);
  backdrop-filter: blur(22px) saturate(160%);
  -webkit-backdrop-filter: blur(22px) saturate(160%);
  border: 1px solid var(--gold-line);
  border-radius: 18px;
  padding: clamp(1.5rem, 5vw, 2.5rem) clamp(0.75rem, 3vw, 1.75rem);
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  align-items: center;
  gap: clamp(0.5rem, 2vw, 1.5rem);
  position: relative;
  overflow: hidden;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.50);
}
.match-detail__scoreboard::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 60%;
  background: radial-gradient(120% 80% at 50% -20%, rgba(245, 199, 47, 0.12), transparent 65%);
  pointer-events: none;
  z-index: 0;
}

/* ─── SIDE (mirror of match-card__side, scaled up) ───── */
.match-detail__side {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(1rem, 4vw, 2.4rem);
  min-width: 0;
  text-align: center;
}
.match-detail__side--tbd { justify-content: center; }

.match-detail__teamcol {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-align: center;
}
.match-detail__logo {
  width: clamp(56px, 12vw, 96px);
  height: clamp(56px, 12vw, 96px);
  object-fit: contain;
  border-radius: 50%;
  background: rgba(255, 245, 225, 0.08);
  border: 1px solid rgba(255, 245, 225, 0.18);
  padding: 6px;
  display: block;
}
.match-detail__logo--blank {
  background: rgba(255, 245, 225, 0.04);
  border: 1px dashed rgba(255, 245, 225, 0.18);
}
/* TBD: dashed circle with a centered '?' — mirrors the finals-list
   card's placeholder so the match detail header reads consistently. */
.match-detail__logo--unknown {
  display: grid;
  place-items: center;
  font-family: 'Anton', 'Bebas Neue', sans-serif;
  font-size: clamp(32px, 7vw, 56px);
  line-height: 1;
  color: rgba(255, 245, 225, 0.45);
  background: rgba(255, 245, 225, 0.04);
  border: 1px dashed rgba(255, 245, 225, 0.22);
  padding: 0;
}
.match-detail__team {
  font-family: 'Anton', sans-serif;
  font-size: clamp(1.2rem, 3.6vw, 1.8rem);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--paper);
  line-height: 1;
}
.match-detail__team--seed {
  font-family: 'Instrument Serif', serif;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1rem, 3vw, 1.25rem);
  text-transform: none;
  letter-spacing: 0;
  color: var(--paper-soft);
}

/* ─── CENTRE (cup icon + pens + status) ──────────────── */
.match-detail__centre {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  min-width: 0;
}
.match-detail__cup-icon {
  width: clamp(48px, 11vw, 80px);
  height: clamp(48px, 11vw, 80px);
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 4px 12px rgba(245, 199, 47, 0.35))
          drop-shadow(0 0 14px rgba(245, 199, 47, 0.22));
}

/* ─── SCORE DIGITS (live in the side, near the cup) ──── */
.match-detail__digit {
  font-family: 'Anton', sans-serif;
  font-size: clamp(3.5rem, 12vw, 6rem);
  color: var(--paper);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 4px 16px rgba(0, 0, 0, 0.55);
  line-height: 0.9;
  flex: 0 0 auto;
}
.match-detail__digit--win {
  color: var(--gold);
  text-shadow:
    0 0 24px rgba(245, 199, 47, 0.55),
    0 4px 16px rgba(0, 0, 0, 0.55);
}
.match-detail__pens {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--paper-soft);
}
.match-detail__status {
  margin-top: 0.25rem;
  padding: 0.4em 0.85em;
  border-radius: 999px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.66rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid rgba(255, 245, 225, 0.18);
  background: rgba(255, 245, 225, 0.06);
  color: var(--paper-soft);
}

/* status palette — same as match-card */
.match-detail--unscheduled .match-detail__status {
  color: rgba(255, 245, 225, 0.55);
  background: rgba(128, 128, 128, 0.16);
  border-color: rgba(128, 128, 128, 0.32);
}
.match-detail--scheduled .match-detail__status {
  color: #0aacff;
  background: rgba(10, 172, 255, 0.16);
  border-color: rgba(10, 172, 255, 0.42);
}
.match-detail--full-time .match-detail__status {
  color: #c8a4ff;
  background: rgba(120, 57, 195, 0.18);
  border-color: rgba(120, 57, 195, 0.50);
}
.match-detail--first-half .match-detail__status,
.match-detail--halftime .match-detail__status,
.match-detail--second-half .match-detail__status,
.match-detail--penalties .match-detail__status {
  color: #fff;
  background: #10b981;
  border-color: rgba(16, 185, 129, 0.85);
  animation: live-throb 1.6s ease-in-out infinite;
}
.match-detail--suspended .match-detail__status {
  color: #fff; background: #ef4444; border-color: rgba(239, 68, 68, 0.85);
}
.match-detail--postponed .match-detail__status {
  color: #ff7c1f;
  background: rgba(255, 124, 31, 0.20);
  border-color: rgba(255, 124, 31, 0.50);
}
.match-detail--cancelled .match-detail__status,
.match-detail--walkover .match-detail__status {
  color: #f2167c;
  background: rgba(242, 22, 124, 0.14);
  border-color: rgba(242, 22, 124, 0.42);
}

@media (max-width: 480px) {
  .match-detail__teamsub { display: none; }
  .match-detail__where   { letter-spacing: 0.12em; font-size: 0.58rem; }
}

/* ================================================================
   MATCH AWARDS — MVP and Best GK as two glass tiles side by side.
   ================================================================ */

.match-awards {
  width: 100%;
  max-width: 900px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(0.75rem, 2vw, 1.25rem);
}
@media (max-width: 640px) { .match-awards { grid-template-columns: 1fr; } }

.match-award {
  background: rgba(8, 4, 24, 0.62);
  backdrop-filter: blur(22px) saturate(160%);
  -webkit-backdrop-filter: blur(22px) saturate(160%);
  border: 1px solid rgba(245, 199, 47, 0.18);
  border-radius: 14px;
  padding: 0.85rem 1rem;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.40);
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  position: relative;
  overflow: hidden;
}
.match-award::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 60%;
  background: radial-gradient(120% 80% at 50% -10%, rgba(245, 199, 47, 0.10), transparent 65%);
  pointer-events: none;
  z-index: 0;
}

.match-award__label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: #F5C72F;
  font-weight: 500;
  position: relative;
  z-index: 1;
}

.match-award__body {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  position: relative;
  z-index: 1;
}

.match-award__photo {
  width: 54px;
  height: 54px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: rgba(255, 245, 225, 0.08);
  border: 1px solid rgba(255, 245, 225, 0.20);
  display: grid;
  place-items: center;
  color: rgba(255, 245, 225, 0.55);
  overflow: hidden;
}
.match-award__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.match-award__txt {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.match-award__name {
  font-family: 'Anton', sans-serif;
  font-size: clamp(1rem, 2.8vw, 1.25rem);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #fff5e1;
  line-height: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.match-award__team {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 245, 225, 0.7);
}
.match-award__crest {
  width: 18px;
  height: 18px;
  object-fit: contain;
  border-radius: 50%;
  background: rgba(255, 245, 225, 0.08);
  padding: 2px;
  border: 1px solid rgba(255, 245, 225, 0.18);
  display: block;
}

/* ================================================================
   LIVE STREAM — YouTube embed in a glass card with 16:9 frame.
   ================================================================ */

.match-stream {
  width: 100%;
  max-width: 900px;
  margin: 0 auto;
  background: rgba(8, 4, 24, 0.62);
  backdrop-filter: blur(22px) saturate(160%);
  -webkit-backdrop-filter: blur(22px) saturate(160%);
  border: 1px solid rgba(245, 199, 47, 0.18);
  border-radius: 14px;
  padding: 0.85rem;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.40);
}
.match-stream__label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: #F5C72F;
  font-weight: 500;
  margin-bottom: 0.6rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
}
/* Pulsing red dot only on Live Stream variant. Game Footage gets no dot. */
.match-stream--live-stream .match-stream__label::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #ef4444;
  box-shadow: 0 0 8px #ef4444;
  animation: live-throb 1.6s ease-in-out infinite;
}
/* Soft gold dot for the Waiting for Kickoff variant (Scheduled w/ link). */
.match-stream--waiting-for-kickoff .match-stream__label::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #F5C72F;
  box-shadow: 0 0 8px rgba(245, 199, 47, 0.7);
  animation: live-throb 2.4s ease-in-out infinite;
}
.match-stream__frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 10px;
  overflow: hidden;
  background: #000;
  border: 1px solid rgba(245, 199, 47, 0.20);
}
.match-stream__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* ================================================================
   LINEUPS — two glass columns, with inline event badges per player.
   ================================================================ */

.match-lineups {
  width: 100%;
  max-width: 900px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(0.75rem, 2vw, 1.25rem);
}
@media (max-width: 640px) {
  .match-lineups { grid-template-columns: 1fr; }
}

.match-lineups__col {
  background: rgba(8, 4, 24, 0.62);
  backdrop-filter: blur(22px) saturate(160%);
  -webkit-backdrop-filter: blur(22px) saturate(160%);
  border: 1px solid rgba(245, 199, 47, 0.18);
  border-radius: 14px;
  padding: 0.85rem 0;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.40);
  overflow: hidden;
}

.match-lineups__header {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0 1rem 0.7rem;
  border-bottom: 1px solid rgba(245, 199, 47, 0.12);
}
.match-lineups__crest img,
.match-lineups__crest--blank {
  width: 32px;
  height: 32px;
  object-fit: contain;
  border-radius: 50%;
  background: rgba(255, 245, 225, 0.08);
  border: 1px solid rgba(255, 245, 225, 0.18);
  padding: 3px;
  display: block;
}
.match-lineups__crest--blank {
  background: rgba(255, 245, 225, 0.04);
  border-style: dashed;
}
.match-lineups__teamname {
  flex: 1 1 auto;
  font-family: 'Anton', sans-serif;
  font-size: 1rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #fff5e1;
  line-height: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.match-lineups__count {
  flex: 0 0 auto;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  color: #F5C72F;
  background: rgba(245, 199, 47, 0.10);
  border: 1px solid rgba(245, 199, 47, 0.30);
  border-radius: 999px;
  padding: 0.18em 0.55em;
  min-width: 1.6em;
  text-align: center;
}

.match-lineups__list {
  list-style: none;
  margin: 0;
  padding: 0.4rem 0;
}
.match-lineups__empty {
  margin: 0;
  padding: 1.2rem 1rem;
  font-family: 'Instrument Serif', serif;
  font-style: italic;
  color: rgba(255, 245, 225, 0.55);
  text-align: center;
}

/* Skeleton states: MVP/GK without an assignee, missing stream, and
   empty lineup columns. All share the same muted treatment so the
   page reads as a consistent "data pending" surface rather than a
   blank section. */
.match-award--placeholder {
  cursor: default;
  border-color: rgba(255, 255, 255, 0.10);
}
.match-award--placeholder .match-award__photo {
  background: rgba(255, 255, 255, 0.05);
  color: rgba(255, 255, 255, 0.32);
}
.match-award--placeholder .match-award__name {
  color: rgba(255, 245, 225, 0.55);
  font-family: 'Anton', 'Bebas Neue', sans-serif;
  font-style: normal;
  letter-spacing: 0.06em;
}
/* Unknown award photo: same circle as the regular avatar but
   centered '?' instead of a silhouette. */
.match-award__photo--unknown {
  display: grid;
  place-items: center;
  font-family: 'Anton', 'Bebas Neue', sans-serif;
  font-size: 30px;
  line-height: 1;
  color: rgba(255, 245, 225, 0.45);
  background: rgba(255, 245, 225, 0.04);
  border: 1px dashed rgba(255, 245, 225, 0.22);
}
.match-award--placeholder .match-award__team span {
  color: rgba(255, 255, 255, 0.32);
}

.match-stream--placeholder .match-stream__frame {
  background: rgba(8, 4, 24, 0.55);
}
.match-stream__placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  color: rgba(255, 245, 225, 0.55);
  font-family: 'Instrument Serif', serif;
  font-style: italic;
  font-size: 1.05rem;
  letter-spacing: 0.02em;
  background:
    repeating-linear-gradient(
      45deg,
      rgba(255, 255, 255, 0.015) 0 8px,
      transparent 8px 18px
    );
}

.match-lineups__list--placeholder { opacity: 0.55; }
.match-player--placeholder {
  pointer-events: none;
  cursor: default;
}
.match-player--placeholder .match-player__photo {
  background: rgba(255, 255, 255, 0.05);
  color: rgba(255, 255, 255, 0.28);
}
.match-player--placeholder .match-player__name {
  color: rgba(255, 245, 225, 0.45);
  font-family: 'Instrument Serif', serif;
  font-style: italic;
  letter-spacing: 0.08em;
}

.match-player {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 1rem;
  transition: background 160ms ease;
}
.match-player + .match-player { border-top: 1px solid rgba(255, 245, 225, 0.06); }
.match-player:hover { background: rgba(255, 245, 225, 0.04); }

.match-player__photo {
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: rgba(255, 245, 225, 0.08);
  border: 1px solid rgba(255, 245, 225, 0.16);
  display: grid;
  place-items: center;
  color: rgba(255, 245, 225, 0.55);
  overflow: hidden;
}
.match-player__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.match-player__name {
  flex: 1 1 auto;
  min-width: 0;
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 500;
  font-size: 0.85rem;
  color: #fff5e1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.match-player__events {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.3rem;
  flex: 0 0 auto;
  max-width: 60%;
}

.match-event {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4em;
  padding: 0.35em 0.7em;
  /* Uniform footprint across every event type: same height, same min-width. */
  min-width: 3.4em;
  height: 1.8em;
  box-sizing: border-box;
  border-radius: 999px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  line-height: 1;
  white-space: nowrap;
  border: 1px solid transparent;
}
.match-event__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  line-height: 1;
  flex: 0 0 14px;
}
.match-event__icon svg { display: block; }
.match-event__min {
  font-variant-numeric: tabular-nums;
}

/* Goals — base + regular (default green), penalty (amber), own-goal (red). */
.match-event--goal,
.match-event--regular {
  color: #10b981;
  background: rgba(16, 185, 129, 0.14);
  border-color: rgba(16, 185, 129, 0.42);
}
.match-event--penalty {
  color: #f59e0b;
  background: rgba(245, 158, 11, 0.14);
  border-color: rgba(245, 158, 11, 0.45);
}
.match-event--own-goal {
  color: #ef4444;
  background: rgba(239, 68, 68, 0.14);
  border-color: rgba(239, 68, 68, 0.45);
}

/* Cards — yellow, red, and "Second Yellow (red)". Icon is an SVG with
   fill:currentColor, so the badge's `color` value is the card colour. */
.match-event--yellow {
  color: #fde047;
  background: rgba(245, 158, 11, 0.16);
  border-color: rgba(245, 158, 11, 0.55);
}
.match-event--red,
.match-event--second-yellow-red {
  color: #ef4444;
  background: rgba(239, 68, 68, 0.14);
  border-color: rgba(239, 68, 68, 0.55);
}

@media (max-width: 480px) {
  .match-player__name { font-size: 0.8rem; }
  .match-player__events { max-width: 65%; gap: 0.22rem; }
  .match-event { font-size: 0.58rem; padding: 0.32em 0.6em; }
}

/* ================================================================
   FESTIVAL MAP — full-bleed Leaflet canvas on /festival/maps
   ================================================================
   The map fills the area below the topbar to the viewport bottom.
   `.app-shell` already pads the topbar height in, so the inner map
   container just absolute-fills its parent. Leaflet's own panes are
   transparent over the host body's gradient — the dark CartoDB tiles
   blend into the festival's dark theme. Zoom controls are styled to
   match the glass aesthetic of the rest of the app.
   ================================================================ */
.festival-map {
  position: absolute;
  inset: 0;
}
#festivalMap {
  position: absolute;
  inset: 0;
  background: transparent;
}

/* Custom marker — Leaflet's .leaflet-div-icon class adds white bg +
   1px border by default; both stripped so only the SVG teardrop shows.
   Drop-shadow gives the pin lift over the satellite imagery. */
.festival-pin {
  background: transparent;
  border: none;
}
.festival-pin svg {
  display: block;
  width: 32px;
  height: 44px;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55));
  transition: transform 160ms cubic-bezier(0.22, 0.61, 0.36, 1);
  transform-origin: 50% 100%;
  cursor: pointer;
}
.festival-pin:hover svg { transform: scale(1.12); }

/* Venue modal — festival glass card triggered by map pin clicks. */
.venue-modal {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.venue-modal[hidden] { display: none; }
.venue-modal__backdrop {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(80% 60% at 50% 40%, rgba(40, 12, 60, 0.55), rgba(4, 2, 14, 0.92) 75%),
    rgba(0, 0, 0, 0.78);
  backdrop-filter: blur(8px) saturate(140%);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  animation: venue-modal-fade 180ms ease-out;
}
.venue-modal__card {
  position: relative;
  width: min(420px, 100%);
  border-radius: 22px;
  padding: 40px 28px;
  background:
    linear-gradient(180deg, rgba(36, 18, 72, 0.78) 0%, rgba(12, 6, 28, 0.86) 100%);
  backdrop-filter: blur(26px) saturate(170%);
  -webkit-backdrop-filter: blur(26px) saturate(170%);
  border: 1px solid rgba(245, 199, 47, 0.32);
  box-shadow:
    0 30px 70px rgba(0, 0, 0, 0.55),
    0 1px 0 rgba(255, 255, 255, 0.06) inset;
  color: #fff;
  animation: venue-modal-pop 220ms cubic-bezier(0.16, 1, 0.3, 1);
  text-align: center;
}
.venue-modal__close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 22px;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background 140ms ease, transform 140ms ease;
}
.venue-modal__close:hover {
  background: rgba(245, 199, 47, 0.22);
  transform: rotate(90deg);
}
.venue-modal__title {
  font-family: 'Anton', 'Bebas Neue', sans-serif;
  font-size: 36px;
  letter-spacing: 0.06em;
  margin: 0 0 28px;
  color: #F5C72F;
  text-transform: uppercase;
}
.venue-modal__apps {
  display: flex;
  justify-content: space-evenly;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 1.25rem 0;  /* row gap only — horizontal handled by space-evenly */
  width: 100%;
}
.venue-modal__apps .app-tile { color: #fff; text-decoration: none; }
body.venue-modal-open { overflow: hidden; }

@keyframes venue-modal-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes venue-modal-pop {
  from { opacity: 0; transform: translateY(8px) scale(0.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .venue-modal__card,
  .venue-modal__backdrop { animation: none; }
}

/* Glass Leaflet zoom controls. */
.leaflet-bar,
.leaflet-bar a,
.leaflet-bar a:hover {
  background: rgba(8, 4, 24, 0.62);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  color: #fff;
  border-color: rgba(245, 199, 47, 0.32);
}
.leaflet-bar a:hover { background: rgba(245, 199, 47, 0.18); }
.leaflet-control-attribution {
  background: rgba(8, 4, 24, 0.45) !important;
  color: rgba(255, 255, 255, 0.7) !important;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9.5px !important;
}
.leaflet-control-attribution a {
  color: rgba(245, 199, 47, 0.9) !important;
}

/* ================================================================
   LIVE TICKER — dashboard carousel of in-progress finals
   ================================================================
   Anchored to the bottom of the stage, above Zelix's lane. The rail
   is duplicated once in the template (two render passes) so the
   keyframe animation can translate from 0% to -50% and the loop is
   visually seamless. Hovering pauses the scroll for readability.
   ================================================================ */
.live-ticker {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 4.5rem);
  height: 36px;
  padding: 0;
  z-index: 30;
  pointer-events: none;
}

.live-ticker__badge {
  /* Glass pill flush against the viewport's left edge. Rigid — the
     pill itself never scales, only the letters inside (.badge-word)
     animate via the pulse keyframes below. Pills in the rail scroll
     BEHIND this badge; backdrop-filter frosts them as they pass. */
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  padding: 5px 14px;
  border-radius: 0 999px 999px 0;  /* flat against the left edge */
  background: rgba(8, 4, 24, 0.55);
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid rgba(239, 68, 68, 0.45);
  border-left: none;
  pointer-events: auto;
}

.live-ticker__badge-word {
  display: inline-block;
  font-family: 'Anton', 'Bebas Neue', sans-serif;
  font-size: 14px;
  letter-spacing: 0.22em;
  color: #ef4444;
  transform-origin: center;
  animation: live-word-pulse 1.5s ease-in-out infinite;
}

@keyframes live-word-pulse {
  0%, 100% {
    color: #ef4444;
    text-shadow:
      0 0 6px rgba(239, 68, 68, 0.55),
      0 0 14px rgba(239, 68, 68, 0.25);
    transform: scale(1);
  }
  50% {
    color: #ff7c1f;
    text-shadow:
      0 0 12px rgba(255, 124, 31, 0.85),
      0 0 22px rgba(255, 124, 31, 0.45);
    transform: scale(1.08);
  }
}

@media (prefers-reduced-motion: reduce) {
  .live-ticker__badge-word { animation: none; }
}

/* Empty-state: no live games. Badge goes muted gray and stops
   pulsing, signalling the ticker is informational rather than live.
   The single placeholder pill scrolls right→left across the whole
   viewport via a different keyframe (no duplication needed). */
.live-ticker--empty .live-ticker__badge {
  border-color: rgba(255, 255, 255, 0.22);
  background: rgba(8, 4, 24, 0.55);
}
.live-ticker--empty .live-ticker__badge-word {
  color: rgba(255, 255, 255, 0.55);
  text-shadow: none;
  animation: none;
}

.live-ticker__viewport {
  /* Fills the whole footer row — pills travel the full width and slip
     underneath the LIVE badge on the way out (left side). The badge
     sits above via z-index and frosts them with its backdrop-filter.
     Mask is asymmetric: entry on the RIGHT is sharp (pills appear
     cleanly), exit on the LEFT fades so they disappear into the LIVE
     pill instead of getting cut off. */
  position: absolute;
  inset: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 10%, #000 100%);
          mask-image: linear-gradient(90deg, transparent 0%, #000 10%, #000 100%);
}

.live-ticker__rail {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  width: max-content;
  /* `--ticker-pill-count` is set inline on the rail by the template
     (1 for empty state, otherwise the number of live games). Base
     8s for the entry + exit travel across the viewport, plus 3s per
     pill for the visible scroll past — keeps per-pill speed roughly
     constant regardless of total count. */
  --ticker-pill-count: 1;
  animation:
    live-ticker-scroll
    calc(8s + (var(--ticker-pill-count) * 3s))
    linear infinite;
  pointer-events: auto;
}
.live-ticker__viewport:hover .live-ticker__rail,
.live-ticker__rail:focus-within {
  animation-play-state: paused;
}

.live-ticker__item {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.45rem 0.85rem;
  border-radius: 999px;
  background: rgba(8, 4, 24, 0.62);
  backdrop-filter: blur(22px) saturate(160%);
  -webkit-backdrop-filter: blur(22px) saturate(160%);
  border: 1px solid rgba(245, 199, 47, 0.32);
  color: #fff;
  text-decoration: none;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: transform 140ms ease, border-color 140ms ease;
}
.live-ticker__item:hover {
  transform: translateY(-1px);
  border-color: rgba(245, 199, 47, 0.65);
}

/* Empty state — no live games. Single pill (repeated for loop fill)
   that reads "NO LIVE GAMES" in muted typography, distinguishable
   from real match pills. No green dot, no link behaviour. */
.live-ticker__item--empty {
  cursor: default;
  pointer-events: none;
  border-color: rgba(255, 255, 255, 0.18);
  background: rgba(8, 4, 24, 0.45);
  color: rgba(255, 255, 255, 0.65);
  font-family: 'Anton', 'Bebas Neue', sans-serif;
  font-size: 14px;
  letter-spacing: 0.22em;
  padding: 0.5rem 1.4rem;
}

.live-ticker__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #10b981;
  box-shadow: 0 0 8px rgba(16, 185, 129, 0.7);
  animation: live-ticker-pulse 1.4s ease-in-out infinite;
  flex: 0 0 auto;
}

.live-ticker__comp  { color: #F5C72F; }
.live-ticker__sep   { opacity: 0.45; }
.live-ticker__team  { color: rgba(255, 255, 255, 0.85); }
.live-ticker__score {
  font-family: 'Anton', 'Bebas Neue', sans-serif;
  font-size: 14px;
  letter-spacing: 0.04em;
  color: #fff;
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
}
.live-ticker__dash { opacity: 0.55; }

@keyframes live-ticker-scroll {
  /* Rail enters off-screen right, exits off-screen left, then loops.
     No duplication needed in the markup — when the rail's right
     edge crosses the left edge, the animation restarts from the
     right and the user sees a clean "set ended, set restarts" cycle. */
  from { transform: translateX(100vw); }
  to   { transform: translateX(-100%); }
}
@keyframes live-ticker-pulse {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.5; transform: scale(0.75); }
}

@media (prefers-reduced-motion: reduce) {
  .live-ticker__rail { animation: none; }
  .live-ticker__dot  { animation: none; }
}

@media (max-width: 480px) {
  .live-ticker { bottom: calc(env(safe-area-inset-bottom, 0px) + 4rem); }
  .live-ticker__badge { font-size: 11px; padding: 5px 9px; }
  .live-ticker__item  { font-size: 9.5px; padding: 0.4rem 0.7rem; }
  .live-ticker__score { font-size: 12.5px; }
}

/* ── Dashboard-only overrides ───────────────────────────────────
   When body.has-ticker is set (currently only the festival home), the
   ticker becomes a full-width bottom bar pinned to the viewport edge,
   Zelix runs ON TOP of that bar instead of along the bare viewport
   bottom, and the exit-portal button lifts above the bar so it isn't
   covered. Defined as a scoped override block so the standalone
   ticker styling stays usable elsewhere later.
   ─────────────────────────────────────────────────────────────── */
:root { --ticker-bar-height: 58px; }

body.has-ticker .live-ticker {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: var(--ticker-bar-height);
  /* No horizontal padding — the inner viewport spans edge-to-edge so
     pills can travel the full width and slip under the LIVE pill.
     Safe-area inset is the only bottom inset. */
  padding: 0 0 env(safe-area-inset-bottom, 0px) 0;
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border: none;
  box-shadow: none;
  mask-image: none;
  -webkit-mask-image: none;
  z-index: 40;
}

body.has-ticker .loyalty-mascot {
  /* The bar is invisible — Zelix sits on top of the visible PILLS,
     which are flex-centered in the bar's height. Pill is ~30px tall,
     bar is 58px → pill top sits at (58 + 30) / 2 = 44px from the
     viewport bottom. -2px nudge accounts for the sprite's empty
     pixels so the visible feet land on the pill's top edge. */
  bottom: calc(42px + env(safe-area-inset-bottom, 0px));
  z-index: 41;
}

body.has-ticker .exit-portal {
  /* Lift the back-to-GAL glass button above the new bottom bar so it
     reads as elevated and never clips into the ticker. The hover
     translate is unchanged — it still rises another 2px on hover. */
  bottom: calc(var(--ticker-bar-height) + env(safe-area-inset-bottom, 0px) + 1rem);
}

body.has-ticker .stage {
  /* Make room for the bottom bar so the centred app-grid doesn't drift
     into it. Replaces the default 3.5rem bottom inset. */
  padding-bottom: calc(var(--ticker-bar-height) + env(safe-area-inset-bottom, 0px) + 1.5rem);

  /* When the tile grid is TALLER than the viewport (e.g., 7 tiles on
     a portrait phone), the default `align-items: center` would push
     the top of the grid up past the topbar where it gets clipped.
     `safe center` keeps the centred layout when there's room but
     falls back to `flex-start` when content overflows. We also drop
     the inherited `height: 100%` + `overflow: hidden` so the parent
     `.app-shell` can scroll the overflow naturally. */
  height: auto;
  min-height: 100%;
  overflow: visible;
  align-items: safe center;
}

@media (max-width: 480px) {
  body.has-ticker { /* sm: slightly shorter bar */ }
  body.has-ticker .live-ticker { height: 52px; }
  body.has-ticker .loyalty-mascot {
    /* Mobile pills ~24px, bar 52px → pill top at 38px. -2px sprite. */
    bottom: calc(36px + env(safe-area-inset-bottom, 0px));
  }
  body.has-ticker .exit-portal {
    bottom: calc(52px + env(safe-area-inset-bottom, 0px) + 0.75rem);
  }
}

/* ================================================================
   FESTIVAL AD POPUP — REMOVED 2026-05-29 (per request). The festival
   is ad-free; sponsor ads run in the MAIN GAL app only. The .festival-
   ad-popup* styles + their keyframes lived here and were removed with
   the popup block in grandfinal/base.html. Recover from git history if
   festival ads ever return.
   ================================================================ */

/* ================================================================
   ZELIX — loyalty mascot, festival edition
   ================================================================
   Same sprite + frame cycle as GAL, but no bottom-nav here so Zelix
   runs along the viewport bottom edge directly. JS-driven horizontal
   travel + click-to-flip-and-escape lives in grandfinal/js/zelix.js.
   Sprite sheet: 2x2 grid, each frame 313x475, rendered at 40x60.
   ================================================================ */
.loyalty-mascot {
  position: fixed;
  /* Anchored via `bottom` so Zelix tracks the visible viewport on iOS
     (vh would drift behind the toolbar). -2px nudges the sprite's
     visible feet flush with the viewport edge — the rendered sprite
     has a sliver of empty pixels at the bottom of each frame. */
  bottom: calc(env(safe-area-inset-bottom, 0px) - 2px);
  left: -56px;
  width: 40px;
  height: 60px;
  z-index: 50;
  pointer-events: auto;
  cursor: pointer;
  background-image: url('/static/images/zelix-run-blink.png');
  background-repeat: no-repeat;
  background-size: 200% 200%;
  background-position: 0% 0%;
  /* Two stacked drop-shadows: tight near shadow that hugs the silhouette,
     softer wider shadow for ground distance. Warm tint reads better
     against the festival's dark navy than pure black. */
  filter:
    drop-shadow(0 1px 0   rgba(40, 20, 0, 0.45))
    drop-shadow(0 6px 10px rgba(40, 20, 0, 0.55));
  animation: zelix-frames 0.4s steps(1, end) infinite;
  will-change: left, transform, background-position, filter;
}

@keyframes zelix-frames {
  0%   { background-position: 0%   0%; }
  25%  { background-position: 100% 0%; }
  50%  { background-position: 0%   100%; }
  75%  { background-position: 100% 100%; }
  100% { background-position: 0%   0%; }
}

@media (prefers-reduced-motion: reduce) {
  .loyalty-mascot { display: none; }
}

/* ================================================================
   PLAYER STATS MODAL — festival edition
   ================================================================
   Triggered by clicks on lineup rows, MVP tile, or GK tile in the
   match detail view. Fetches season totals from
   /festival/api/player-team-stats and renders them in a dark glass
   card with gold accents and oversized Anton stat numbers.
   ================================================================ */
.player-modal {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.player-modal[hidden] { display: none; }

.player-modal__backdrop {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(80% 60% at 50% 40%, rgba(40, 12, 60, 0.55), rgba(4, 2, 14, 0.92) 75%),
    rgba(0, 0, 0, 0.78);
  backdrop-filter: blur(8px) saturate(140%);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  animation: player-modal-fade 180ms ease-out;
}

.player-modal__card {
  position: relative;
  width: min(440px, 100%);
  border-radius: 22px;
  padding: 28px 24px 22px;
  background:
    linear-gradient(180deg, rgba(36, 18, 72, 0.78) 0%, rgba(12, 6, 28, 0.86) 100%);
  backdrop-filter: blur(26px) saturate(170%);
  -webkit-backdrop-filter: blur(26px) saturate(170%);
  border: 1px solid rgba(245, 199, 47, 0.32);
  box-shadow:
    0 30px 70px rgba(0, 0, 0, 0.55),
    0 0 0 1px rgba(255, 255, 255, 0.04) inset,
    0 1px 0 rgba(255, 255, 255, 0.06) inset;
  animation: player-modal-pop 220ms cubic-bezier(0.16, 1, 0.3, 1);
  color: #fff;
}

.player-modal__close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background 140ms ease, transform 140ms ease;
}
.player-modal__close:hover {
  background: rgba(245, 199, 47, 0.22);
  transform: rotate(90deg);
}

.player-modal__header {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 22px;
  padding-bottom: 18px;
  border-bottom: 1px solid rgba(245, 199, 47, 0.18);
}

.player-modal__photo {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  overflow: hidden;
  flex: 0 0 80px;
  background:
    linear-gradient(135deg, rgba(245, 199, 47, 0.25), rgba(245, 199, 47, 0.06));
  border: 1px solid rgba(245, 199, 47, 0.45);
  display: grid;
  place-items: center;
}
.player-modal__photo img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  /* Anchor faces near the top of the crop the way GAL's profile does —
     most photos are portrait shoulders-up, so default center crop slices
     into the forehead. `center top` keeps the face intact. */
  object-position: center top;
  display: block;
}

.player-modal__id { min-width: 0; }
.player-modal__name {
  font-family: 'Anton', 'Bebas Neue', sans-serif;
  font-size: 26px;
  line-height: 1.05;
  letter-spacing: 0.02em;
  color: #fff;
  text-transform: uppercase;
  margin-bottom: 4px;
  word-break: break-word;
}
.player-modal__yob {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12px;
  letter-spacing: 0.14em;
  color: #F5C72F;
}

.player-modal__stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}
.player-modal__stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 16px 6px 12px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.06);
  position: relative;
  overflow: hidden;
}
.player-modal__stat::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(120, 57, 195, 0.22), transparent 60%);
  pointer-events: none;
}
.player-modal__stat--yellow::before {
  background: linear-gradient(180deg, rgba(250, 204, 21, 0.22), transparent 60%);
}
.player-modal__stat--red::before {
  background: linear-gradient(180deg, rgba(239, 68, 68, 0.22), transparent 60%);
}

.player-modal__stat-num {
  font-family: 'Anton', 'Bebas Neue', sans-serif;
  font-size: 38px;
  line-height: 1;
  color: #7839c3;
  letter-spacing: 0.02em;
  position: relative;
}
.player-modal__stat--yellow .player-modal__stat-num { color: #facc15; }
.player-modal__stat--red    .player-modal__stat-num { color: #ef4444; }

.player-modal__stat-label {
  margin-top: 8px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.58);
  text-align: center;
  position: relative;
}

.player-modal__footnote {
  margin-top: 16px;
  text-align: center;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.36);
}

.player-modal__loading,
.player-modal__error {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(12, 6, 28, 0.85);
  backdrop-filter: blur(4px);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-align: center;
  color: rgba(255, 255, 255, 0.7);
  border-radius: 22px;
  word-break: break-word;
  overflow: auto;
  line-height: 1.5;
}
.player-modal__error { color: #ef4444; text-transform: none; }

/* The class rule above sets display:grid, which has equal specificity
   to the UA `[hidden]{display:none}` rule but comes later — so the
   overlay would stay visible even when the JS sets .hidden=true.
   Force `display:none` when [hidden] is present so the overlay
   actually disappears. */
.player-modal__loading[hidden],
.player-modal__error[hidden] {
  display: none !important;
}

/* Make trigger elements feel interactive. */
.js-player-trigger {
  cursor: pointer;
  transition: transform 140ms ease, background 140ms ease;
}
.js-player-trigger:hover {
  transform: translateY(-1px);
}
.js-player-trigger:focus-visible {
  outline: 2px solid #F5C72F;
  outline-offset: 2px;
  border-radius: 8px;
}

body.player-modal-open { overflow: hidden; }

@keyframes player-modal-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes player-modal-pop {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.96);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .player-modal__card,
  .player-modal__backdrop {
    animation: none;
  }
}

@media (max-width: 420px) {
  .player-modal__card { padding: 24px 18px 18px; border-radius: 18px; }
  .player-modal__name { font-size: 22px; }
  .player-modal__stat-num { font-size: 32px; }
  .player-modal__stat { padding: 14px 4px 10px; }
}

/* ================================================================
   .info-* — portable "stage / wrap / header / card" primitives.
   Originally inline in templates/grandfinal/info.html. Promoted here
   so the non_gal_signup / non_gal_pending / event_register /
   my_registrations / setup_invitation pages share the same horizontal
   padding, max-width, and glass-card visual language. Mobile-first:
   max-width keeps content readable on desktop without artificial
   stretching; iOS safe-area insets keep content off the notch.
   ================================================================ */
.info-stage {
  padding: 18px 14px 90px;
  max-width: 680px;
  margin: 0 auto;
  position: relative;
  z-index: 2;
}
.info-stage > .info-wrap { width: 100%; }

.info-head { text-align: center; margin: 6px 0 22px; }
.info-eyebrow {
  margin: 0 0 6px;
  font-family: 'Manrope', sans-serif;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--accent);
  text-shadow: 0 0 14px color-mix(in srgb, var(--accent) 65%, transparent);
}
.info-title {
  margin: 0;
  font-family: 'Unbounded', 'Manrope', sans-serif;
  font-weight: 800;
  font-size: 28px;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: #fff;
  text-shadow:
    0 0 18px color-mix(in srgb, var(--accent-2) 55%, transparent),
    0 0 40px color-mix(in srgb, var(--accent) 35%, transparent);
}
@media (min-width: 520px) {
  .info-title { font-size: 32px; }
}

.info-card {
  margin-bottom: 14px;
  padding: 16px 18px 18px;
  background:
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.055) 0%,
      rgba(255, 255, 255, 0.02) 100%);
  backdrop-filter: blur(14px) saturate(150%);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  border-radius: 16px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.35);
  color: #fff;
}
.info-card h2,
.info-card h3 { color: #fff; }

/* iOS safe-area: keep content off the notch + home indicator without
   blowing out the 14px baseline on standard phones. */
@supports (padding: env(safe-area-inset-left)) {
  .info-stage {
    padding-left:  max(14px, env(safe-area-inset-left));
    padding-right: max(14px, env(safe-area-inset-right));
  }
}

/* ================================================================
   .hg-caret — the hg›tech brand caret, reusable on any CTA.
   Same signature as .hub-btn__caret and the map pins: Syne 800 in
   Filament Gold (#F5C72F) with a single 3D drop shadow (no outline,
   no glow). 4px slide-right on parent hover.
   ================================================================ */
.hg-caret {
  font-family: 'Syne', Georgia, serif;
  font-weight: 800;
  display: inline-block;
  margin-left: 8px;
  line-height: 1;
  font-size: 1.2em;
  vertical-align: -1px;
  color: #F5C72F;                              /* hg-tech Filament Gold */
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.6);   /* 3D drop, same as hub-btn__caret */
  transition: transform 0.18s ease;
  pointer-events: none;                        /* clicks pass through to the button */
}

/* Slide right on hover of the closest interactive parent. Gated behind
   `hover: hover` so the slide doesn't get stuck after a tap on touch. */
@media (hover: hover) {
  a:hover > .hg-caret,
  button:hover > .hg-caret,
  label:hover > .hg-caret {
    transform: translateX(4px);
  }
}

/* ════════════════════════════════════════════════════════════════
   UNIVERSAL TAP FEEDBACK — festival-wide (added 2026-05-29)
   Mirror of the league app's baseline: every interactive control
   greys-in instantly on press. :active paints synchronously on touch
   (nav.js now registers the touchstart that unlocks :active on iOS),
   so feedback is immediate + reliable on iPhone. Components with their
   own :active (.app-tile, .hub-btn, .eo-btn, .vm-btn, .vp-btn…) keep
   their tuned values via higher specificity; this is the baseline for
   everything else (info/store CTAs, Contact tile, links, summaries).
   ════════════════════════════════════════════════════════════════ */
a, button, summary,
[role="button"],
input[type="submit"],
input[type="button"] {
  -webkit-tap-highlight-color: transparent;
}
a:active, button:active, summary:active,
[role="button"]:active,
input[type="submit"]:active,
input[type="button"]:active {
  opacity: 0.6;
}
button:active,
[role="button"]:active,
input[type="submit"]:active,
input[type="button"]:active {
  transform: scale(0.97);
}

/* ════════════════════════════════════════════════════════════════
   MATCH CARD (.mc) — festival VS card for My SMF / My Vortex + the
   public Matches pages. Side A | score | side B; each side stacks its
   player(s) as avatar + name. Glass + gold festival tokens.
   ════════════════════════════════════════════════════════════════ */
.mc {
  background: linear-gradient(180deg, rgba(255,255,255,0.055) 0%, rgba(255,255,255,0.02) 100%);
  border: 1px solid rgba(245, 199, 47, 0.22);
  border-radius: 14px;
  padding: 12px 14px;
  margin-bottom: 12px;
  backdrop-filter: blur(12px) saturate(150%);
  -webkit-backdrop-filter: blur(12px) saturate(150%);
}
.mc--live  { border-color: rgba(242, 22, 124, 0.6); box-shadow: 0 0 22px rgba(242,22,124,0.25); }
.mc--final { border-color: rgba(16, 185, 129, 0.45); }
.mc--cancelled { opacity: 0.6; }

.mc__top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.mc__stage {
  font-size: 10.5px; letter-spacing: 0.12em; text-transform: uppercase;
  font-weight: 700; color: #F5C72F;
}
.mc__badge {
  font-size: 10px; letter-spacing: 0.06em; font-weight: 800;
  padding: 2px 8px; border-radius: 9px; background: rgba(255,255,255,0.12); color: #fff;
}
.mc__badge--live  { background: #f2167c; color: #fff; animation: mc-blink 1.4s ease-in-out infinite; }
.mc__badge--final { background: #10b981; color: #fff; }
.mc__badge--cx    { background: #6b7280; }
@keyframes mc-blink { 0%,100% { opacity: 1; } 50% { opacity: 0.45; } }

.mc__body {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
}
.mc__side { display: flex; flex-direction: column; gap: 10px; }
.mc__side--right { align-items: flex-end; }
.mc__player { display: flex; align-items: center; gap: 9px; }
.mc__side--right .mc__player { flex-direction: row-reverse; text-align: right; }
.mc__avatar {
  width: 44px; height: 44px; border-radius: 50%; overflow: hidden; flex-shrink: 0;
  background: rgba(245,199,47,0.18); border: 1px solid rgba(245,199,47,0.4);
  color: #F5C72F; font-weight: 800;
  display: flex; align-items: center; justify-content: center; font-size: 17px;
}
.mc__avatar img { width: 100%; height: 100%; object-fit: cover; }
.mc__name { font-size: 13px; font-weight: 700; color: #fff; line-height: 1.15; }

.mc__score { text-align: center; min-width: 54px; }
.mc__num  { font-family: 'Unbounded','Manrope',sans-serif; font-weight: 900; font-size: 26px; color: #fff; }
.mc__dash { font-weight: 900; font-size: 20px; color: rgba(255,255,255,0.5); margin: 0 3px; }
.mc__vs   { font-family: 'Unbounded','Manrope',sans-serif; font-weight: 900; font-size: 18px; color: #F5C72F; letter-spacing: 0.04em; }

.mc__foot {
  margin-top: 10px; padding-top: 8px; border-top: 1px solid rgba(255,255,255,0.08);
  font-size: 12px; color: rgba(255,255,255,0.7); text-align: center;
}

@media (max-width: 360px) {
  .mc__avatar { width: 38px; height: 38px; font-size: 15px; }
  .mc__name { font-size: 12px; }
  .mc__num { font-size: 22px; }
}

/* ── Group standings table (.st) ──────────────────────────────────── */
.st-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: 12px; }
.st {
  width: 100%; border-collapse: collapse;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(245,199,47,0.18);
  border-radius: 12px; overflow: hidden;
  font-size: 13px; color: #fff;
}
.st thead th {
  font-size: 10.5px; letter-spacing: 0.04em; text-transform: uppercase;
  font-weight: 700; color: rgba(255,255,255,0.6);
  padding: 8px 6px; text-align: center;
  border-bottom: 1px solid rgba(255,255,255,0.1);
}
.st td { padding: 9px 6px; text-align: center; border-bottom: 1px solid rgba(255,255,255,0.06); }
.st tbody tr:last-child td { border-bottom: none; }
.st .st-pos  { width: 26px; color: rgba(255,255,255,0.55); font-weight: 700; }
.st .st-name { text-align: left; font-weight: 700; white-space: nowrap; }
.st .st-pts  { font-weight: 900; color: #F5C72F; }
/* top-2 qualify highlight */
.st tbody tr:nth-child(1) .st-pos,
.st tbody tr:nth-child(2) .st-pos { color: #10b981; }
.st tbody tr:nth-child(1) td,
.st tbody tr:nth-child(2) td { background: rgba(16,185,129,0.07); }
