/* ZNA Gathering 2026 - Reel-style snap experience (mobile-first) */

:root {
  --bg-deep: #0a0524;
  --bg-mid: #1a0a3e;
  --sun-yellow: #FEB447;
  --sun-pink: #ff5c8a;
  --sun-orange: #ff7e1f;
  --neon-cyan: #00d4ff;
  --neon-purple: #9b51e0;
  --neon-magenta: #ff006e;
  --text: #fff8e7;
  --text-muted: #c8b9e4;
  --grid: rgba(255, 92, 138, 0.18);
  --card-bg: rgba(15, 8, 40, 0.72);
  --card-border: rgba(254, 180, 71, 0.35);
  --vh: 1vh;
}

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

html, body {
  height: 100%;
  width: 100%;
  overflow: hidden;
  margin: 0;
  padding: 0;
  background: var(--bg-deep);
  -webkit-text-size-adjust: 100%;
  /* Stop iOS Safari + Chrome Android from synthesising the 300ms tap
     delay everywhere — combined with the per-button overrides below this
     makes every interactive surface feel instant. */
  -webkit-touch-callout: none;
}

body {
  font-family: "Heebo", "Segoe UI", "Assistant", system-ui, sans-serif;
  color: var(--text);
  direction: ltr;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

/* Cross-browser interactive-surface defaults. Single rule covers every
   button / link / role=button across the app: skip the tap-highlight
   flash, and tell the browser this is a tap target (not a double-tap-
   to-zoom hint), so iOS doesn't sit on the gesture for 300ms before
   firing the click. */
button, a, [role="button"], summary, label[for] {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/* Hebrew (and any other strong-direction) text inside an LTR layout reads
   correctly when each text block decides its own direction. dir="auto" on
   text-bearing elements does this — punctuation and word order stay native
   while the surrounding boxes/buttons stay LTR. */
.bio-text,
.artist-name,
.hero-tagline,
.hero-stage-name,
.hero-hint,
.muted,
.album-name,
.album-meta,
.track-title,
.search-result-name,
.search-result-meta,
.info-meta,
.info-section-title,
.live-status,
.artist-set-time,
.panel-eyebrow,
.chip,
.meta-item {
  unicode-bidi: plaintext;
  text-align: start;
}

/* Hero stage name is the big centred title on each stage panel — keep
   `unicode-bidi: plaintext` from the rule above (so mixed-direction
   names resolve correctly) but force center alignment so a wrapped
   two-line title (e.g. "Retro Universe" on narrower desktop widths)
   doesn't suddenly read as a left-aligned block while the rest of the
   panel content is centered. `!important` wins over any cached
   `text-align: start` left over from older bundles still in browsers. */
.hero-stage-name,
.hero-panel .hero-stage-name,
.logo-mark.hero-stage-name {
  text-align: center !important;
  text-align-last: center;
}

img, iframe { display: block; max-width: 100%; }
button { font-family: inherit; }

a { color: var(--sun-yellow); }

/* ===== Background ===== */
.bg-scene {
  position: fixed;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  background:
    radial-gradient(ellipse at 50% 90%, rgba(255, 92, 138, 0.45) 0%, transparent 55%),
    radial-gradient(ellipse at 50% 100%, rgba(254, 180, 71, 0.55) 0%, transparent 40%),
    linear-gradient(180deg, #0a0524 0%, #1a0a3e 30%, #4a1e6e 60%, #c0367a 80%, #ff7e1f 100%);
  transition: background 0.6s ease-out;
}

.stars {
  position: fixed; inset: 0; z-index: -2; pointer-events: none;
  background-image:
    radial-gradient(1px 1px at 20% 15%, white, transparent),
    radial-gradient(1px 1px at 60% 25%, white, transparent),
    radial-gradient(1.5px 1.5px at 85% 10%, var(--sun-yellow), transparent),
    radial-gradient(1px 1px at 30% 35%, white, transparent),
    radial-gradient(2px 2px at 75% 40%, var(--neon-cyan), transparent),
    radial-gradient(1px 1px at 12% 50%, white, transparent),
    radial-gradient(1.5px 1.5px at 50% 8%, white, transparent);
  background-size: 100% 60%;
  background-repeat: no-repeat;
  animation: twinkle 4s ease-in-out infinite;
}

@keyframes twinkle {
  0%, 100% { opacity: 0.7; }
  50% { opacity: 1; }
}

.bg-grid {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  height: 50%;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(transparent 70%, rgba(10, 5, 36, 0.9) 100%),
    repeating-linear-gradient(90deg, var(--grid) 0 1px, transparent 1px 60px),
    repeating-linear-gradient(0deg, var(--grid) 0 1px, transparent 1px 50px);
  transform: perspective(400px) rotateX(60deg);
  transform-origin: bottom;
  animation: grid-scroll 6s linear infinite;
}

@keyframes grid-scroll {
  0% { background-position: 0 0, 0 0, 0 0; }
  100% { background-position: 0 0, 0 0, 0 50px; }
}

/* ===== Reel: vertical TikTok-like ===== */
.reel {
  position: absolute;
  inset: 0;
  height: calc(var(--vh, 1vh) * 100);
  height: 100dvh;
  width: 100%;
  overflow-y: scroll;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: y mandatory;
  overscroll-behavior-y: contain;
  touch-action: pan-y;
  scrollbar-width: none;
}
.reel::-webkit-scrollbar { display: none; }

/* ===== Section (full viewport) ===== */
.section {
  height: calc(var(--vh, 1vh) * 100);
  height: 100dvh;
  width: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  position: relative;
  overflow: hidden;
  /* Lets the browser skip rendering offscreen sections — huge memory win */
  content-visibility: auto;
  contain-intrinsic-size: 100vh;
}

/* ===== Multi-hero section: horizontal pager of stage heroes ===== */
.section--multi-hero {
  position: relative;
  overflow: hidden;
}

.hero-pager {
  height: 100%;
  width: 100%;
  display: flex;
  overflow-x: scroll;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  /* Both axes — the browser picks horizontal for the pager (snap) and lets
     vertical fall through to the reel so the user can swipe down to artists. */
  touch-action: pan-x pan-y;
  scrollbar-width: none;
  will-change: transform;
}
.hero-pager::-webkit-scrollbar { display: none; }

/* Apple-TV-style auto-advance — see app.js startHeroAutoplay(). The
   active stage dot fills with the accent colour over 7s, then the pager
   auto-advances to the next stage. Old "peek" sideways nudge is gone. */

.hero-panel {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px 18px 80px;
  position: relative;
  --hero-glow-1: var(--sun-yellow);
  --hero-glow-2: var(--sun-pink);
  --hero-glow-shadow: rgba(255, 126, 31, 0.6);
  --hero-text-1: var(--sun-yellow);
  --hero-text-2: var(--sun-pink);
  --hero-text-3: var(--neon-purple);
  /* Let clicks / element-picker hits pass through empty flex space to
     #hero-pager (horizontal scroll) instead of "sticking" to this full-
     viewport slab. Interactive children opt back in below. */
  pointer-events: none;
}

.hero-panel > :not(.hero-stage-element):not(.hero-logo-scene) {
  position: relative;
  z-index: 1;
  pointer-events: auto;
}

.hero-stage-element {
  position: absolute;
  bottom: -4px;
  right: max(-86px, -13vw);
  /* The natural artwork is portrait 600×1066 (~0.563 aspect), so a
     pure width-driven sizing rule made the rendered height balloon to
     80–90vh on taller viewports and dominate every stage panel. The
     rules below hold the element to a strict 50vh ceiling: locking
     aspect-ratio to 600/1066 and clamping width to min(originalWidth,
     28vh) means the implied height never exceeds 28vh × 1066/600 ≈
     49.7vh on any viewport. `height: auto` still resolves via the
     aspect ratio; `max-height: 50vh` is a belt-and-braces fallback
     for any browser without `aspect-ratio` support. */
  width: min(clamp(210px, 48vw, 390px), 28vh);
  aspect-ratio: 600 / 1066;
  height: auto;
  max-height: 50vh;
  z-index: 0;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  filter: drop-shadow(0 18px 34px rgba(0, 0, 0, 0.45));
}
.hero-stage-element > img {
  display: block;
  width: 100%;
  height: auto;
  /* Force the image's natural aspect ratio to drive layout — the W/H
     attributes on <img> get translated to an aspect-ratio presentational
     hint that can stretch portrait artwork into a square box. */
  aspect-ratio: auto;
  object-fit: contain;
  -webkit-user-drag: none;
}

.hero-dots {
  position: absolute;
  /* Anchor near the bottom of the section, just above the dive-hint
     arrow (which sits at bottom: max(16px, env(safe-area-inset-bottom))
     plus its ~50 px arrow + label = ~66 px of bottom real estate). 70 px
     places the dot strip immediately above the hint without overlap and
     reads as "near the bottom edge" the way the user expects. */
  bottom: 70px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 6px;
  z-index: 12;
  /* Idle: transparent strip with generous padding for the tap target — same
     two-stage tap behavior as the per-artist .dots strip. The first tap arms
     the strip (pill chrome appears), the second tap navigates. */
  background: transparent;
  border: 1px solid transparent;
  padding: 18px 24px;
  border-radius: 999px;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
  transition: bottom 0.25s ease, background 0.25s ease, border-color 0.25s ease, padding 0.25s ease, box-shadow 0.25s ease;
}

.hero-dots.is-armed,
.hero-dots.is-hovered {
  background: rgba(10, 5, 36, 0.92);
  border-color: var(--card-border);
  padding: 12px 18px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

/* Pause / resume automatic hero stage rotation — only while the strip is
   armed (mobile two-tap) or hovered (desktop). */
.hero-autoplay-toggle {
  flex-shrink: 0;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 0;
  min-width: 0;
  height: 22px;
  padding: 0;
  margin: 0;
  overflow: hidden;
  border: none;
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.88);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, width 0.2s ease, min-width 0.2s ease, margin-inline-start 0.2s ease, background 0.2s ease;
}
.hero-dots.is-armed .hero-autoplay-toggle,
.hero-dots.is-hovered .hero-autoplay-toggle {
  opacity: 1;
  pointer-events: auto;
  width: 24px;
  min-width: 24px;
  margin-inline-start: 8px;
}
.hero-autoplay-toggle:hover {
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
}
.hero-autoplay-toggle:focus-visible {
  outline: 2px solid var(--sun-yellow);
  outline-offset: 2px;
}
.hero-autoplay-ico {
  width: 11px;
  height: 11px;
  display: block;
}
.hero-dots:not(.autoplay-user-off) .hero-autoplay-ico--play {
  display: none;
}
.hero-dots.autoplay-user-off .hero-autoplay-ico--pause {
  display: none;
}
.hero-dots.autoplay-user-off .hero-autoplay-toggle {
  background: rgba(254, 180, 71, 0.2);
  color: var(--sun-yellow);
}

.hero-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: none;
  padding: 0;
  cursor: pointer;
  background: rgba(255, 255, 255, 0.55);
  /* Magnify via transform: scale so layout stays stable and adjacent dots
     don't shift when the focus moves (matches the per-artist .dots
     behaviour). */
  transform: scale(0.78);
  transform-origin: center;
  transition: transform 0.18s ease, background 0.25s ease, box-shadow 0.25s ease;
  box-sizing: content-box;
}

.hero-dot.active {
  /* Wider pill for the hero strip so the auto-advance fill (see
     .hero-dot.is-filling below) has visible runway. Renders identically
     in every browser because both width and height are explicit. The
     filled segment is painted by a child ::before so the unfilled
     "track" can stay subtly visible behind it. */
  width: 28px;
  background: rgba(255, 255, 255, 0.18);
  border-radius: 999px;
  box-shadow: 0 0 10px rgba(254, 180, 71, 0.0);
  position: relative;
  overflow: hidden;
}
.hero-dot.active::before {
  content: "";
  position: absolute;
  inset: 0;
  width: var(--fill, 0%);
  background: var(--sun-yellow);
  border-radius: inherit;
  /* Faint glow trails the filled segment; intensifies as it gets longer. */
  box-shadow: 0 0 8px var(--sun-yellow);
  /* Snap to 0% / full as the autoplay script sets --fill each frame; no
     CSS transition needed (RAF drives it). */
}
/* While the fill animation is running, lift the dot a touch and brighten
   the shell so the runway reads clearly even in the empty portion. */
.hero-dot.is-filling {
  background: rgba(255, 255, 255, 0.22);
  box-shadow: 0 0 8px rgba(254, 180, 71, 0.35);
}
@media (prefers-reduced-motion: reduce) {
  .hero-dot.active::before { width: 100%; }
}

body.has-mini-player .hero-dots {
  bottom: calc(70px + env(safe-area-inset-bottom, 0px) + 12px);
}

.hero-hint {
  margin-top: 18px;
  font-size: 12px;
  color: var(--text-muted);
  letter-spacing: 1px;
  opacity: 0.8;
  animation: hint-bounce 2.4s ease-in-out infinite;
}

/* Section-level dive-hint that lives BELOW the hero-dots strip, pinned
   to the bottom of the multi-hero section with generous breathing room
   between the dots and this text. Same arrow-bob animation, but always
   centered below the dots regardless of which hero panel is active. */
.section--multi-hero > .hero-hint {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: max(16px, env(safe-area-inset-bottom, 0));
  margin-top: 0;
  z-index: 11;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  text-align: center;
  pointer-events: none;
  animation: hint-bounce 2.4s ease-in-out infinite;
}
.section--multi-hero > .hero-hint .hero-hint-arrow {
  font-size: 22px;
  line-height: 1;
  opacity: 0.92;
  animation: hero-hint-arrow-bob 1.6s ease-in-out infinite;
}

@keyframes hint-bounce {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 1; }
}

.hero-logo-scene {
  position: relative;
  /* Slightly smaller than before — logo + slogan + UFOs were dominating
     short viewports; children use % sizing inside this box. */
  width: min(86vw, 580px);
  aspect-ratio: 1536 / 980;
  /* Spacing handled by .hero-stack gap. */
  margin: 0 auto;
  pointer-events: none;
  isolation: isolate;
}

.hero-logo-main,
.hero-logo-community {
  position: absolute;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}

.hero-logo-main {
  left: 13%;
  top: 28%;
  width: 74%;
  z-index: 2;
  filter: drop-shadow(0 10px 22px rgba(0, 0, 0, 0.55));
  animation: zna-float-main 7.2s ease-in-out infinite;
}

.hero-logo-community {
  left: 27%;
  top: 63%;
  width: 46%;
  z-index: 3;
  filter: drop-shadow(0 9px 18px rgba(0, 0, 0, 0.48));
  animation: zna-float-community 6.6s ease-in-out infinite;
  animation-delay: -1.7s;
}

.hero-logo-main > img,
.hero-logo-community > img,
.hero-section-ufo-img > img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: auto;
  object-fit: contain;
  -webkit-user-drag: none;
}

/* ===== Section-level UFO + orb decor =====
   Five tiny pictures pinned to the .section--multi-hero. Each stage
   shows exactly THREE; the other two sit just off the viewport edge.
   Positions per stage live in app.js (UFO_LAYOUT) and are interpolated
   per scroll frame via inline `transform: translate3d(...vw, ...vh, 0)`
   on the outer wrapper — so the offscreen UFOs slide INTO frame and
   onscreen UFOs slide OUT during a swipe, 1:1 with the finger.
   The outer wrapper owns the stage transform; the inner picture owns
   the gentle CSS idle float, so the two transforms compose cleanly. */
.hero-section-ufo {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  z-index: 2;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  will-change: transform;
  /* Initial transform = main-stage layout; overwritten by JS on next
     scroll frame. Without this the UFOs would flash at (0,0) on first
     paint before the RAF kicks in. */
  transform: translate3d(0, 0, 0);
}
.hero-section-ufo-img {
  display: block;
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.42));
  opacity: 0.92;
  animation: zna-float-decor var(--float-duration, 7.4s) ease-in-out infinite;
  animation-delay: var(--float-delay, 0s);
}

/* Each UFO keeps its identity (size + idle-float cadence). */
.hero-section-ufo--ufo-top-left {
  width: clamp(28px, 5vw, 44px);
}
.hero-section-ufo--ufo-top-left .hero-section-ufo-img {
  --float-duration: 7.8s;
  --float-delay: -1.4s;
}

.hero-section-ufo--ufo-top-right {
  width: clamp(30px, 5.4vw, 48px);
}
.hero-section-ufo--ufo-top-right .hero-section-ufo-img {
  --float-duration: 8s;
  --float-delay: -1s;
}

.hero-section-ufo--ufo-bottom-left {
  width: clamp(32px, 5.8vw, 50px);
}
.hero-section-ufo--ufo-bottom-left .hero-section-ufo-img {
  --float-duration: 8.4s;
  --float-delay: -3s;
}

.hero-section-ufo--ufo-bottom-right {
  width: clamp(28px, 5vw, 44px);
}
.hero-section-ufo--ufo-bottom-right .hero-section-ufo-img {
  --float-duration: 7.6s;
  --float-delay: -2.7s;
}

.hero-section-ufo--orb-center {
  width: clamp(14px, 2.4vw, 22px);
}
.hero-section-ufo--orb-center .hero-section-ufo-img {
  opacity: 0.85;
  --float-duration: 6.9s;
  --float-delay: -3.5s;
}

@media (prefers-reduced-motion: reduce) {
  .hero-section-ufo-img { animation: none; }
}

@keyframes zna-float-main {
  0%, 100% { transform: translateY(0) rotate(-0.25deg); }
  50%      { transform: translateY(-5px) rotate(0.2deg); }
}

@keyframes zna-float-community {
  0%, 100% { transform: translateY(0) rotate(0.2deg); }
  50%      { transform: translateY(4px) rotate(-0.15deg); }
}

@keyframes zna-float-decor {
  0%, 100% { transform: translateY(0) rotate(var(--float-rotate, 0deg)); }
  50%      { transform: translateY(-3px) rotate(calc(var(--float-rotate, 0deg) * -1)); }
}

/* Per-stage hero color identity — Zambu Temple (main temple), The
   Peninsula (lakeside chill-out) and Market (Boshke Beats). */
.hero-panel--zambu {
  --hero-glow-1: #ff006e;
  --hero-glow-2: #9b51e0;
  --hero-glow-shadow: rgba(155, 81, 224, 0.65);
  --hero-text-1: #ff006e;
  --hero-text-2: #9b51e0;
  --hero-text-3: #5a189a;
  background:
    radial-gradient(ellipse at 50% 110%, rgba(155, 81, 224, 0.55), transparent 60%),
    linear-gradient(165deg, #1c0640 0%, #2a0a55 50%, #100322 100%);
}
.hero-panel--peninsula {
  --hero-glow-1: #00d4ff;
  --hero-glow-2: #4dd0a6;
  --hero-glow-shadow: rgba(0, 212, 255, 0.55);
  --hero-text-1: #00d4ff;
  --hero-text-2: #4dd0a6;
  --hero-text-3: #118ab2;
  background:
    radial-gradient(ellipse at 50% 110%, rgba(0, 212, 255, 0.45), transparent 60%),
    linear-gradient(165deg, #03182d 0%, #08294a 50%, #02101e 100%);
}
.hero-panel--market {
  --hero-glow-1: #ffd60a;
  --hero-glow-2: #ffbf69;
  --hero-glow-shadow: rgba(255, 191, 105, 0.6);
  --hero-text-1: #ffd60a;
  --hero-text-2: #ffbf69;
  --hero-text-3: #fb8500;
  background:
    radial-gradient(ellipse at 50% 110%, rgba(255, 191, 105, 0.5), transparent 60%),
    linear-gradient(165deg, #2a1308 0%, #3d1c0e 50%, #1a0d05 100%);
}

/* The "All / Main" hero — keeps the original deep ZNA night sky so it reads
   as the home base while still being clearly different from any stage. */
.hero-panel--main {
  background:
    radial-gradient(ellipse at 50% 110%, rgba(254, 180, 71, 0.45), transparent 60%),
    linear-gradient(165deg, #0a0524 0%, #1a0a3e 50%, #050214 100%);
}

.hero-panel .logo-mark {
  background: linear-gradient(180deg, var(--hero-text-1) 0%, var(--hero-text-2) 60%, var(--hero-text-3) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero-panel--zambu .logo-mark,
.hero-panel--peninsula .logo-mark,
.hero-panel--market .logo-mark {
  font-size: clamp(40px, 11vw, 90px);
}

.logo-mark {
  font-family: "Orbitron", "Heebo", sans-serif;
  font-weight: 900;
  font-size: clamp(56px, 18vw, 140px);
  letter-spacing: -3px;
  background: linear-gradient(180deg, var(--sun-yellow) 0%, var(--sun-pink) 60%, var(--neon-purple) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-shadow: 0 0 40px rgba(254, 180, 71, 0.4);
  line-height: 0.85;
  /* Spacing handled by .hero-stack gap inside hero panels. */
  margin-bottom: 0;
}

.hero-tagline {
  font-size: clamp(13px, 3.6vw, 18px);
  color: var(--text-muted);
  font-weight: 400;
  max-width: 540px;
  line-height: 1.6;
  /* Spacing handled by .hero-stack gap. */
  margin-bottom: 0;
  padding: 0 12px;
}

.hero-meta {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px 16px;
  padding: 10px 18px;
  background: rgba(10, 5, 36, 0.65);
  border: 1px solid var(--card-border);
  border-radius: 999px;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  font-size: 13px;
  color: var(--sun-yellow);
  /* Spacing handled by .hero-stack gap. */
  margin-bottom: 0;
}

.live-status {
  width: min(540px, calc(100% - 28px));
  display: grid;
  gap: 5px;
  padding: 12px 16px;
  margin: -6px auto 2px;
  background: rgba(10, 5, 36, 0.76);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 16px;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--text);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.35);
}

.live-status strong {
  font-size: clamp(17px, 4vw, 24px);
  line-height: 1.15;
  color: var(--sun-yellow);
}

.live-status span:last-child {
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-muted);
}

.live-status--live {
  border-color: rgba(57, 255, 20, 0.55);
  box-shadow: 0 0 28px rgba(57, 255, 20, 0.18), 0 12px 34px rgba(0, 0, 0, 0.35);
}

.live-status--live strong {
  color: #39ff14;
}

/* ===== Artist section: horizontal pager ===== */
.artist-section {
  padding: 0;
  overflow: hidden;
}

/* First artist of each stage: top edge picks up the stage's background tint
   so the dive from the stage hero into its first artist feels seamless. */
.artist-section.is-first-of-stage::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 55%;
  background: linear-gradient(180deg, var(--stage-tint, transparent) 0%, transparent 100%);
  pointer-events: none;
  z-index: 0;
}
.artist-section.is-first-of-stage[data-stage="retro"]     { --stage-tint: rgba(56, 16, 32, 0.85); }
.artist-section.is-first-of-stage[data-stage="zambu"]     { --stage-tint: rgba(42, 12, 84, 0.85); }
.artist-section.is-first-of-stage[data-stage="guardians"] { --stage-tint: rgba(16, 51, 84, 0.85); }
.artist-section.is-first-of-stage[data-stage="market"]    { --stage-tint: rgba(56, 36, 14, 0.85); }

.pager {
  height: 100%;
  width: 100%;
  display: flex;
  overflow-x: scroll;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  /* Both axes — Chromium/Android intersects touch-action across ancestors,
     so restricting to pan-x here secretly blocked vertical pans on the
     .panel children (where the panel actually scrolls / chains to the
     reel). Allow pan-y too: the pager only scrolls X, but children get
     to scroll Y, which is what users expect from a vertical lineup. */
  touch-action: pan-x pan-y;
  scrollbar-width: none;
}
.pager::-webkit-scrollbar { display: none; }

.panel {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  padding: 80px 18px 100px;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: center;
  position: relative;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* Suppress iOS Safari's native rubber-band on overflowing panels so it
     doesn't fight our explicit pull-to-navigate gesture (long bios/videos
     panels were swallowing the over-pull as their own bounce, never
     advancing to the next artist). The pull gesture handler in app.js
     replaces that chain with a real navigation. */
  overscroll-behavior: contain;
}

/* While the user is pulling past the panel edge, take control of the touch
   gesture so iOS/Android don't try to bounce the panel's native scroll
   underneath our rubber-band transform. preventDefault in JS does the rest. */
.panel.is-pulling {
  touch-action: none;
  overscroll-behavior: contain;
}

/* Telegram-style pull-to-navigate indicator: appears at the top edge when
   the user pulls down past the start, and at the bottom edge when they pull
   up past the end. Goes from translucent to "ready" colour once the pull
   exceeds the commit threshold; release at that point fires navVertical. */
.pull-indicator {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  background: rgba(10, 5, 36, 0.92);
  color: var(--text);
  border: 1px solid var(--card-border, rgba(255, 255, 255, 0.18));
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.3px;
  opacity: 0;
  pointer-events: none;
  z-index: 6;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  white-space: nowrap;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.pull-indicator.pull-from-top    { top: 18px; }
.pull-indicator.pull-from-bottom { bottom: 18px; }
.pull-indicator.is-visible       { opacity: 1; }
.pull-indicator.is-ready {
  background: var(--panel-accent, var(--sun-yellow));
  color: #0a0524;
  border-color: transparent;
}
.pull-indicator .pull-arrow {
  width: 14px;
  height: 14px;
  display: inline-block;
  border: 2px solid currentColor;
  border-top: 0;
  border-right: 0;
  transform: rotate(-45deg);
  transition: transform 0.2s ease;
  margin-bottom: 2px;
}
.pull-indicator.pull-from-bottom .pull-arrow {
  transform: rotate(135deg);
  margin-bottom: 0;
  margin-top: 2px;
}
.pull-indicator.is-ready .pull-arrow {
  transform: rotate(-45deg) scale(1.15);
}
.pull-indicator.pull-from-bottom.is-ready .pull-arrow {
  transform: rotate(135deg) scale(1.15);
}
@media (hover: hover) and (pointer: fine) {
  /* Pull-to-navigate is a touch gesture; no mouse equivalent. */
  .pull-indicator { display: none !important; }
}

/* Hero panel content stays vertically centered */
.panel.panel-hero {
  justify-content: center;
}

.panel-inner {
  width: 100%;
  max-width: 600px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.panel-eyebrow {
  font-size: 11px;
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--panel-accent, var(--sun-yellow));
  text-align: center;
  opacity: 0.85;
  font-weight: 700;
}

/* Two-line eyebrow on Info / Tracks / Discography: artist name on top
   so the user always knows whose discography or bio they're reading,
   section label below in the existing accent style. */
.panel-eyebrow.has-artist {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Tight gap between the two lines so they read as one stacked label
     (the eyebrow on top of the artist name), not two separate beats. */
  gap: 2px;
  letter-spacing: normal;
  text-transform: none;
  opacity: 1;
  /* Tighter top-margin so the eyebrow stack sits closer to the
     top-bar logo (was 24 — felt floaty on shorter screens). */
  margin-top: 8px;
}

.panel-eyebrow.has-artist .eyebrow-artist {
  font-size: clamp(15px, 3.6vw, 18px);
  font-weight: 800;
  color: var(--text);
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.1;
}

.panel-eyebrow.has-artist .eyebrow-section {
  font-size: 10px;
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--panel-accent, var(--sun-yellow));
  font-weight: 700;
  opacity: 0.85;
}

/* ===== Per-panel color identity (ZNA palette) =====
   --panel-accent feeds eyebrow, card borders, dividers, chips and CTAs.
   The radial+linear gradient is layered on top of bg-scene so every
   panel type reads as its own warm/cool surface as you swipe across. */
.panel--hero {
  --panel-accent: #FEB447; --panel-accent-2: #ff5c8a;
  background:
    radial-gradient(ellipse at 50% 0%, rgba(255, 126, 31, 0.22) 0%, transparent 65%),
    linear-gradient(180deg, rgba(255, 126, 31, 0.10), transparent 60%);
}
.panel--info {
  --panel-accent: #b87cff; --panel-accent-2: #ff5c8a;
  background:
    radial-gradient(ellipse at 50% 0%, rgba(155, 81, 224, 0.24) 0%, transparent 65%),
    linear-gradient(180deg, rgba(155, 81, 224, 0.12), transparent 60%);
}
.panel--tracks {
  --panel-accent: #00d4ff; --panel-accent-2: #4d8eff;
  background:
    radial-gradient(ellipse at 50% 0%, rgba(0, 153, 255, 0.22) 0%, transparent 65%),
    linear-gradient(180deg, rgba(0, 153, 255, 0.10), transparent 60%);
}
.panel--discography {
  --panel-accent: #5cdb8b; --panel-accent-2: #00d4ff;
  background:
    radial-gradient(ellipse at 50% 0%, rgba(74, 222, 128, 0.20) 0%, transparent 65%),
    linear-gradient(180deg, rgba(74, 222, 128, 0.09), transparent 60%);
}

/* About panel: bio card ≈ 90% of column below eyebrow; bottom 1fr = gap
   above pager dots. Bio paragraph scrolls inside the tall card.
   The info panel uses a tighter bottom padding than other panels so the
   bio-card claims more vertical real estate — every saved px shows up
   as another line of bio rather than empty space above the pager dots. */
.panel.panel--info {
  align-items: stretch;
  padding-bottom: 50px;
}
@media (min-width: 768px) {
  .panel.panel--info { padding-bottom: 60px; }
}
@media (min-width: 1024px) {
  .panel.panel--info { padding-bottom: 70px; }
}
.panel--info .panel-inner {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  max-width: 600px;
  margin-inline: auto;
  display: grid;
  grid-template-columns: 1fr;
  /* eyebrow (auto) → bio-card (takes the rest) → fixed 20px tail spacer.
     The old `minmax(0, 1fr)` tail expanded to ~50px on tall viewports and
     looked like dead air below the card. */
  grid-template-rows: auto minmax(0, 1fr) 20px;
  /* A measured gap between the SECTION/artist eyebrow and the bio-card —
     enough breathing room that the card reads as its own surface, not so
     much that the eyebrow drifts away from the content it labels. */
  gap: 14px;
}
.panel--info .panel-eyebrow.has-artist {
  grid-row: 1;
  align-self: start;
}
.panel--info .bio-card {
  grid-row: 2;
  min-height: 0;
  overflow: hidden;
}
.panel--info .panel-inner::after {
  content: "";
  grid-row: 3;
  display: block;
  min-height: 0;
  pointer-events: none;
}
.panel--info .bio-card .info-section:has(.bio-text) {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.panel--info .bio-card .bio-text {
  max-height: none;
  flex: 1 1 auto;
  min-height: 0;
}

.panel--info .bio-card,
.panel--tracks .track-card,
.panel--discography .albums-list li {
  border-color: rgba(255, 255, 255, 0.1);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.04) inset,
    0 0 0 1px color-mix(in oklab, var(--panel-accent) 30%, transparent);
}

.panel--info .info-section-title {
  color: var(--panel-accent);
  border-color: color-mix(in oklab, var(--panel-accent) 35%, transparent);
}
.panel--info .info-meta-row,
.panel--info .info-meta-text,
.panel--info .info-rep-text { color: var(--text-muted); }
/* Inline external links stay muted/grey in the info panel — they're
   secondary navigation, not a CTA, so they shouldn't shout in the
   per-stage accent colour. */
.panel--info .link-text {
  color: var(--text-muted);
  text-decoration-color: color-mix(in oklab, var(--text-muted) 50%, transparent);
}

.panel--tracks .track-card.is-zna {
  border-color: var(--sun-yellow);
}
.panel--tracks .play-btn {
  background: var(--panel-accent);
  box-shadow: 0 8px 30px color-mix(in oklab, var(--panel-accent) 50%, transparent);
}
.panel--tracks .search-yt-btn {
  background: linear-gradient(135deg, var(--panel-accent), var(--panel-accent-2));
}

.panel--discography .albums-list li {
  border-right: 3px solid color-mix(in oklab, var(--panel-accent) 50%, transparent);
}
.panel--discography .album-name { color: var(--text); }
.panel--discography .album-meta { color: color-mix(in oklab, var(--panel-accent) 70%, white 30%); }

/* === Panel 1: Artist Hero — editorial layout ===
   Mobile: centered column (photo on top, text below).
   Desktop ≥1024px: magazine-style 2-column grid (photo + text). */
.panel-hero { text-align: center; }

.panel-hero .panel-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 38px;
}

.artist-hero-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  max-width: 100%;
}

/* Tight headline group — artist name + real name + meta-line sit close
   together so the timer beneath them reads as part of the same block.
   Inherits align-items from .artist-hero-text so it stays centered on
   mobile and left-aligned on desktop without extra rules. */
.artist-hero-headline {
  display: flex;
  flex-direction: column;
  align-items: inherit;
  gap: 4px;
  max-width: 100%;
}

.artist-meta-line {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px 18px;
  font-size: 13px;
  color: var(--text-muted);
  letter-spacing: 0.2px;
  margin-top: 4px;
}

.artist-meta-line .meta-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.artist-meta-line .meta-item + .meta-item::before {
  content: "·";
  color: var(--accent, var(--sun-yellow));
  margin-inline-end: 14px;
  margin-inline-start: -8px;
  font-weight: 900;
}

/* The country meta-item now renders just the flag emoji (the spelled-
   out country name and the 📍 pin both removed per the slim-down). A
   slightly larger size makes the flag readable on its own. */
.artist-meta-line .meta-item--flag {
  font-size: 1.25em;
  line-height: 1;
}

.artist-set-time {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 5px 11px;
  border-radius: 999px;
  background: rgba(10, 5, 36, 0.7);
  border: 1px solid rgba(254, 180, 71, 0.34);
  color: var(--sun-yellow);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.2px;
  /* Sits right below the headline group; gap on the parent already
     gives breathing room, no per-element margin needed. */
  margin: 0;
}

.artist-set-time.is-tba {
  color: var(--text-muted);
  border-style: dashed;
}

.artist-set-time.is-countdown {
  background: transparent;
  border: 0;
  padding: 0;
  border-radius: 0;
}

/* Wrapper around the artist photo. Holds the photo figure plus the small
   "scattered" tags that are absolutely positioned around the photo's
   perimeter. The wrapper itself doesn't clip — the figure inside still
   does, so the photo keeps its rounded corners while the tags can peek
   out beyond the edges. */
.artist-hero-photo {
  position: relative;
  width: min(70vw, 240px);
  margin: 0 auto 8px;
  overflow: visible;
}

.artist-hero-photo .artist-hero-art {
  width: 100%;
  margin: 0;
}

/* Small "scattered" tag chips that float around the artist photo. Each
   tag's CSS variables (--x, --y, --rot, --delay) come from buildPhotoTags
   in app.js — the JS picks a slot near a photo corner, jitters it, picks
   a random rotation clamped to ±25° so the text stays readable, and
   schedules a staggered pop-in. */
.photo-tag {
  position: absolute;
  left: var(--x, 50%);
  top: var(--y, 50%);
  transform: translate(-50%, -50%) rotate(var(--rot, 0deg)) scale(0.4);
  font-family: "Heebo", sans-serif;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.4px;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.78);
  border: 1px solid rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  white-space: nowrap;
  pointer-events: none;
  z-index: 4;
  box-shadow: 0 6px 14px rgba(0, 0, 0, 0.28);
  opacity: 0;
}

/* Pop the tags in once the artist section is on-screen so the user
   actually sees the stagger. They stay in their final state afterwards. */
.section.is-active .photo-tag {
  animation: photo-tag-pop 600ms cubic-bezier(.18, .85, .35, 1.18) var(--delay, 0s) forwards;
}

@keyframes photo-tag-pop {
  0%   { opacity: 0; transform: translate(-50%, -50%) rotate(var(--rot, 0deg)) scale(0.4); }
  60%  { opacity: 1; transform: translate(-50%, -50%) rotate(var(--rot, 0deg)) scale(1.08); }
  100% { opacity: 1; transform: translate(-50%, -50%) rotate(var(--rot, 0deg)) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .photo-tag,
  .section.is-active .photo-tag {
    animation: none;
    opacity: 1;
    transform: translate(-50%, -50%) rotate(var(--rot, 0deg));
  }
}

/* Heart-only "favorite" button on the artist hero. Outline heart by
   default, fills red and pops once tapped. State stored in localStorage
   as a Set of artist ids. */
.artist-favorite {
  margin-top: 4px;
  align-self: center;
  width: 48px;
  height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.5);
  color: rgba(255, 255, 255, 0.92);
  border-radius: 999px;
  padding: 0;
  cursor: pointer;
  /* Tell the browser to skip the synthetic 300ms tap delay and the
     double-tap-to-zoom heuristic on this button — both contribute to
     the "first tap only shows hover, second tap saves" feel on mobile. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease, color 0.2s ease;
  position: relative;
}

/* Generous invisible hit area extending past the visible 48px circle so
   the heart is forgiving to tap. The pseudo-element only handles pointer
   events; the click delegates to the button itself. */
.artist-favorite::before {
  content: "";
  position: absolute;
  inset: -10px;
  border-radius: inherit;
}

.artist-favorite .fav-icon {
  width: 24px;
  height: 24px;
  display: block;
  transition: transform 0.2s ease;
  /* Keep the SVG out of the hit-test so the click lands on the button
     itself — avoids the iOS pattern where the icon eats the tap and the
     button never receives it. */
  pointer-events: none;
}

/* Hover styling is restricted to real-mouse devices. On mobile, the first
   tap was triggering sticky hover (color change but no save), forcing the
   user to tap a second time to commit. With hover gated to (hover:hover),
   a single mobile tap goes straight to the click handler and the heart
   saves immediately. */
@media (hover: hover) and (pointer: fine) {
  .artist-favorite:hover {
    background: rgba(255, 92, 138, 0.12);
    border-color: rgba(255, 92, 138, 0.6);
    color: #ff5c8a;
  }
}

.artist-favorite:active {
  transform: scale(0.92);
}

.artist-favorite.is-favorite {
  border-color: rgba(255, 92, 138, 0.85);
  background: rgba(255, 92, 138, 0.16);
  color: #ff5c8a;
}

.artist-favorite.is-popping .fav-icon {
  animation: fav-pop 360ms cubic-bezier(.2, .85, .35, 1.3);
}

@keyframes fav-pop {
  0%   { transform: scale(0.6); }
  55%  { transform: scale(1.25); }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .artist-favorite.is-popping .fav-icon { animation: none; }
}

.artist-hero-art {
  width: min(70vw, 240px);
  aspect-ratio: 1 / 1;
  margin: 0 auto 16px;
  border-radius: 28px;
  position: relative;
  overflow: hidden;
  /* Drop shadows only — no inset white ring. The previous 2px inset
     box-shadow rendered at the figure's outer border-radius while the
     photo (at inset: 0, clipped by overflow:hidden) appeared to follow
     a slightly tighter curve, making the corners read as misaligned.
     Without the inset shadow the photo's clip IS the visible edge. */
  box-shadow:
    0 16px 50px rgba(0, 0, 0, 0.5),
    0 0 60px var(--accent);
  background: linear-gradient(135deg, var(--accent) 0%, rgba(20, 10, 50, 0.7) 100%);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Make the <picture> wrapper that holds the photo a transparent layout
   shim that exactly matches the figure — otherwise the inline-block
   default leaves a sub-pixel baseline gap on some browsers and the photo
   appears to "float" a hair inside the rounded background. */
.artist-hero-art > picture {
  display: block;
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;
}

.artist-hero-art::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 25% 25%, rgba(255,255,255,0.3), transparent 50%),
    repeating-linear-gradient(45deg, transparent 0 26px, rgba(255,255,255,0.07) 26px 27px),
    repeating-linear-gradient(-45deg, transparent 0 26px, rgba(255,255,255,0.07) 26px 27px);
  pointer-events: none;
}

/* Real photo. Fills its <picture> wrapper exactly (which itself fills the
   figure at inset: 0), so the visible photo edge AND the figure's rounded
   background share one set of corners with the same radius. */
.artist-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  border-radius: inherit;
  z-index: 1;
}

.artist-hero-art.has-photo .artist-initials {
  display: none;
}

.artist-hero-art.has-photo::after {
  background:
    radial-gradient(circle at 50% 100%, rgba(0,0,0,0.5), transparent 50%);
}

.artist-hero-art.photo-failed .artist-initials {
  display: block;
}

.artist-initials {
  font-family: "Orbitron", sans-serif;
  font-size: clamp(60px, 16vw, 110px);
  font-weight: 900;
  color: rgba(255,255,255,0.95);
  text-shadow: 0 6px 30px rgba(0,0,0,0.5);
  letter-spacing: -4px;
  position: relative;
  z-index: 2;
}

/* 2-char ISO country code (IL, FR, JP…) printed ABOVE the artist
   name. Replaces the flag-emoji + spelled-out country combo. Small,
   accent-coloured monospace so it reads like a passport stamp. */
.artist-country-iso {
  margin-bottom: 6px;
  padding: 2px 8px;
  font-family: "Orbitron", "Heebo", system-ui, sans-serif;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 2px;
  color: var(--accent, var(--sun-yellow));
  background: color-mix(in oklab, var(--accent, var(--sun-yellow)) 12%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent, var(--sun-yellow)) 35%, transparent);
  border-radius: 4px;
  /* Inherit alignment from .artist-hero-headline (centered on mobile,
     flex-start on desktop) — the badge sits above the name and lines
     up with whatever side the rest of the headline lines up with. */
  align-self: inherit;
  max-width: max-content;
}

.artist-name {
  font-family: "Orbitron", "Heebo", sans-serif;
  font-size: clamp(28px, 8vw, 48px);
  font-weight: 900;
  background: linear-gradient(135deg, var(--sun-yellow), var(--sun-pink));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  margin: 0;
  line-height: 1.05;
}

.chip {
  font-size: 12px;
  padding: 5px 12px;
  border-radius: 999px;
  background: rgba(254, 180, 71, 0.12);
  color: var(--sun-yellow);
  border: 1px solid rgba(254, 180, 71, 0.3);
  white-space: nowrap;
}

.chip.cyan { color: var(--neon-cyan); background: rgba(0, 212, 255, 0.1); border-color: rgba(0, 212, 255, 0.3); }
.chip.pink { color: var(--sun-pink); background: rgba(255, 92, 138, 0.12); border-color: rgba(255, 92, 138, 0.4); }

/* === About / Info panel === */
.bio-card {
  background: var(--card-bg);
  border: 1.5px solid var(--card-border);
  border-radius: 20px;
  padding: 22px 20px;
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  display: flex;
  flex-direction: column;
  /* Tighter than the original 22 px so the notable-lede sits closer to
     the Biography heading below it, and the biography paragraph sits
     closer to the Streaming heading that follows. Every saved pixel
     hands the .bio-text flex item more room — visible as additional
     lines of bio before the user has to scroll. */
  gap: 12px;
}

/* Plain-text replacement for the old .info-meta / .info-rep chips. The
   row holds the two single-line facts (announced / representative) on
   one flex line — anchored to opposite ends on desktop, stacked on
   mobile (CSS-driven via flex-wrap). No chip chrome — these are just
   muted captions so they don't dominate the card. */
.info-meta-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px 18px;
  font-size: 12.5px;
  letter-spacing: 0.2px;
  color: var(--text-muted);
  margin: -2px 0 6px;
}
/* Each side of the meta row gets a hanging indent so when the text wraps
   to a second or third line, the prose continues to align with the first
   character of line one — never crawling back under the emoji icon. The
   indent matches the emoji width + its inline-end margin (~1.8em). */
.info-meta-text,
.info-rep-text {
  display: inline-block;
  padding-inline-start: 1.8em;
  text-indent: -1.8em;
}
.info-meta-text { text-align: start; }
.info-rep-text  { text-align: end; }

@media (max-width: 520px) {
  /* Mobile: stack one above the other, each anchored to the start so
     they read as a small captions list. */
  .info-meta-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }
  .info-rep-text { text-align: start; }
}

.info-section { display: flex; flex-direction: column; gap: 6px; }
/* Bio section sits tighter against its title than the other info sections —
   the dashed border-bottom on the title already reads as a separator, so a
   wide gap on top of that felt loose. */
.info-section:has(.bio-text) { gap: 4px; }

.info-section-title {
  font-size: 12px;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--sun-yellow);
  font-weight: 700;
  opacity: 0.85;
  border-bottom: 1px dashed var(--card-border);
  padding-bottom: 6px;
  margin: 0;
}

.bio-text {
  font-size: clamp(14px, 3.6vw, 17px);
  /* Tighter than 1.8 — at body-text size this still reads as airy
     without padding empty space between every line, so more of the
     bio fits in the visible card before the user has to scroll. */
  line-height: 1.55;
  color: var(--text);
  margin: 0;
  /* Cap the bio (outside .panel--info) at ~7 lines and let the rest scroll
     INSIDE the box, so a long bio doesn't push the streaming row + links
     off the panel (and doesn't trigger the panel's outer scroll).
     overscroll-behavior: contain stops the scroll from chaining to the
     reel once it hits the internal top/bottom — the reel's wheel router
     also checks for this scrollable via findScrollableAncestor() so a
     desktop wheel inside the bio stays local until it runs out of room.
     Inside .panel--info the grid layout overrides max-height to none and
     lets the bio fill the column below the eyebrow. */
  max-height: calc(1.55em * 8);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Force the scrollbar to be visible always (instead of the OS-default
     auto-hide), with a brighter accent-coloured thumb on a soft track
     so the user immediately sees "there's more text below". */
  scrollbar-width: thin;
  scrollbar-color: var(--sun-yellow) rgba(255, 255, 255, 0.08);
  scrollbar-gutter: stable;
  -webkit-overflow-scrolling: touch;
  /* Vertical padding doubles as the runway for the top/bottom fade mask
     below — the fade falls on the padding (empty space) when the bio is
     short, and on the partially-visible top/bottom lines when the bio
     scrolls, signalling "there's more content above/below" without
     covering any actual text on a short bio. */
  padding-block: 12px;
  padding-inline-end: 10px;
  /* Pronounced top + bottom fade — the first ~36px and last ~36px of the
     scroll viewport gradually go transparent, so the edges of the prose
     visibly dim into the panel (not just the empty padding runway).
     As the user scrolls, new lines arriving at the bottom fade in and
     the top line fades as it exits. The fade reaches past the padding
     into ~1.5 lines of real text at each end. */
  --bio-fade-mask: linear-gradient(to bottom, transparent 0, #000 36px, #000 calc(100% - 36px), transparent 100%);
  -webkit-mask-image: var(--bio-fade-mask);
  mask-image: var(--bio-fade-mask);
  /* Pull-to-navigate uses touch-action on the panel; the bio-text gets
     its own pan-y allowance so iOS doesn't claim the gesture before our
     pointer-stack figures out the user is reading the bio. */
  touch-action: pan-y;
}

/* Global `.bio-text { unicode-bidi: plaintext }` is for LTR shells; Hebrew
   UI bios need explicit RTL + isolate so mixed Latin names read correctly.
   `direction: rtl` also flips the WebKit scrollbar to the LEFT side of
   the paragraph — matching the reading direction in Hebrew mode. */
.bio-text[dir="rtl"] {
  direction: rtl;
  unicode-bidi: isolate;
  text-align: start;
}
/* The matching LTR rule is needed on iOS Safari — without it the
   inherited `unicode-bidi: plaintext` from the global utility class
   above causes the box direction to be inferred from the first strong
   character in the bio text, which sometimes resolves to RTL and
   moves the yellow scrollbar to the LEFT side even though the UI is
   English. Setting `direction: ltr` + `unicode-bidi: isolate`
   explicitly anchors the box (and therefore the scrollbar) to LTR. */
.bio-text[dir="ltr"] {
  direction: ltr;
  unicode-bidi: isolate;
  text-align: start;
}
.bio-text::-webkit-scrollbar {
  width: 6px;
}
.bio-text::-webkit-scrollbar-thumb {
  background: var(--sun-yellow);
  border-radius: 999px;
  box-shadow: 0 0 6px rgba(254, 180, 71, 0.45);
  /* Keep the thumb visible even when the user isn't actively scrolling. */
  opacity: 1;
}
.bio-text::-webkit-scrollbar-thumb:hover {
  background: var(--sun-pink);
}
.bio-text::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.07);
  border-radius: 999px;
}

/* "Notable" caption — small grey one-liner that sits between the
   announced/represented meta row and the Biography heading. Reads as
   a subtle subtitle, not as part of the biography paragraph itself. */
.bio-notable-lede {
  /* No bottom margin — the bio-card's `gap: 12 px` already gives this
     caption its space from the Biography heading below. Stacking margin
     on top of gap left a visible loose band between the two. */
  margin: 0;
  font-size: 11.5px;
  font-weight: 400;
  letter-spacing: 0.2px;
  line-height: 1.45;
  color: var(--text-muted);
  opacity: 0.85;
  /* Hanging indent — the leading "★ " glyph sits in the left gutter
     and any wrapped second/third line starts under the first letter
     of the prose, not under the star. */
  padding-inline-start: 1.2em;
  text-indent: -1.2em;
}
/* Pull the notable lede tight up under the info-meta-row above it. The
   bio-card now uses gap: 12 px between every flex child, so we only
   need a small extra negative margin to make the meta row + caption
   read as a single grouped block. */
.info-meta-row + .bio-notable-lede {
  margin-top: -6px;
}
.bio-notable-lede[dir="rtl"] {
  text-align: start;
}
.bio-notable-embed {
  unicode-bidi: isolate;
}

/* === Panel 3: Discography ===
   Each album card stacks its title on top and the green meta line
   (label/year/collaborator) UNDER it — used to sit beside the title
   on a flex row, which made long titles overlap the meta. The card
   is also narrower than the panel-inner so a row of albums doesn't
   span the full width on tablet/desktop. */
.albums-list {
  list-style: none;
  display: grid;
  gap: 8px;
  max-width: 520px;
  margin-inline: auto;
  width: 100%;
}

.albums-list li {
  background: rgba(20, 10, 50, 0.55);
  padding: 12px 16px;
  border-radius: 12px;
  border: 1px solid var(--card-border);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
}

.album-name { font-weight: 700; color: var(--text); font-size: 14px; line-height: 1.3; }
.album-meta { font-size: 11px; color: var(--text-muted); line-height: 1.3; }

/* Cross-link to a collaborator artist embedded in an album's project
   string. Underline + accent color so it's obviously tappable but
   doesn't break the muted look of the surrounding meta line. */
.album-meta .collab-link,
a.collab-link {
  color: var(--panel-accent, var(--sun-yellow));
  text-decoration: underline;
  text-decoration-color: color-mix(in oklab, var(--panel-accent, var(--sun-yellow)) 55%, transparent);
  text-underline-offset: 2px;
  font-weight: 700;
  cursor: pointer;
  transition: color 0.15s ease, text-decoration-color 0.15s ease;
}
a.collab-link:hover {
  color: var(--text);
  text-decoration-color: currentColor;
}

/* === Panel 4: Tracks === */
.tracks-stack { display: grid; gap: 12px; }

.track-card {
  background: var(--card-bg);
  border: 1.5px solid var(--card-border);
  border-radius: 16px;
  overflow: hidden;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
}

.track-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
}

.track-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.track-thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: linear-gradient(135deg, #2a1450, #0a0524);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  padding: 0;
  overflow: hidden;
}

.track-thumb-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.track-thumb::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 30%, rgba(0,0,0,0.5) 100%);
}

.play-btn {
  position: relative;
  z-index: 2;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: rgba(255, 92, 138, 0.95);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8px 30px rgba(255, 92, 138, 0.6);
  transition: transform 0.2s;
}

.track-thumb:active .play-btn { transform: scale(0.95); }

.play-btn svg { width: 26px; height: 26px; fill: white; margin-inline-start: 4px; }

.track-meta {
  padding: 10px 14px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.track-title {
  font-weight: 700;
  font-size: 13px;
  color: var(--text);
  flex: 1;
}

.track-year { font-size: 11px; color: var(--text-muted); }

/* ZNA festival recording badge */
.track-card.is-zna {
  border-color: var(--sun-yellow);
  box-shadow: 0 0 20px rgba(254, 180, 71, 0.25);
}

.track-card.is-zna .track-title::before {
  content: "🌅 ZNA · ";
  color: var(--sun-yellow);
  font-weight: 800;
  font-size: 11px;
  margin-inline-end: 4px;
}

.no-tracks {
  text-align: center;
  padding: 20px 18px;
  color: var(--text-muted);
  font-size: 13px;
}

.search-yt-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  padding: 12px 22px;
  border-radius: 999px;
  background: rgba(255, 0, 0, 0.85);
  color: white;
  text-decoration: none;
  font-weight: 700;
  font-size: 13px;
  box-shadow: 0 6px 20px rgba(255, 0, 0, 0.4);
}

/* === Panel 5: Links =====
   Plain in-line text links separated by a centered dot. The old
   pill-button look was visually heavy and ate vertical space; dotted
   underlines are enough to read as "click me" and keep the bio card
   compact. */
.links-inline {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 6px;
  align-items: baseline;
  font-size: 11.5px;
  line-height: 1.55;
}
.link-text {
  color: var(--text-muted);
  text-decoration: underline dotted;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in oklab, var(--text-muted) 50%, transparent);
  font-weight: 500;
  cursor: pointer;
  transition: color 0.15s ease, text-decoration-color 0.15s ease;
}
@media (hover: hover) and (pointer: fine) {
  .link-text:hover {
    color: var(--text);
    text-decoration-color: currentColor;
  }
}
.links-sep {
  color: var(--text-muted);
  opacity: 0.4;
  font-size: 10px;
  user-select: none;
}

/* ===== Streaming icons row =====
   YouTube / Spotify / SoundCloud / Apple Music. Icon-only buttons; verified
   official channels get a small blue check. Unknown channels still link out
   via a search URL on that platform so the buttons always do something. */
.streaming-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.stream-icon {
  position: relative;
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  text-decoration: none;
  color: var(--text);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.10);
  transition: transform 0.15s, background 0.2s, color 0.2s, box-shadow 0.2s;
}
.stream-icon > svg {
  width: 16px;
  height: 16px;
}
.stream-icon--soundcloud > svg {
  width: 18px;
  height: 18px;
}

@media (hover: hover) and (pointer: fine) {
  .stream-icon:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
  }
  .stream-icon--youtube:hover    { background: #FF0000; color: #fff; }
  .stream-icon--spotify:hover    { background: #1DB954; color: #fff; }
  .stream-icon--soundcloud:hover { background: #FF5500; color: #fff; }
  .stream-icon--applemusic:hover { background: linear-gradient(135deg, #FA233B, #FB5C74); color: #fff; }
}

.stream-verified {
  position: absolute;
  inset-inline-end: -3px;
  bottom: -2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #1d9bf0;          /* Twitter/X blue check */
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--card-bg, #0a0524);
  box-shadow: 0 2px 6px rgba(29, 155, 240, 0.6);
}

/* Backward-compat shim for the old .info-rep chip (now rendered as
   plain text via .info-meta-row → .info-rep-text). Strips the chip
   chrome in case any cached bundle still emits the old class. */
.info-rep {
  display: inline;
  padding: 0;
  background: none;
  border: none;
  border-radius: 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--sun-yellow);
  opacity: 0.85;
}

/* ===== Pager dots (horizontal) ===== */
.dots {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 6px;
  z-index: 12;
  /* Idle state: just the dots, no pill chrome. Generous padding makes the
     whole strip a bigger tap target (the user's first tap arms it without
     navigating). */
  background: transparent;
  border: 1px solid transparent;
  padding: 18px 24px;
  border-radius: 999px;
  transition: bottom 0.25s ease, background 0.25s ease, border-color 0.25s ease, padding 0.25s ease, box-shadow 0.25s ease;
  box-shadow: none;
}

/* Armed state — appears after the user's first tap on the strip OR while
   a desktop mouse pointer hovers over the strip area. The pill chrome
   appears (so the targets read as buttons) and the dots magnify based on
   pointer X. A 2s timer reverts back to idle when not hovering. */
.dots.is-armed,
.dots.is-hovered,
.hero-dots.is-armed,
.hero-dots.is-hovered {
  background: rgba(10, 5, 36, 0.92);
  border-color: var(--card-border);
  padding: 12px 18px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}

.dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.55);
  /* Magnify via transform: scale so the dot's LAYOUT BOX never changes
     size — siblings don't shift around as the focus moves. That was the
     source of the cursor-flicker on hover (dot grows → flex re-lays-out
     neighbours → cursor now over a different dot → cycle). transform
     scaling renders visually larger without touching layout. */
  transform: scale(0.78); /* 7/9 — pre-armed dots appear slightly smaller */
  transform-origin: center;
  transition: transform 0.18s ease, background 0.2s ease;
  border: none;
  padding: 0;
  cursor: pointer;
  box-sizing: content-box;
}

/* Armed/hovered: dots become more visible (full size), with dock-style
   graduated magnification driven by [data-near] (set by JS pointer
   tracking). All magnification is transform-only so no layout shifts. */
.dots.is-armed .dot,
.dots.is-hovered .dot,
.hero-dots.is-armed .hero-dot,
.hero-dots.is-hovered .hero-dot {
  transform: scale(1);
}
.dots.is-armed .dot.active,
.dots.is-hovered .dot.active,
.hero-dots.is-armed .hero-dot.active,
.hero-dots.is-hovered .hero-dot.active {
  /* Subtle proportional bump only — was scaleX(2.45) which turned the
     active pill into a comically-long bar on hover. The pill is
     already pre-stretched to 18px wide via .dot.active { width:18px },
     so a uniform scale(1.1) just makes it pop ~10% bigger relative to
     the rest of the strip without distorting its 2:1 aspect ratio. */
  transform: scale(1.1);
}

/* Per-dot dock-magnification on the horizontal strips was retired:
   the strip's own armed/hovered chrome already gives enough visual
   feedback, and magnifying each dot on top of that felt like
   double-action. Vertical-progress dots keep their data-near
   magnification (see .vertical-progress rules below) since the
   strip itself doesn't grow there. */

/* Soft fade over the bottom of the panel so scrolled content doesn't visually
   collide with the dots pill above it. */
.section[data-section="artist"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 80px;
  background: linear-gradient(180deg, transparent, rgba(10, 5, 36, 0.85));
  pointer-events: none;
  z-index: 8;
}

body.has-mini-player .section[data-section="artist"]::after {
  bottom: calc(70px + env(safe-area-inset-bottom, 0px));
}

/* When the mini-player is open, lift the dots so they're not covered. */
body.has-mini-player .dots {
  bottom: calc(70px + env(safe-area-inset-bottom, 0px) + 16px);
}

/* Lift the panel content too so its bottom isn't hidden behind the player. */
body.has-mini-player .panel {
  padding-bottom: calc(70px + env(safe-area-inset-bottom, 0px) + 16px);
}

.dot.active {
  background: var(--sun-yellow);
  width: 18px;
  border-radius: 999px;
  box-shadow: 0 0 10px var(--sun-yellow);
}

/* ===== Vertical progress bar ===== */
.vertical-progress {
  position: fixed;
  top: 50%;
  /* Pin to the left edge with a hard right:auto so a stale `right:8px`
     from the inline critical CSS can't co-anchor and stretch the
     strip across the viewport (which made the dots float in the
     middle on desktop). */
  left: 18px;
  right: auto;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  z-index: 20;
  max-height: 70vh;
  /* Allow magnified dots to extend horizontally beyond the strip's
     natural column width without being clipped. The vertical limit
     (max-height) is still enforced. */
  overflow-y: hidden;
  overflow-x: visible;
  /* Always-on tap-target padding (was previously desktop-only). The
     negative margin keeps the strip's visual position the same as before
     so the dots still hug the edge of the screen. */
  padding: 8px 18px 8px 4px;
  margin-inline-start: -4px;
  pointer-events: auto;
  opacity: 1;
  transition: opacity 0.4s;
}

/* Wide invisible "approach zone" that extends rightward of the visible
   strip on desktop. Mouse entering this zone counts as hovering the
   strip — that's how the dots reveal themselves the moment the cursor
   gets near, instead of only when the user manages to land directly on
   one of them. The pseudo doesn't grow the visible footprint; it only
   intercepts hit-testing for the hover handler in app.js. */
@media (hover: hover) and (pointer: fine) {
  .vertical-progress::before {
    content: "";
    position: absolute;
    top: -10px;
    bottom: -10px;
    left: -8px;
    right: -90px;
    pointer-events: auto;
  }
  /* While the mouse is in the approach zone, force the strip to stay
     visible — the auto-hide / is-idle states are overridden so it
     doesn't disappear under the cursor. */
  .vertical-progress.is-hovered,
  .vertical-progress.is-hovered.is-idle,
  .vertical-progress.is-hovered.is-hidden {
    opacity: 1 !important;
    pointer-events: auto !important;
  }
}

/* Auto-hide when there's no scroll activity for ~2s. Wakes up the moment
   the user scrolls again. Both classes can apply; either one hides it. */
.vertical-progress.is-hidden,
.vertical-progress.is-idle {
  opacity: 0;
  pointer-events: none;
}

.v-dot {
  /* Layout box stays a fixed size; magnification happens via transform
     so adjacent dots never shift around. The previous height-based
     magnification was the source of the cursor-flicker the user saw on
     hover — growing one dot pushed neighbours, the cursor ended up on
     a different dot, that one grew, layout shifted again, etc. */
  width: 4px;
  height: 14px;
  border-radius: 999px;
  background: rgba(255,255,255,0.25);
  transform: scale(0.78);
  transform-origin: center;
  transition: transform 0.18s ease, background 0.22s ease, box-shadow 0.22s ease;
  border: none;
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
}

.v-dot.active {
  background: var(--sun-yellow);
  box-shadow: 0 0 10px var(--sun-yellow);
  transform: scaleY(1.6);
}

/* Two-stage tap + dock-style magnification on the vertical strip,
   mirroring the horizontal panel-dot behaviour. Both armed (tap-driven)
   and hovered (desktop pointer-driven) states share the same look so
   the strip behaves consistently across input methods. */
.vertical-progress.is-armed .v-dot,
.vertical-progress.is-hovered .v-dot {
  background: rgba(255,255,255,0.45);
  transform: scale(1);
}

.vertical-progress.is-armed .v-dot[data-near="2"],
.vertical-progress.is-hovered .v-dot[data-near="2"] { transform: scaleY(1.3); }
.vertical-progress.is-armed .v-dot[data-near="1"],
.vertical-progress.is-hovered .v-dot[data-near="1"] { transform: scaleY(1.7); }
.vertical-progress.is-armed .v-dot[data-near="0"],
.vertical-progress.is-hovered .v-dot[data-near="0"] {
  transform: scaleX(1.25) scaleY(2.3);
}
/* Brighten the focused dot on inactive sections so it reads as the
   tap target. The active dot keeps its yellow accent untouched. */
.vertical-progress.is-armed .v-dot[data-near="0"]:not(.active),
.vertical-progress.is-hovered .v-dot[data-near="0"]:not(.active) {
  background: rgba(255,255,255,0.85);
}

/* Active dot keeps its yellow accent but follows the same graduated
   magnification so the user can still see where they are in the lineup. */
.vertical-progress.is-armed .v-dot.active,
.vertical-progress.is-hovered .v-dot.active            { transform: scaleY(1.85); }
.vertical-progress.is-armed .v-dot.active[data-near="2"],
.vertical-progress.is-hovered .v-dot.active[data-near="2"] { transform: scaleY(2.1); }
.vertical-progress.is-armed .v-dot.active[data-near="1"],
.vertical-progress.is-hovered .v-dot.active[data-near="1"] { transform: scaleY(2.4); }
.vertical-progress.is-armed .v-dot.active[data-near="0"],
.vertical-progress.is-hovered .v-dot.active[data-near="0"] {
  transform: scaleX(1.5) scaleY(2.85);
}

/* ===== Bottom-anchored mini-player (Spotify-style) ===== */
.mini-player {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 30;
  will-change: transform;
  background: linear-gradient(180deg, rgba(10, 5, 36, 0.92) 0%, rgba(10, 5, 36, 0.98) 100%);
  border-top: 1.5px solid var(--card-border);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 -10px 36px rgba(0, 0, 0, 0.4);
  display: grid;
  grid-template-columns: 88px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  padding-bottom: max(10px, env(safe-area-inset-bottom));
  transform: translateY(100%);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.32s cubic-bezier(0.2, 0.9, 0.3, 1.1), opacity 0.25s;
}

.mini-player-controls {
  display: flex;
  align-items: center;
  gap: 6px;
}

.mini-player-next {
  font-size: 10px;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 2px;
}

.mini-player-next.has-next::before {
  content: "▶ הבא: ";
  color: var(--neon-cyan);
  font-weight: 700;
}

.mini-player.is-open {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
}

/* Desktop: center the mini-player as a fixed-width pill instead of letting
   it span the full width like a Spotify bar. Looks more like Apple Music's
   floating Now Playing pill on macOS. */
@media (min-width: 768px) {
  .mini-player {
    left: 50%;
    right: auto;
    bottom: 16px;
    width: min(620px, calc(100vw - 32px));
    border: 1px solid var(--card-border);
    border-top: 1px solid var(--card-border);
    border-radius: 18px;
    padding: 10px 14px;
    transform: translate(-50%, calc(100% + 32px));
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5);
  }
  .mini-player.is-open {
    transform: translate(-50%, 0);
  }
}

.mini-player-frame {
  position: relative;
  width: 88px;
  height: 50px;
  background: #000;
  border-radius: 8px;
  overflow: hidden;
}

.mini-player-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.mini-player-info {
  min-width: 0;
  overflow: hidden;
}

.mini-player-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mini-player-artist {
  font-size: 11px;
  color: var(--sun-yellow);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mini-player-btn {
  border: none;
  background: rgba(255, 255, 255, 0.06);
  color: var(--text);
  width: 32px;
  height: 32px;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s;
}

@media (hover: hover) and (pointer: fine) {
  .mini-player-btn:hover {
    background: var(--sun-pink);
    color: var(--bg-deep);
    transform: scale(1.05);
  }
  #mini-player-close:hover {
    background: var(--neon-magenta);
    color: white;
  }
}

/* Expanded view: large 16:9 player on top, info below, controls at the
   bottom — buttons NEVER overlay the iframe. */
.mini-player.is-expanded {
  /* Explicit named-area layout: controls on top, frame in the middle, info
     at the bottom. Using grid-template-areas guarantees the row order
     across browsers — earlier `grid-row: N` declarations weren't always
     respected when grid-template-rows used `auto auto auto`. */
  display: grid;
  grid-template-columns: 1fr;
  grid-template-areas:
    "controls"
    "frame"
    "info";
  gap: 10px;
  padding: 12px 16px;
  padding-bottom: max(12px, env(safe-area-inset-bottom));
  cursor: default;
}

/* Expanded state: controls are the FIRST row (above the iframe) so the
   close/skip/expand buttons never overlay the YouTube player. Order along
   the row: close (start) · expand (middle) · skip (end). */
.mini-player.is-expanded .mini-player-controls {
  grid-area: controls;
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
}

.mini-player.is-expanded .mini-player-frame {
  grid-area: frame;
  width: 100%;
  max-width: 720px;
  height: auto;
  aspect-ratio: 16 / 9;
  margin: 0 auto;
  border-radius: 12px;
}

.mini-player.is-expanded .mini-player-info {
  grid-area: info;
  text-align: center;
}

/* Expand/collapse icon swaps based on state. Show only the appropriate one. */
#mini-player-expand .icon-when-expanded { display: none; }
.mini-player.is-expanded #mini-player-expand .icon-when-collapsed { display: none; }
.mini-player.is-expanded #mini-player-expand .icon-when-expanded { display: block; }

/* When collapsed, the entire bar is a hint that there's more — hand cursor
   on the non-button area to telegraph "tap me to enlarge". */
.mini-player:not(.is-expanded) {
  cursor: pointer;
}
.mini-player:not(.is-expanded) .mini-player-btn { cursor: pointer; }

@media (max-width: 480px) {
  .mini-player {
    grid-template-columns: 60px minmax(0, 1fr) auto;
    gap: 8px;
    padding: 8px 10px;
    padding-bottom: max(8px, env(safe-area-inset-bottom));
  }
  .mini-player-frame { width: 60px; height: 36px; }
  .mini-player-btn { width: 30px; height: 30px; }
  .mini-player-title { font-size: 12px; }
  .mini-player-artist { font-size: 10px; }
  .mini-player-next { display: none; }
  .mini-player-controls { gap: 4px; }
}

/* ===== Top bar: search left · ZNA brand center · stage right ===== */
.top-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 25;
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  pointer-events: none;
  /* Default (EN / PT): stage dropdown leads, search+heart trail —
     i.e. dropdown on the visual LEFT of the bar, the other controls
     on the right. DOM order is unchanged (search+heart first, then
     dropdown), so row-reverse just flips the visual placement. The
     :lang(he) rule below restores natural row order so the Hebrew
     layout keeps the dropdown on the right (its leading edge). */
  flex-direction: row-reverse;
}
:lang(he) .top-bar { flex-direction: row; }

.top-bar > * { pointer-events: auto; }

.top-bar-side {
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  align-items: center;
  flex-shrink: 0;
}

/* ZNA brand pinned to true viewport center, regardless of side widths. */
.top-bar-center {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

/* ===== Apple Liquid Glass primitives ===== */
.glass-card {
  background: rgba(255, 255, 255, 0.10);
  border: 1px solid rgba(255, 255, 255, 0.22);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow:
    0 8px 32px rgba(0, 0, 0, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    inset 0 -1px 0 rgba(0, 0, 0, 0.15);
  color: var(--text);
  position: relative;
  overflow: hidden;
}

/* Top-bar favorites pill — heart icon + counter badge. Hidden by default
   (the [hidden] attr is removed by JS once at least one artist is
   favorited). Counter sits OUTSIDE the circle, top-right; override to
   overflow: visible here so the badge isn't clipped. */
.favorites-btn {
  position: relative;
  color: #ff5c8a;
  overflow: visible;
}

.favorites-btn[hidden] { display: none; }

.favorites-btn .favorites-count {
  position: absolute;
  top: -5px;
  right: -5px;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: #ff5c8a;
  color: #1a0a3a;
  font-family: "Heebo", sans-serif;
  font-size: 11px;
  font-weight: 800;
  line-height: 20px;
  text-align: center;
  box-shadow: 0 2px 8px rgba(255, 92, 138, 0.55), 0 0 0 2px rgba(10, 5, 36, 0.85);
  pointer-events: none;
  z-index: 2;
}

/* ===== Clean top-bar buttons (no pill chrome) =====
   The icons sit directly on the canvas with just a soft drop-shadow so
   they remain readable on top of bright hero gradients, and lean on
   color/opacity for hover state instead of a circular background. */
.top-bar-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  padding: 8px 4px;
  color: var(--text);
  cursor: pointer;
  font-family: inherit;
  font-weight: 700;
  border-radius: 8px;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.55));
  transition: color 0.15s ease, transform 0.12s ease, filter 0.15s ease;
}
.top-bar-btn:active { transform: scale(0.94); }
@media (hover: hover) and (pointer: fine) {
  .top-bar-btn:hover {
    color: var(--sun-yellow);
    filter: drop-shadow(0 0 10px rgba(254, 180, 71, 0.55));
  }
}

/* Top-bar favorites variant: heart icon stays pink, badge keeps its
   absolute-positioned chip — no surrounding circle any more. */
.favorites-btn.top-bar-btn { color: #ff5c8a; position: relative; }

/* Stage-select trigger: completely chromeless on the header — no
   border, no pill — and the chevron is ALWAYS positioned on the
   leading visual side (left of the label) regardless of language.
   The drop-shadow on .top-bar-btn keeps the text + glyph readable
   over bright hero gradients. */
.stage-select-trigger.top-bar-btn {
  padding: 6px 4px;
  border: none;
  border-radius: 0;
  font-size: 13px;
  letter-spacing: 0.3px;
  /* Natural row order: label first, chevron second — chevron sits on
     the RIGHT of the text in LTR languages (EN/PT). The button's
     DOM is `<span>label</span><svg/>`. */
}
.stage-select-trigger.top-bar-btn svg {
  filter: none; /* the parent already drop-shadows */
}

/* ===== Top-bar logo =====
   Clean ZNA26 mark for navigation. The full animated composition now
   lives inside the Main hero instead of being stretched from the header. */
.top-bar-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  transition: transform 0.1s cubic-bezier(0.4, 0.05, 0.2, 1.0),
              filter 0.1s ease;
  filter: drop-shadow(0 4px 14px rgba(0, 0, 0, 0.55));
  -webkit-tap-highlight-color: transparent;
}
.top-bar-logo-img {
  display: block;
  height: 38px;
  width: auto;
  max-width: 40vw;
  transition: height 0.1s cubic-bezier(0.4, 0.05, 0.2, 1.0);
  user-select: none;
  -webkit-user-drag: none;
}
.top-bar-logo:active { transform: scale(0.96); }
@media (hover: hover) and (pointer: fine) {
  .top-bar-logo:hover {
    filter: drop-shadow(0 0 14px rgba(255, 92, 138, 0.45)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.55));
  }
}

body.is-on-hero .top-bar-logo {
  transform: none;
}
body.is-on-hero .top-bar-logo-img {
  height: 38px;
}
/* Main hero already shows the large ZNA mark — fade out the header
   duplicate so swiping between hero panels doesn't make the header logo
   blink on/off as the user crosses the 30%-threshold back and forth. */
.top-bar-center {
  opacity: 1;
  transition: opacity 0.32s ease;
}
body.is-on-main-hero .top-bar-center {
  opacity: 0;
  pointer-events: none;
}

@media (max-width: 480px) {
  body.is-on-hero .top-bar-logo-img { height: 32px; }
  .top-bar-logo-img { height: 32px; }
}

/* Stage selector */
.stage-select { position: relative; }

.stage-select-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 16px;
  border-radius: 999px;
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  letter-spacing: 0.3px;
  transition: transform 0.15s;
  max-width: 38vw;
  white-space: nowrap;
  overflow: hidden;
}

@media (max-width: 480px) {
  .stage-select-trigger { padding: 10px 6px; font-size: 12px; max-width: 32vw; }
  .top-bar { gap: 6px; top: 0; left: 0; right: 0; }
  .top-bar-side { gap: 6px; }
}

.stage-select-trigger > span {
  overflow: hidden;
  text-overflow: ellipsis;
}

.stage-select-trigger svg {
  transition: transform 0.25s;
  opacity: 0.85;
}

/* (Chevron-left-of-label is now applied to .stage-select-trigger
   universally via .stage-select-trigger.top-bar-btn above — same
   placement in every language.) */

.stage-select.is-open .stage-select-trigger svg {
  transform: rotate(180deg);
}

/* Hebrew typography pass: the Latin defaults (weight 700 + 0.3px tracking)
   give Heebo a heavy, spread-out feel on short labels like "הכל". Match
   the cleaner .hero-tagline body voice — weight 500, no tracking — so the
   header label reads as a calm chip rather than a bold callout. */
:lang(he) .stage-select-trigger,
:lang(he) .stage-select-trigger > span {
  font-weight: 500;
  letter-spacing: 0;
}

.stage-select-menu {
  list-style: none;
  position: absolute;
  top: calc(100% + 10px);
  /* Anchor follows the trigger's screen position so the panel always
     opens INTO the viewport, never out of it:
       * EN / PT — top-bar flips so the dropdown sits on the screen-LEFT.
         Pin the menu's left edge to the trigger and let it extend
         rightward.
       * Hebrew — top-bar is in natural order; trigger sits on the
         screen-RIGHT. Pin the menu's right edge to the trigger and let
         it extend leftward.
     A previous version always used right:0 which pushed the menu off
     the left edge of the screen the moment the layout was flipped to
     EN/PT. */
  left: 0;
  right: auto;
  border-radius: 18px;
  padding: 8px;
  min-width: 220px;
  max-width: min(320px, calc(100vw - 28px));
  max-height: 70vh;
  overflow-y: auto;
  animation: glass-pop 0.22s cubic-bezier(0.2, 0.9, 0.3, 1.2) both;
  display: block;
  z-index: 30;
}
:lang(he) .stage-select-menu {
  left: auto;
  right: 0;
}

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

.stage-select-menu li { padding: 0; margin: 0; }

.stage-select-menu button {
  width: 100%;
  text-align: start;
  border: none;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 11px 14px;
  border-radius: 12px;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  transition: background 0.15s;
}

@media (hover: hover) and (pointer: fine) {
  .stage-select-menu button:hover {
    background: rgba(255, 255, 255, 0.08);
  }
}

.stage-select-menu button.active {
  background: linear-gradient(135deg, rgba(255, 92, 138, 0.85), rgba(255, 126, 31, 0.85));
  color: white;
  font-weight: 800;
}

/* Language picker row at the bottom of the stage dropdown menu — three
   columns of flag + name. Tapping switches the whole UI language. */
.stage-select-menu .lang-row {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 4px;
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid rgba(255, 255, 255, 0.10);
}

.stage-select-menu .lang-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 8px 4px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 10px;
  cursor: pointer;
  color: var(--text);
  font-family: inherit;
  font-size: 11px;
  transition: background 0.15s, border-color 0.15s, transform 0.15s;
}

@media (hover: hover) and (pointer: fine) {
  .stage-select-menu .lang-cell:hover {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.12);
  }
}

.stage-select-menu .lang-cell.active {
  background: linear-gradient(135deg, rgba(255, 92, 138, 0.85), rgba(255, 126, 31, 0.85));
  color: white;
  font-weight: 800;
  border-color: rgba(255, 255, 255, 0.25);
}

.stage-select-menu .lang-cell-code {
  font-family: "Orbitron", "Heebo", system-ui, sans-serif;
  font-size: 16px;
  font-weight: 800;
  letter-spacing: 1.5px;
  line-height: 1;
  color: var(--text);
  opacity: 0.95;
}
.stage-select-menu .lang-cell.active .lang-cell-code {
  color: white;
  opacity: 1;
}

.stage-select-menu .count {
  opacity: 0.7;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

/* Compact "Contact us" mailto row at the very bottom of the dropdown. */
.stage-select-menu .contact-row {
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.stage-select-menu .contact-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 6px 8px;
  border-radius: 8px;
  color: var(--text);
  opacity: 0.7;
  font-size: 11px;
  font-weight: 500;
  text-decoration: none;
  transition: background 0.15s, opacity 0.15s;
}
.stage-select-menu .contact-link svg {
  opacity: 0.85;
}
@media (hover: hover) and (pointer: fine) {
  .stage-select-menu .contact-link:hover {
    background: rgba(255, 255, 255, 0.06);
    opacity: 1;
  }
}

/* ===== Search overlay ===== */
.search-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(5, 2, 18, 0.85);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 80px;
  animation: fade-in 0.2s ease-out both;
}

/* The native [hidden] attribute is overridden by our display:flex above,
   so re-assert it explicitly. Same for the stage menu. */
.search-overlay[hidden] { display: none; }
.stage-select-menu[hidden] { display: none; }

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

.search-panel {
  width: min(560px, calc(100vw - 28px));
  max-height: 70vh;
  border-radius: 22px;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  animation: glass-pop 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.2) both;
}

.search-input {
  width: 100%;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 14px;
  color: var(--text);
  font-family: inherit;
  font-size: 16px;
  padding: 14px 18px;
  outline: none;
  /* EN/PT: Latin-heavy queries — LTR + align start (left). */
  direction: ltr;
  text-align: left;
}

/* Hebrew UI: RTL typing + placeholder order; keep text visually left-aligned. */
.search-input[dir="rtl"] {
  direction: rtl;
  text-align: left;
}

.search-input::placeholder {
  text-align: left;
}

.search-input:focus {
  border-color: var(--sun-yellow);
  box-shadow: 0 0 0 3px rgba(254, 180, 71, 0.2);
}

.search-results {
  overflow-y: auto;
  display: grid;
  gap: 6px;
  padding-inline-end: 4px;
}

.search-result {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.08);
  cursor: pointer;
  transition: background 0.15s;
  /* Match the search input — names render LTR consistently across UI
     languages so the artist-name column always reads left-to-right. */
  direction: ltr;
  text-align: left;
}

@media (hover: hover) and (pointer: fine) {
  .search-result:hover {
    background: rgba(254, 180, 71, 0.15);
    border-color: rgba(254, 180, 71, 0.4);
  }
}

.search-result-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: flex-start;
  min-width: 0;
}
.search-result-name { font-weight: 700; color: var(--text); font-size: 14px; text-align: left; line-height: 1.25; }
.search-result-meta { font-size: 11px; color: var(--text-muted); text-align: left; line-height: 1.25; }
.search-empty { text-align: center; padding: 22px; color: var(--text-muted); font-size: 14px; }

/* ===== Favorites overlay ===== */
.favorites-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(5, 2, 18, 0.85);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 80px;
  animation: fade-in 0.2s ease-out both;
}
.favorites-overlay[hidden] { display: none; }

.favorites-panel {
  width: min(560px, calc(100vw - 28px));
  max-height: 70vh;
  border-radius: 22px;
  padding: 16px 18px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  animation: glass-pop 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.2) both;
}

.favorites-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.favorites-title {
  margin: 0;
  font-family: "Heebo", sans-serif;
  font-size: 18px;
  font-weight: 800;
  color: var(--text);
}

.favorites-close {
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  color: var(--text);
  width: 36px;
  height: 36px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
@media (hover: hover) and (pointer: fine) {
  .favorites-close:hover {
    background: rgba(255, 92, 138, 0.18);
    border-color: rgba(255, 92, 138, 0.5);
    color: #ff5c8a;
  }
}

.favorites-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: grid;
  gap: 6px;
  padding-inline-end: 4px;
}

/* Each favorites row now has two clickable areas: the main button
   navigates to the artist, the heart button on the right removes them
   from favorites. Pure-CSS no-op for the old <button class="favorites-row">
   selector (we rebuild the DOM as <div> + child buttons). */
.favorites-row {
  display: flex;
  align-items: stretch;
  gap: 6px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.08);
  overflow: hidden;
  transition: background 0.15s, border-color 0.15s;
}
@media (hover: hover) and (pointer: fine) {
  .favorites-row:hover {
    background: rgba(255, 92, 138, 0.14);
    border-color: rgba(255, 92, 138, 0.4);
  }
}

.favorites-row-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: transparent;
  border: 0;
  text-align: start;
  font-family: inherit;
  color: var(--text);
  cursor: pointer;
}

.favorites-row-name {
  font-weight: 700;
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.favorites-row-date {
  font-size: 12px;
  color: var(--text-muted);
  white-space: nowrap;
  flex-shrink: 0;
}

.favorites-row-remove {
  flex: 0 0 auto;
  width: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-inline-start: 1px solid rgba(255, 255, 255, 0.06);
  color: #ff5c8a;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, transform 0.15s;
}
@media (hover: hover) and (pointer: fine) {
  .favorites-row-remove:hover {
    background: rgba(255, 92, 138, 0.18);
    color: #ff5c8a;
  }
}
.favorites-row-remove:active { transform: scale(0.92); }
.favorites-empty {
  text-align: center;
  padding: 32px 16px;
  color: var(--text-muted);
  font-size: 14px;
}

/* "Get notifications" CTA at the bottom of the favorites panel.
   Sits below the list with a divider, then a description line and a
   bell-icon button. Toggles to a "Notifications on" green pill once the
   user grants permission. */
.favorites-notify {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  text-align: center;
  margin-top: 4px;
  padding-top: 14px;
  border-top: 1px dashed rgba(255, 255, 255, 0.14);
}

.favorites-notify-desc {
  margin: 0;
  font-size: 12.5px;
  color: var(--text-muted);
  line-height: 1.4;
  max-width: 360px;
}

/* Two channel pills sit side-by-side: browser (yellow→pink gradient,
   reflects Notification.permission) and Telegram (blue, always
   available so iOS users without the Notification API still have a
   real opt-in path). */
.favorites-notify-channels {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}

.favorites-notify-channel {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-radius: 999px;
  font-family: "Heebo", sans-serif;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.4px;
  cursor: pointer;
  border: 1px solid transparent;
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  text-decoration: none;
  transition: background 0.18s, border-color 0.18s, transform 0.15s, color 0.18s;
}

.favorites-notify-channel:active { transform: scale(0.97); }
.favorites-notify-channel svg { flex-shrink: 0; }

.favorites-notify-channel.is-browser {
  border-color: rgba(254, 180, 71, 0.55);
  background: linear-gradient(135deg, rgba(254, 180, 71, 0.18), rgba(255, 92, 138, 0.16));
  color: var(--sun-yellow);
}

@media (hover: hover) and (pointer: fine) {
  .favorites-notify-channel.is-browser:hover {
    background: linear-gradient(135deg, rgba(254, 180, 71, 0.3), rgba(255, 92, 138, 0.25));
    border-color: rgba(254, 180, 71, 0.85);
  }
}

.favorites-notify-channel.is-on {
  border-color: rgba(57, 255, 20, 0.7);
  background: rgba(57, 255, 20, 0.12);
  color: #39ff14;
  cursor: default;
}

.favorites-notify-channel.is-disabled {
  border-color: rgba(255, 255, 255, 0.15);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text-muted);
  cursor: not-allowed;
}

.favorites-notify-channel.is-telegram {
  border-color: rgba(56, 175, 226, 0.55);
  background: linear-gradient(135deg, rgba(56, 175, 226, 0.18), rgba(0, 153, 255, 0.16));
  color: #5ec3ee;
}

@media (hover: hover) and (pointer: fine) {
  .favorites-notify-channel.is-telegram:hover {
    background: linear-gradient(135deg, rgba(56, 175, 226, 0.3), rgba(0, 153, 255, 0.25));
    border-color: rgba(56, 175, 226, 0.85);
    color: #38afe2;
  }
}

.favorites-notify-channel-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ===== First-visit language picker ===== */
.welcome-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(5, 2, 18, 0.88);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  animation: fade-in 0.25s ease-out both;
}
.welcome-overlay[hidden] { display: none; }

.welcome-panel {
  width: min(440px, calc(100vw - 28px));
  border-radius: 24px;
  padding: 28px 24px 26px;
  display: flex;
  flex-direction: column;
  gap: 22px;
  text-align: center;
  animation: glass-pop 0.3s cubic-bezier(0.2, 0.9, 0.3, 1.2) both;
}

.welcome-header {
  display: flex;
  flex-direction: column;
  gap: 14px;
  align-items: center;
}

.welcome-brand {
  font-family: "Orbitron", sans-serif;
  font-size: 14px;
  letter-spacing: 4px;
  background: linear-gradient(180deg, var(--sun-yellow), var(--sun-pink));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.welcome-titles {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.welcome-title-line {
  font-family: "Heebo", sans-serif;
  font-size: 18px;
  font-weight: 800;
  color: var(--text);
  line-height: 1.2;
}

.welcome-title-line + .welcome-title-line {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-muted);
}

.welcome-langs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}

.welcome-lang {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 18px 10px 14px;
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.12);
  color: var(--text);
  cursor: pointer;
  font-family: inherit;
  transition: background 0.18s, border-color 0.18s, transform 0.15s, box-shadow 0.2s;
}

@media (hover: hover) and (pointer: fine) {
  .welcome-lang:hover {
    background: rgba(254, 180, 71, 0.10);
    border-color: rgba(254, 180, 71, 0.45);
  }
}

.welcome-lang:active {
  transform: scale(0.97);
}

.welcome-lang.is-default {
  background: linear-gradient(160deg, rgba(254, 180, 71, 0.18), rgba(255, 92, 138, 0.16));
  border-color: rgba(254, 180, 71, 0.7);
  box-shadow: 0 8px 30px rgba(254, 180, 71, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

.welcome-lang-code {
  font-family: "Orbitron", sans-serif;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: 1px;
  background: linear-gradient(180deg, var(--sun-yellow), var(--sun-pink));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.welcome-lang-name {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  letter-spacing: 0.4px;
}

.welcome-lang.is-default .welcome-lang-name {
  color: var(--text);
}

/* Entry transitions: element starts hidden, becomes visible when its section
   becomes active. Using transitions instead of keyframes avoids the visible
   flash that happened when the image painted and then the keyframe restarted
   it from opacity:0. */
.panel-inner {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.5s ease, transform 0.55s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.section.is-active .panel-inner {
  opacity: 1;
  transform: translateY(0);
}

.artist-hero-art {
  opacity: 0;
  transform: scale(0.9);
  transition: opacity 0.5s ease, transform 0.6s cubic-bezier(0.2, 0.9, 0.3, 1.3);
}

.section.is-active .artist-hero-art {
  opacity: 1;
  transform: scale(1);
}

.section-title {
  font-size: 18px;
  font-weight: 800;
  color: var(--sun-yellow);
  text-align: center;
  margin-bottom: 8px;
  letter-spacing: 1px;
}

/* Respect users (and battery-conscious devices) who prefer fewer animations */
@media (prefers-reduced-motion: reduce) {
  .stars,
  .bg-grid {
    animation: none !important;
  }
  .panel-inner,
  .artist-hero-art {
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* On phone-sized viewports, kill the continuous background animations
   entirely. They thrash GPU on iOS Safari and contribute to OOM crashes. */
@media (max-width: 820px) {
  .stars,
  .bg-grid {
    animation: none !important;
  }
}

.muted { color: var(--text-muted); font-size: 13px; text-align: center; }

@media (max-width: 380px) {
  .panel { padding: 70px 14px 70px; }
  .artist-hero-photo,
  .artist-hero-art { width: min(64vw, 200px); }
  .vertical-progress { left: 4px; }
}

@media (min-width: 768px) {
  .panel { padding: 100px 32px 120px; }
  .panel-inner { max-width: 720px; }
  .artist-hero-photo,
  .artist-hero-art { width: min(36vw, 320px); }
  .artist-name { font-size: clamp(40px, 5.5vw, 64px); }
  .bio-text { font-size: 18px; line-height: 1.6; }
  .bio-card { padding: 28px 32px; }
  .albums-list li { padding: 16px 22px; }
  .album-name { font-size: 16px; }
  .albums-list { gap: 10px; }
  .tracks-stack { gap: 16px; }
}

@media (min-width: 1024px) {
  .panel { padding: 110px 48px 140px; }
  .panel-inner { max-width: 1080px; }

  .hero-stage-element {
    right: max(-88px, -6vw);
    width: clamp(300px, 30vw, 520px);
  }

  /* === Artist Hero — editorial desktop layout ===
     Photo on the LEFT, text on the RIGHT (English magazine layout). Forces
     direction:ltr on the grid container so the columns flow left → right
     regardless of the text content's own bidi resolution. */
  .panel-hero { padding: 120px 56px; text-align: left; direction: ltr; }
  .panel-hero .panel-inner {
    max-width: 1080px;
    display: grid;
    grid-template-columns: minmax(280px, 420px) 1fr;
    gap: 64px;
    align-items: center;
    text-align: left;
    direction: ltr;
  }
  .panel-hero .artist-hero-photo,
  .panel-hero .artist-hero-art { grid-column: 1; }
  .panel-hero .artist-hero-text { grid-column: 2; text-align: left; align-items: flex-start; }
  .panel-hero .artist-hero-text > * { text-align: left; }
  .panel-hero .artist-meta-line { justify-content: flex-start; }
  .panel-hero .artist-favorite { align-self: flex-start; }
  .panel-hero .artist-hero-photo {
    width: 100%;
    max-width: 420px;
    margin: 0;
  }
  .panel-hero .artist-hero-art {
    width: 100%;
    max-width: 420px;
    aspect-ratio: 1 / 1;
    margin: 0;
    border-radius: 24px;
  }
  .panel-hero .artist-hero-text { gap: 16px; }
  .panel-hero .artist-hero-text::before {
    content: "";
    display: block;
    width: 60px;
    height: 3px;
    background: linear-gradient(90deg, var(--accent, var(--sun-yellow)), transparent);
    border-radius: 2px;
    margin-bottom: 6px;
  }
  .panel-hero .artist-name {
    font-size: clamp(56px, 5.6vw, 96px);
    /* Slightly looser line-height and a small bottom pad give Orbitron's
       tall glyphs + descenders (e.g. lowercase "g") room to render the
       gradient (background-clip:text) all the way down. With the tight
       0.95 line-height + no padding, the last line's "g"/"y"/"p" tails
       were rendering past the element's background box and getting
       clipped to invisible (color:transparent). */
    line-height: 1.0;
    letter-spacing: -2px;
    margin: 0;
    padding-bottom: 0.12em;
  }
  .panel-hero .artist-meta-line { font-size: 15px; }

  /* Bio panel: wider, more readable */
  .bio-card { padding: 32px 36px; }
  .bio-text { font-size: 18px; line-height: 1.6; }

  /* Discography: 2-col when there are many albums */
  .albums-list { grid-template-columns: repeat(2, 1fr); gap: 12px; }

  /* Tracks: 2-col grid; 3-col on extra-wide */
  .tracks-stack { grid-template-columns: repeat(2, 1fr); gap: 18px; }
  .tracks-stack:has(> .track-card:only-child) {
    grid-template-columns: minmax(0, 560px);
    justify-content: center;
  }
}

@media (min-width: 1400px) {
  .panel { padding: 130px 80px 170px; }
  .panel-inner { max-width: 1280px; }
  .panel-hero { padding: 130px 80px; }
  .panel-hero .panel-inner {
    max-width: 1240px;
    grid-template-columns: minmax(360px, 480px) 1fr;
    gap: 80px;
  }
  .panel-hero .artist-hero-photo,
  .panel-hero .artist-hero-art { max-width: 480px; }
  .panel-hero .artist-name { font-size: clamp(72px, 6vw, 120px); padding-bottom: 0.12em; }
  .tracks-stack { grid-template-columns: repeat(3, 1fr); }
  .tracks-stack:has(> .track-card:only-child),
  .tracks-stack:has(> .track-card:nth-child(2):last-child) {
    grid-template-columns: repeat(2, minmax(0, 480px));
    justify-content: center;
  }
}

/* ===== User-requested overrides — must win the cascade ===== */

/* Center the description (Hebrew). Overrides the bidi block higher up. */
.hero-panel .hero-tagline {
  text-align: center !important;
  max-width: 480px;
  font-size: clamp(13px, 3.6vw, 17px);
  line-height: 1.55;
  /* Spacing handled by .hero-stack gap. */
  margin-bottom: 0;
}

/* Artist hero name should center even when it wraps to two lines. The
   bidi block earlier sets text-align:start, which makes the second line
   left-align as soon as the first line wraps. Force-center on mobile;
   the editorial desktop layout (>=1024px) explicitly re-overrides to
   left-align further down so the magazine treatment still works. */
.panel-hero .artist-name {
  text-align: center !important;
}
@media (min-width: 1024px) {
  .panel-hero .artist-name {
    text-align: left !important;
  }
}

/* Compact single-line meta — dates · location · count, no icons. */
.hero-panel .hero-meta {
  display: block;
  font-size: 12px;
  letter-spacing: 0.4px;
  color: var(--text-muted);
  /* Spacing handled by .hero-stack gap. */
  margin-bottom: 0;
  opacity: 0.85;
  background: none;
  border: none;
  border-radius: 0;
  padding: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  text-align: center !important;
}

/* Unofficial fan-site disclaimer on the Main hero only. */
.hero-disclaimer {
  /* Spacing handled by .hero-stack gap. */
  margin-top: 0;
  font-size: 10.5px;
  color: var(--text-muted);
  opacity: 0.55;
  letter-spacing: 0.3px;
  max-width: 360px;
  line-height: 1.45;
  text-align: center !important;
  font-style: italic;
}

/* Compact live status / countdown stack. The chrome (background, border,
   blur, padding) is gone — the timer + title + progress dot now sit
   directly on the hero gradient with no pill behind them, per the
   "no container" brief. Markup order also flips so the countdown
   reads first, the "event hasn't started" caption sits below it, and
   the progress dot line lives at the bottom of the stack. */
.hero-panel .live-status {
  width: min(340px, calc(100% - 36px));
  box-sizing: border-box;
  overflow: visible;
  display: grid;
  gap: 8px;
  padding: 0;
  /* Spacing handled by .hero-stack gap (+ optional .hero-stack > .live-status margin-top). */
  margin: 0 auto;
  background: transparent;
  border: none;
  border-radius: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  color: var(--text);
  text-align: center !important;
  box-shadow: none;
}

/* Countdown grid never wider than its parent pill. flex-wrap is on
   .countdown-timer, so units flow to a second row instead of pushing
   the pill horizontally. */
.hero-panel .live-status .countdown-timer {
  max-width: 100%;
  width: 100%;
  box-sizing: border-box;
}

.live-now-track {
  position: relative;
  height: 1px;
  width: 100%;
  background: linear-gradient(90deg,
    var(--sun-pink) 0,
    var(--sun-pink) 6px,
    rgba(255, 255, 255, 0.18) 6px,
    rgba(255, 255, 255, 0.18) 100%);
  margin: 2px 0 6px;
}

.live-now-dot {
  position: absolute;
  left: 0;
  top: 50%;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--sun-pink);
  box-shadow: 0 0 8px var(--sun-pink), 0 0 14px rgba(255, 92, 138, 0.55);
  transform: translate(-2px, -50%);
  animation: live-now-pulse 2s ease-in-out infinite;
}

@keyframes live-now-pulse {
  0%, 100% { opacity: 1;   box-shadow: 0 0 8px var(--sun-pink), 0 0 14px rgba(255, 92, 138, 0.55); }
  50%      { opacity: 0.7; box-shadow: 0 0 6px var(--sun-pink), 0 0 10px rgba(255, 92, 138, 0.35); }
}

.live-status-title {
  font-size: 14px !important;
  font-weight: 700;
  color: var(--text) !important;
  letter-spacing: 0.2px;
  line-height: 1.2 !important;
}

/* ===== Boxed digital countdown timer =====
   Used both in the hero "festival starts in" card (modifier: hero) and in
   each artist's set-time badge while the per-set schedule is still TBA
   (modifier: mini). The numbers tick every second via a global interval. */
.countdown-timer {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: stretch;
  justify-content: center;
  gap: 6px;
  margin-top: 4px;
  max-width: 100%;
  font-family: "Orbitron", "Heebo", system-ui, sans-serif;
  direction: ltr;
  unicode-bidi: isolate;
}

.countdown-unit {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  padding: 6px 8px 5px;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.62), rgba(10, 5, 36, 0.7));
  border: 1px solid rgba(254, 180, 71, 0.55);
  border-radius: 8px;
  box-shadow:
    inset 0 0 10px rgba(255, 200, 80, 0.18),
    0 0 12px rgba(255, 180, 71, 0.18);
}

.countdown-value {
  font-size: 22px;
  font-weight: 900;
  letter-spacing: 1px;
  line-height: 1;
  color: var(--sun-yellow);
  text-shadow: 0 0 8px rgba(255, 180, 71, 0.7);
  font-variant-numeric: tabular-nums;
}

.countdown-label {
  margin-top: 4px;
  font-size: 9px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--text-muted);
}

.countdown-sep {
  align-self: center;
  font-size: 22px;
  font-weight: 900;
  color: var(--sun-yellow);
  text-shadow: 0 0 8px rgba(255, 180, 71, 0.7);
  animation: countdown-sep-blink 1s steps(2, end) infinite;
}

/* When the countdown sits inside a hero panel, recolour it with the
   stage's accent palette so the timer reads as part of the panel's
   identity. Each .hero-panel-- variant already defines --hero-text-1
   / --hero-text-2 / --hero-text-3 via CSS custom properties; we pull
   from those so the timer borders + glow on Retro look orange-pink,
   Zambu purple-magenta, Guardians cyan-yellow, Market sun-orange,
   and the Main hero stays at its default yellow. */
.hero-panel .countdown-unit {
  border-color: color-mix(in oklab, var(--hero-text-1) 55%, transparent);
  box-shadow:
    inset 0 0 10px color-mix(in oklab, var(--hero-text-1) 22%, transparent),
    0 0 12px color-mix(in oklab, var(--hero-text-2, var(--hero-text-1)) 20%, transparent);
}
.hero-panel .countdown-value {
  color: var(--hero-text-1);
  text-shadow: 0 0 8px color-mix(in oklab, var(--hero-text-1) 70%, transparent);
}
.hero-panel .countdown-sep {
  color: var(--hero-text-2, var(--hero-text-1));
  text-shadow: 0 0 8px color-mix(in oklab, var(--hero-text-2, var(--hero-text-1)) 70%, transparent);
}
.hero-panel .countdown-label {
  color: color-mix(in oklab, var(--hero-text-1) 75%, white 25%);
  opacity: 0.85;
}

@keyframes countdown-sep-blink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0.25; }
}

.countdown-timer--hero { gap: 4px; }
.countdown-timer--hero .countdown-unit  { min-width: 0; padding: 3px 6px 2px; border-radius: 6px; flex: 0 1 auto; }
.countdown-timer--hero .countdown-value { font-size: 15px; letter-spacing: 0.4px; }
.countdown-timer--hero .countdown-sep   { font-size: 13px; }
.countdown-timer--hero .countdown-label { font-size: 7.5px; letter-spacing: 0.8px; margin-top: 2px; }

.countdown-timer--mini { gap: 4px; margin-top: 0; }
.countdown-timer--mini .countdown-unit  { min-width: 34px; padding: 4px 7px 3px; border-radius: 6px; }
.countdown-timer--mini .countdown-value { font-size: 15px; letter-spacing: 0.4px; }
.countdown-timer--mini .countdown-sep   { font-size: 13px; }
.countdown-timer--mini .countdown-label { font-size: 7.5px; letter-spacing: 0.9px; margin-top: 2px; }

.live-status-meta {
  font-size: 12px;
  color: var(--text-muted);
}

.live-status--live .live-status-title { color: #39ff14 !important; }
.live-now-track.is-live {
  background: linear-gradient(90deg, #39ff14 0, #39ff14 6px, rgba(57, 255, 20, 0.25) 6px, rgba(57, 255, 20, 0.25) 100%);
}
.live-now-track.is-live .live-now-dot {
  background: #39ff14;
  box-shadow: 0 0 8px #39ff14, 0 0 14px rgba(57, 255, 20, 0.55);
}

/* ===== "Live now" demo card on the Market hero =====
   Adds a top-line with a LIVE / Demo strip, a thin progress bar that
   ticks every second, and a previous/next set hand-off line so the
   user can see at a glance who is coming up next. (Originally built for
   the pre-schedule demo; now driven by the real timetable.)
   The double-class selector (.hero-panel .live-status.live-status--card)
   wins specificity over the generic .hero-panel .live-status block above
   so the wider/taller demo layout is respected. */
.hero-panel .live-status.live-status--card,
.live-status--card {
  /* Narrower + lower opacity so the now-playing card reads as a quiet
     status pill rather than a competing hero element. The faintly-glowing
     green border was reading as a "callout" — replace with a near-
     transparent white hairline + light backdrop blur for a calmer pro
     look. */
  width: min(86%, 360px);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  padding: 10px 14px;
  border-radius: 14px;
  background: rgba(10, 5, 36, 0.42);
  border: 1px solid rgba(255, 255, 255, 0.1);
  text-align: center;
  overflow: visible;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.live-status--card .live-status-topline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.live-status--card .live-now-track {
  width: auto;
  background: transparent;
  height: auto;
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0;
}

/* Demo card uses an inline-flex layout instead of the legacy absolute
   positioning, so the dot needs to flow normally next to the label. */
.live-status--card .live-now-track .live-now-dot {
  position: static;
  transform: none;
  flex-shrink: 0;
  width: 7px;
  height: 7px;
}

.live-status--card .live-now-label {
  font-family: "Heebo", sans-serif;
  font-size: 9px;
  letter-spacing: 2px;
  text-transform: uppercase;
  font-weight: 800;
  color: #39ff14;
  line-height: 1;
}

.live-demo-badge {
  font-family: "Heebo", sans-serif;
  font-size: 8px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  font-weight: 800;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-muted);
  border: 1px solid rgba(255, 255, 255, 0.18);
}

.live-status--card .live-status-title {
  font-family: "Orbitron", "Heebo", sans-serif;
  font-size: clamp(16px, 3.8vw, 22px);
  font-weight: 900;
  letter-spacing: 0.3px;
  margin: 2px 0 0;
}

.live-status--card .live-status-meta {
  font-size: 11px;
  color: var(--text-muted);
}

/* Hebrew typography pass for the live-now demo card.
   The Latin styling (Orbitron display face on the title, wide letter-spacing
   + uppercase on the eyebrows) doesn't translate to Hebrew — Hebrew has no
   uppercase, Orbitron has no Hebrew glyphs, and tracking out small Hebrew
   text leaves the letters floating apart. Drop those flourishes so Hebrew
   reads in the same calm Heebo body voice as .hero-tagline. */
:lang(he) .live-status--card .live-now-label,
:lang(he) .live-status--card .live-demo-badge,
:lang(he) .live-status--card .live-pn-label {
  font-family: "Heebo", sans-serif;
  letter-spacing: 0;
  text-transform: none;
  font-weight: 600;
  font-size: 10px;
}
:lang(he) .live-status--card .live-status-title {
  font-family: "Heebo", sans-serif;
  font-weight: 700;
  letter-spacing: 0;
}

.live-progress {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 2px;
}

.live-progress-track {
  position: relative;
  height: 3px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
}

.live-progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0%;
  background: linear-gradient(90deg, #39ff14, #00d4ff);
  border-radius: 999px;
  /* Subtle glow only — the heavy 12px glow read as decorative noise on
     a calmer card. */
  box-shadow: 0 0 6px rgba(57, 255, 20, 0.4);
  transition: width 0.6s linear;
}

.live-progress-times {
  display: flex;
  justify-content: space-between;
  font-size: 10px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.live-prev-next {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-top: 4px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  padding-top: 6px;
}

.live-pn-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 11px;
  font-family: "Heebo", sans-serif;
}

.live-pn-label {
  font-size: 9px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--text-muted);
}

.live-pn-name {
  font-weight: 700;
  color: var(--text);
}

.live-pn-prev .live-pn-name { color: var(--text-muted); }
.live-pn-next .live-pn-name { color: var(--sun-yellow); }

.hero-icon-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: transparent;
  border: none;
  padding: 0;
  color: rgba(255, 255, 255, 0.92);
  cursor: pointer;
  border-radius: 6px;
  transition: color 0.15s ease, transform 0.12s ease;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.hero-icon-action:active { transform: scale(0.92); }
@media (hover: hover) and (pointer: fine) {
  .hero-icon-action:hover {
    color: #fff;
  }
  .hero-icon-action:hover svg {
    filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.55));
  }
}
.hero-icon-action svg {
  width: 26px;
  height: 26px;
  display: block;
  pointer-events: none;
  transition: filter 0.18s ease;
}

/* Top-anchored variant — sits ABOVE the stage name on the stage hero
   panels. The flex parent (.hero-panel) is column-centered, so a tiny
   negative-leaning margin nudges it up close to the stage artwork
   without floating loose. */
.hero-icon-action--top {
  /* Spacing handled by .hero-stack gap. */
  margin: 0 auto;
  width: 44px;
  height: 44px;
}
.hero-icon-action--top svg { width: 28px; height: 28px; }

/* "Official site" link is rendered inline inside the disclaimer
   paragraph (see STRINGS["hero.disclaimer"]). It drops onto its own
   line via display: block — the prose above explains the unofficial
   status, the link below is the call-to-action to the real site. */
.hero-official-link {
  display: block;
  margin-top: 4px;
  color: var(--sun-yellow);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color 0.15s ease, opacity 0.15s ease;
}
@media (hover: hover) and (pointer: fine) {
  .hero-official-link:hover { color: var(--sun-pink); }
}

/* ===== Navigation chooser sheet ===== */
.nav-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(5, 2, 18, 0.85);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 20px 14px max(28px, env(safe-area-inset-bottom, 14px));
  animation: fade-in 0.2s ease-out both;
}
.nav-overlay[hidden] { display: none; }

.nav-sheet {
  width: min(440px, calc(100vw - 28px));
  border-radius: 22px;
  padding: 18px 18px 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  text-align: center;
  animation: glass-pop 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.2) both;
}

.nav-sheet-title {
  margin: 0;
  font-family: "Heebo", sans-serif;
  font-size: 16px;
  font-weight: 800;
  color: var(--text);
}

.nav-sheet-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.nav-sheet-action {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: var(--text);
  text-decoration: none;
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  text-align: start;
  transition: background 0.15s, border-color 0.15s, transform 0.15s;
}

@media (hover: hover) and (pointer: fine) {
  .nav-sheet-action:hover {
    background: rgba(254, 180, 71, 0.15);
    border-color: rgba(254, 180, 71, 0.4);
  }
}

.nav-sheet-action:active { transform: scale(0.98); }

.nav-sheet-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 999px;
  flex-shrink: 0;
}

#nav-waze .nav-sheet-icon  { background: rgba(0, 153, 255, 0.18); color: #00b3ff; }
#nav-gmaps .nav-sheet-icon { background: rgba(74, 222, 128, 0.18); color: #4ade80; }

.nav-sheet-cancel {
  background: transparent;
  border: 0;
  color: var(--text-muted);
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  padding: 10px;
  border-radius: 10px;
}

.nav-sheet-cancel:hover { background: rgba(255, 255, 255, 0.05); color: var(--text); }

/* ===== Festival map lightbox ===== */
.map-overlay {
  position: fixed;
  inset: 0;
  z-index: 55;
  background: rgba(5, 2, 18, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
  animation: fade-in 0.2s ease-out both;
}
.map-overlay[hidden] { display: none; }

.map-panel {
  position: relative;
  width: min(960px, 100%);
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

/* The viewport clips the transformed image so it can't wander outside
   its own rounded frame. touch-action: none disables the browser's
   built-in pinch/pan so our pointer handlers own the gestures.
   user-select: none stops a long-press from selecting the image. */
.map-viewport {
  position: relative;
  width: 100%;
  height: min(75vh, 720px);
  border-radius: 16px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.35);
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6);
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.map-viewport.is-panning { cursor: grabbing; }

.map-image {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transform-origin: 0 0;
  /* Default identity; JS overrides --map-tx / --map-ty / --map-scale
     / --map-rotation as the user pans / zooms / rotates. */
  transform: translate(var(--map-tx, 0px), var(--map-ty, 0px))
             scale(var(--map-scale, 1))
             rotate(var(--map-rotation, 0deg));
  transition: transform 0.18s cubic-bezier(0.2, 0.85, 0.35, 1.05);
  -webkit-user-drag: none;
  pointer-events: none;
}

.map-viewport.is-gesturing .map-image {
  /* Drop the smoothing while a gesture is active so motion tracks the
     user's fingers 1:1 — the transition would otherwise lag behind. */
  transition: none;
}

/* Zoom / rotate / reset controls. Sits under the viewport on a row so
   they're always reachable on phones (the iPhone safe-area bottom is
   already accounted for by the overlay padding). */
.map-controls {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px;
  border-radius: 999px;
  background: rgba(10, 5, 36, 0.85);
  border: 1px solid rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.map-control {
  width: 38px;
  height: 38px;
  border-radius: 999px;
  border: 0;
  background: transparent;
  color: var(--text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, transform 0.15s;
}
@media (hover: hover) and (pointer: fine) {
  .map-control:hover {
    background: rgba(254, 180, 71, 0.18);
    color: var(--sun-yellow);
  }
}
.map-control:active { transform: scale(0.93); }
.map-control:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  background: transparent;
  color: var(--text-muted);
}

.map-caption {
  font-size: 12px;
  color: var(--text-muted);
  letter-spacing: 0.4px;
}

.map-close {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  background: rgba(10, 5, 36, 0.85);
  color: var(--text);
  border: 1px solid rgba(255, 255, 255, 0.18);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 2;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.map-close:hover { background: rgba(255, 92, 138, 0.25); border-color: rgba(255, 92, 138, 0.5); }

/* ===== Landscape phones (short viewport, wide-ish screen) =====
   When users rotate their phone to landscape, height becomes the constraint.
   The portrait hero/artist layouts feel cramped — switch to compact spacing
   and a side-by-side artist hero layout that uses the extra horizontal room. */
@media (orientation: landscape) and (max-height: 520px) {
  .hero-panel {
    padding: 16px 22px 70px;
  }
  .hero-stack { gap: 6px; }
  .hero-stack > .live-status { margin-top: 4px; }
  .hero-stack > .hero-artists-pill { margin-top: 2px; }
  .hero-panel .logo-mark {
    font-size: clamp(34px, 11vh, 64px);
    line-height: 0.95;
  }
  .hero-tagline {
    font-size: 12px;
    line-height: 1.45;
    max-width: 560px;
  }
  .hero-meta {
    font-size: 11px;
    padding: 6px 14px;
    gap: 4px 12px;
  }
  .hero-hint { font-size: 10px; margin-top: 6px; }
  .hero-dots { bottom: 64px; padding: 14px 18px; }
  .section--multi-hero > .hero-hint { bottom: max(10px, env(safe-area-inset-bottom, 0)); font-size: 11px; }
  .hero-dots.is-armed { padding: 8px 12px; }
  .hero-stage-element { display: none; } /* save the GPU + room */

  /* Artist sections: tighter padding so dots and content don't fight. */
  .panel { padding: 60px 22px 70px; }

  /* Artist hero: editorial 2-column even at narrow widths because we now
     have horizontal room. Photo left, text right, English layout. */
  .panel-hero { padding: 50px 28px 70px; text-align: left; direction: ltr; }
  .panel-hero .panel-inner {
    display: grid;
    grid-template-columns: minmax(140px, 200px) 1fr;
    gap: 24px;
    align-items: center;
    text-align: left;
    direction: ltr;
    max-width: 720px;
  }
  .panel-hero .artist-hero-photo {
    width: 100%;
    max-width: 200px;
    margin: 0;
    grid-column: 1;
  }
  .panel-hero .artist-hero-art {
    width: 100%;
    max-width: 200px;
    aspect-ratio: 1 / 1;
    margin: 0;
    border-radius: 18px;
    grid-column: 1;
  }
  .panel-hero .artist-hero-text {
    grid-column: 2;
    text-align: left;
    align-items: flex-start;
    gap: 8px;
  }
  .panel-hero .artist-hero-text > * { text-align: left; }
  .panel-hero .artist-hero-text::before {
    width: 36px;
    height: 2px;
    margin-bottom: 2px;
  }
  .panel-hero .artist-name {
    font-size: clamp(26px, 7vh, 44px);
    /* Looser line-height + bottom padding so descenders on the last
       wrapped line (e.g. "g" in "Vlugt") aren't clipped by the
       background-clip:text gradient box. */
    line-height: 1.0;
    letter-spacing: -1px;
    margin: 0;
    padding-bottom: 0.12em;
  }
  .panel-hero .artist-meta-line { font-size: 12px; gap: 4px 12px; }
  .panel-hero .artist-meta-line .meta-item + .meta-item::before { margin: 0 8px; }

  /* Tracks/discography lists: tighter and 2-up where possible. */
  .tracks-stack { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .albums-list { grid-template-columns: repeat(2, 1fr); gap: 6px; }

  /* Mini-player thumb stays visible but doesn't dominate */
  .mini-player { padding: 6px 12px; }
  .mini-player-frame { width: 64px; height: 38px; }
  .mini-player-next { display: none; }

  /* Top bar tighter */
  .top-bar { top: 0; left: 0; right: 0; }
  .stage-select-trigger { padding: 8px 6px; font-size: 12px; max-width: 28vw; }
}

/* Very short landscape (e.g. iPhone Pro Max landscape ~414px high stays
   above 520, but small Androids can go lower). One more tier. */
@media (orientation: landscape) and (max-height: 380px) {
  .hero-panel .logo-mark { font-size: clamp(28px, 10vh, 48px); }
  .panel-hero .artist-name { font-size: clamp(22px, 6vh, 34px); padding-bottom: 0.12em; }
  .panel-hero .artist-hero-photo,
  .panel-hero .artist-hero-art { max-width: 150px; }
  .panel { padding: 48px 16px 60px; }
  .panel-hero { padding: 40px 20px 60px; }
}

/* ===== Hero stack =====
   One flex column inside every hero panel that holds the logo scene
   (or stage title), tagline, meta line, live-status / now-playing
   card with its countdown timer + progress bar, the avatar-stack
   "+N artists" pill, and the disclaimer — all driven by a single
   `gap` so per-element margin-top/margin-bottom rules above can stay
   zeroed out. The stage-element background art and the section-level
   .hero-dots / .hero-hint stay OUTSIDE this stack: the first is
   absolutely-positioned decor, the latter two are shared across all
   5 stage panels and live at section level. */
.hero-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, 2.2vh, 14px);
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  text-align: center;
  /* Override the pointer-events: auto inherited from the .hero-panel >
     :not(...) rule near the top of the file. Empty space inside the
     stack (the flex gaps between items) needs to pass clicks/swipes
     through to the .hero-pager so horizontal navigation isn't trapped
     in the gaps. Each interactive child re-enables pointer-events
     individually via the .hero-stack > :not(.hero-logo-scene) rule. */
  pointer-events: none;
}
.hero-stack > :not(.hero-logo-scene) {
  pointer-events: auto;
}
/* Slightly bigger breathing room before the now-playing block and the
   artists pill so they read as their own units within the stack
   rhythm — recreates the deliberate 18-26px gaps from the old
   .hero-stats-group wrapper without re-introducing the wrapper. */
.hero-stack > .live-status { margin-top: clamp(4px, 1vh, 10px); }
.hero-stack > .hero-artists-pill { margin-top: clamp(2px, 0.6vh, 6px); }

/* ===== Avatar-stack "+N artists" pill (on each hero panel) =====
   Three overlapping circular avatars followed by a total/+N counter.
   Tapping it opens the #artists-overlay scoped to the relevant pool
   (all artists on Main, the stage's artists on stage heroes). */
.hero-artists-pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  /* Spacing handled by .hero-stack gap (+ optional .hero-stack > .hero-artists-pill margin-top). */
  margin: 0 auto;
  padding: 6px 14px 6px 8px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.2px;
  color: var(--text);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  cursor: pointer;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
@media (hover: hover) and (pointer: fine) {
  .hero-artists-pill:hover {
    background: rgba(254, 180, 71, 0.14);
    border-color: rgba(254, 180, 71, 0.45);
  }
}
.hero-artists-pill:active { transform: scale(0.97); }

.hero-artists-stack {
  display: inline-flex;
  align-items: center;
  position: relative;
}
.hero-artists-stack > .hero-artists-avatar + .hero-artists-avatar {
  margin-inline-start: -10px;
}
.hero-artists-avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(20, 10, 50, 0.85);
  border: 2px solid rgba(10, 5, 36, 0.95);
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 800;
  color: var(--accent, var(--sun-yellow));
  z-index: var(--z, 1);
  position: relative;
}
.hero-artists-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.hero-artists-count {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Hebrew: read RTL so "+N" sits to the right of the word — Hebrew readers
   scan right→left and expect the number first, then "אמנים". Without
   this, the BiDi algorithm in the LTR body parks the LTR "+N" run on the
   left and you get "אמנים +N" visually = "+N אמנים" read RTL, which feels
   inverted to a native reader. */
:lang(he) .hero-artists-count { direction: rtl; }

/* Hebrew typography pass for the artists pill: 700 + 0.2px tracking reads
   as a heavy button label on Hebrew. Match the .hero-tagline body voice —
   weight 500, no tracking — so "7+ אומנים" feels like the surrounding hero
   copy rather than a separate bold chip. */
:lang(he) .hero-artists-pill,
:lang(he) .hero-artists-count {
  font-weight: 500;
  letter-spacing: 0;
}

/* ===== In-place pill flyout =====
   Tapping the avatars pill morphs the hero stack: everything else fades
   out and the flyout scales up at viewport center. Visually it's the
   same chrome as the pill itself — quiet glass, hairline border,
   gentle blur, no heavy shadow — just at a larger size. */
.hero-artists-flyout {
  /* Fixed to the viewport so vertical centering is unambiguous (no
     reliance on the hero panel's intrinsic padding/flex centering).
     openPillFlyout portals the flyout to <body> on open so this
     `position: fixed` resolves against the actual viewport rather
     than .hero-pager's transformed coordinate system — that's also
     what lets `backdrop-filter` blur the page gradient cleanly here
     (it would no-op inside a transformed ancestor). */
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) scale(0.92);
  transform-origin: 50% 50%;
  width: min(380px, calc(100% - 32px));
  max-height: min(58vh, 460px);
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 10px 12px;
  /* Clean glass — quiet white wash, hairline border, gentle blur, no
     heavy shadow. Reads as the same chrome as the pill itself,
     just at a larger size. */
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 22px;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity 0.22s ease,
    transform 0.28s cubic-bezier(0.2, 0.85, 0.3, 1.06),
    visibility 0s linear 0.28s;
}

/* Hide every section-level decoration while the flyout is open. The
   UFOs (z-index 2), .hero-dots (z-index 12) and .hero-hint (z-index
   11) live in `.section--multi-hero`'s stacking context, OUTSIDE
   .hero-pager's transform-induced context that holds the flyout, so
   they would paint over the flyout regardless of its own z-index.
   :has() scopes the fade to "the section currently holding the open
   flyout". Same 0.22 s timing as the in-stack fade for continuity. */
.section--multi-hero:has(.hero-stack.pill-expanded) .hero-section-ufo,
.section--multi-hero:has(.hero-stack.pill-expanded) .hero-dots,
.section--multi-hero:has(.hero-stack.pill-expanded) .hero-hint,
.section--multi-hero:has(.hero-stack.pill-expanded) .hero-stage-element {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
}
/* Flyout visibility is keyed off the aria-hidden attribute that
   openPillFlyout / closePillFlyouts toggle. We can't rely on
   `.hero-stack.pill-expanded .hero-artists-flyout` because the JS
   portals the flyout to <body> when opening (so position: fixed
   resolves against the viewport rather than the hero-pager's
   transformed coordinate system) — at that point the flyout no
   longer has .hero-stack as an ancestor. */
.hero-artists-flyout[aria-hidden="false"] {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, -50%) scale(1);
  pointer-events: auto;
  transition:
    opacity 0.22s ease,
    transform 0.28s cubic-bezier(0.2, 0.85, 0.3, 1.06),
    visibility 0s linear 0s;
}

/* Fade every sibling in the stack — including the pill — while the
   flyout is open. */
.hero-stack.pill-expanded > *:not(.hero-artists-flyout) {
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.hero-stack > *:not(.hero-artists-flyout) {
  transition: opacity 0.22s ease, transform 0.22s ease;
}

.hero-artists-flyout-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px 0;
}
.hero-artists-flyout-title {
  flex: 1 1 auto;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  letter-spacing: 0.2px;
  text-align: start;
}
:lang(he) .hero-artists-flyout-title { font-weight: 500; letter-spacing: 0; }

/* Hebrew: flip the flyout to RTL. `body { direction: ltr }` near the
   top of this file traps every descendant in LTR even when
   <html dir="rtl">; the override below scopes RTL exactly to the
   flyout so the title sits flush-right and each row's avatar lands on
   the right with the artist name/meta flowing from the right edge.
   Each child uses `text-align: start` already, so RTL re-resolves
   start = right automatically. */
:lang(he) .hero-artists-flyout {
  direction: rtl;
}

.hero-artists-flyout-list {
  flex: 1 1 auto;
  overflow-y: auto;
  display: grid;
  gap: 4px;
  padding: 2px 4px 4px;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
  /* Fade the bottom edge so the last row dissolves into the panel. */
  mask-image: linear-gradient(180deg, black 0, black calc(100% - 16px), transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, black 0, black calc(100% - 16px), transparent 100%);
}
.hero-artists-flyout-list::-webkit-scrollbar { width: 4px; }
.hero-artists-flyout-list::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.16);
  border-radius: 999px;
}

/* Lighter row treatment inside the flyout — drop the artists-overlay's
   chip-card look (filled bg, ring border) and let each row read as a
   plain glass list item, matching the surrounding pill aesthetic. */
.hero-artists-flyout-list .artists-row {
  background: transparent;
  border: 1px solid transparent;
  padding: 6px 8px;
  border-radius: 12px;
}
@media (hover: hover) and (pointer: fine) {
  .hero-artists-flyout-list .artists-row:hover {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.1);
  }
}
.hero-artists-flyout-list .artists-row-avatar { width: 32px; height: 32px; }
.hero-artists-flyout-list .artists-row-name { font-weight: 600; font-size: 13px; }
.hero-artists-flyout-list .artists-row-meta { font-size: 10px; }

/* ===== Artist-list overlay =====
   Same visual language as the favorites overlay (glass panel, big
   typography, full-screen on mobile). Lists every artist scoped to the
   hero panel that opened it; tapping a row jumps to that artist's
   section. Header carries a "Random" button as the primary action. */
.artists-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(5, 2, 18, 0.78);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 28px 14px;
  animation: fade-in 0.18s ease-out both;
}
.artists-overlay[hidden] { display: none; }

.artists-panel {
  width: min(560px, 100%);
  max-height: min(82vh, 720px);
  border-radius: 22px;
  padding: 14px 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.artists-header {
  display: flex;
  align-items: center;
  gap: 8px;
}
.artists-title {
  flex: 1 1 auto;
  font-size: 16px;
  font-weight: 800;
  letter-spacing: 0.3px;
  color: var(--text);
  margin: 0;
}
.artists-random {
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
}
.artists-random svg {
  display: block;
  pointer-events: none;
}
.artists-random:active { transform: scale(0.96); }
@media (hover: hover) and (pointer: fine) {
  .artists-random:hover {
    background: rgba(255, 92, 138, 0.12);
    border-color: rgba(255, 92, 138, 0.35);
  }
}
.artists-close {
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.06);
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
@media (hover: hover) and (pointer: fine) {
  .artists-close:hover {
    background: rgba(255, 92, 138, 0.18);
    border-color: rgba(255, 92, 138, 0.5);
    color: var(--sun-pink);
  }
}

.artists-list {
  flex: 1 1 auto;
  overflow-y: auto;
  display: grid;
  gap: 6px;
  padding: 2px 4px 4px 0;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
}
.artists-list::-webkit-scrollbar { width: 6px; }
.artists-list::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.22);
  border-radius: 999px;
}

.artists-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.07);
  color: var(--text);
  cursor: pointer;
  text-align: start;
  font-family: inherit;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.12s ease;
  width: 100%;
}
@media (hover: hover) and (pointer: fine) {
  .artists-row:hover {
    background: rgba(254, 180, 71, 0.14);
    border-color: rgba(254, 180, 71, 0.4);
  }
}
.artists-row:active { transform: scale(0.99); }

.artists-row-avatar {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  overflow: hidden;
  background: rgba(20, 10, 50, 0.7);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 800;
  color: var(--accent, var(--sun-yellow));
}
.artists-row-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.artists-row-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1 1 auto;
}
.artists-row-name {
  font-weight: 700;
  font-size: 14px;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.artists-row-meta {
  font-size: 11px;
  color: var(--text-muted);
}

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

/* Hint with arrow stacked BELOW the text (one column, centered). */
.hero-hint {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  text-align: center;
}
.hero-hint-arrow {
  font-size: 16px;
  line-height: 1;
  opacity: 0.9;
  animation: hero-hint-arrow-bob 1.6s ease-in-out infinite;
}
@keyframes hero-hint-arrow-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(3px); }
}

@media (min-width: 1024px) {
  .panel--info .panel-inner {
    max-width: 720px;
  }
}

/* ---------- Main hero: better composition on tablet/desktop ----------
   Logo + UFO scene is the dominant visual; shrinking it on wider
   viewports prevents the stack from overflowing into .hero-dots /
   .hero-disclaimer and keeps the composition deliberate instead of
   dominating. The pre-existing .hero-zna-mark rules pointed at a
   class that JS doesn't emit (the live element is .hero-logo-scene),
   so they were dead. Re-anchor the sizing on the real selector. */
@media (min-width: 768px) {
  /* Reserve the SAME flex-content margin on top (for the fixed top-bar)
     and on bottom (for the absolutely-positioned .hero-dots + dive-hint
     strip) so the hero-stack, which is justify-content: center inside
     the content box, sits visually equidistant from the top chrome and
     the bottom nav dots. Numbers:
       · top-bar height ≈ 58 px → padding-top: 58
       · .hero-dots { bottom: 70 } + ~60 px strip height = 130 px → padding-bottom: 130
     Verified at 1024 / 1366 / 1600 → top gap ≈ bottom gap on every stage
     panel (was offset by ~84 px high). */
  .hero-panel { padding-top: 58px; padding-bottom: 130px; }
  .hero-logo-scene { width: min(44vw, 340px); }
  .hero-panel--main .logo-mark {
    font-size: clamp(96px, 14vw, 180px);
    letter-spacing: -5px;
  }
  .hero-panel--main .hero-tagline {
    font-size: 17px !important;
    max-width: 580px !important;
    line-height: 1.65 !important;
  }
  .hero-panel--main .hero-meta { font-size: 13.5px; }
  .hero-panel--main .live-status { width: min(460px, calc(100% - 36px)); }
  /* Roomier breathing on bigger viewports — the stack stays compact on
     mobile (clamped 10-14px), and opens up here so the main panel reads
     as a deliberate composition rather than a tight stack. */
  .hero-stack { gap: clamp(14px, 1.8vh, 20px); }
  .hero-stack > .live-status { margin-top: clamp(8px, 1.2vh, 14px); }
  .hero-stack > .hero-artists-pill { margin-top: clamp(4px, 0.6vh, 8px); }
}

@media (min-width: 1024px) {
  .hero-panel--main {
    /* Match the unified 58 / 130 vertical inset from the .hero-panel
       desktop rule so the main hero's stack centers visually between
       the top-bar and the hero-dots strip on the same axis as every
       stage panel. Horizontal padding stays generous (40 px) to mirror
       the main hero's wider headline composition. */
    padding: 58px 40px 130px;
  }
  /* Desktop sizing: prior values had the logo+UFOs visually dominating
     the panel and pushing .hero-disclaimer into the .hero-dots strip.
     This keeps the scene around ~28-32% of the panel width so the stack
     below it (tagline, meta, live-status, artists pill, disclaimer) all
     fit comfortably between the panel top and the .hero-dots row. */
  .hero-logo-scene { width: min(26vw, 300px); }
  .hero-panel--main .logo-mark {
    font-size: clamp(80px, 9vw, 140px);
    line-height: 0.92;
  }
  /* Final tier of breathing room — the desktop main hero has lots of
     vertical real-estate, so the stack opens up another notch. */
  .hero-stack { gap: clamp(16px, 2vh, 22px); }
  /* Stage panels: bigger than mobile but capped well below the previous
     160px ceiling — the title was eating the live-status row. */
  .hero-panel--zambu .logo-mark,
  .hero-panel--peninsula .logo-mark,
  .hero-panel--market .logo-mark {
    font-size: clamp(56px, 6.5vw, 110px);
  }
  .hero-panel .hero-tagline {
    font-size: 18px !important;
    max-width: 640px !important;
  }
}

@media (min-width: 1400px) {
  /* On very wide laptops / external monitors, cap the logo scene so it
     doesn't grow with the viewport — large monitors get more whitespace,
     not a bigger scene. */
  .hero-logo-scene { width: min(22vw, 320px); }
}

/* Prevent the giant ZNA 2026 wordmark from spilling out at any width.
   `letter-spacing: normal` falls back when the negative tracking would
   pull the last glyph past the panel edge on narrow desktops. */
.hero-panel--main .logo-mark {
  max-width: 100%;
  overflow-wrap: break-word;
}

/* ---------- Vertical dots: desktop hover hit-zone tweaks ----------
   Magnification is now handled entirely via the .is-hovered/.is-armed
   transform: scale rules above (no width/height changes), which fixes
   the cursor-flicker the previous height-based :hover rules caused. */
@media (hover: hover) and (pointer: fine) {
  /* Wider hit-zone so users don't need pixel-perfect aim */
  .vertical-progress {
    padding: 8px 24px 8px 4px;
    margin-inline-start: -4px;
  }
  .v-dot {
    position: relative;
  }
  .v-dot:focus-visible {
    background: var(--sun-yellow);
    box-shadow: 0 0 14px var(--sun-yellow);
  }
}

/* ===== Keyboard arrow-keys hint (desktop only) =====
   Tiny cluster pinned to the bottom-right that mimics the arrow-keys
   block of a keyboard. Hidden on touch devices, always visible on
   desktop. When the user presses an arrow key, the matching <kbd>
   briefly lights up white via .is-pressed (toggled in app.js). */
.kb-hint {
  position: fixed;
  bottom: max(14px, env(safe-area-inset-bottom, 0));
  right: 16px;
  z-index: 18;
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  pointer-events: none;
  opacity: 0.7;
  user-select: none;
}
@media (hover: hover) and (pointer: fine) and (min-width: 720px) {
  .kb-hint { display: flex; }
}
body.has-mini-player .kb-hint {
  bottom: calc(70px + env(safe-area-inset-bottom, 0px) + 14px);
}
.kb-hint-keys {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}
.kb-hint-row {
  display: flex;
  gap: 3px;
  justify-content: center;
}
.kb-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  font-family: "Heebo", system-ui, sans-serif;
  font-size: 12px;
  line-height: 1;
  color: rgba(255, 255, 255, 0.85);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-bottom-width: 2px;
  border-radius: 5px;
  font-weight: 700;
  transition: color 0.08s ease, background 0.08s ease, border-color 0.08s ease, transform 0.08s ease;
}
/* "I just got pressed" flash — the key glyph flips to a solid white
   tile so the user sees a visual ack on every arrow keypress. */
.kb-key.is-pressed {
  color: #0a0524;
  background: #fff;
  border-color: #fff;
  transform: translateY(1px);
}
.kb-hint-label {
  font-size: 10px;
  letter-spacing: 0.4px;
  color: rgba(255, 255, 255, 0.55);
  text-transform: uppercase;
}

/* Floating tooltip for vertical dots (desktop only). Positioned via JS
   next to the hovered dot, fixed to the viewport so the
   .vertical-progress container's overflow can't clip it. */
.v-dot-tooltip {
  position: fixed;
  background: rgba(10, 5, 36, 0.95);
  color: var(--text);
  padding: 6px 12px;
  border-radius: 8px;
  border: 1px solid var(--card-border);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.3px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  z-index: 100;
  transform: translateY(-50%);
}
.v-dot-tooltip.is-visible { opacity: 1; }
@media (hover: none), (pointer: coarse) {
  .v-dot-tooltip { display: none !important; }
}

/* ===== Timetable overlay =====
   Full official schedule: day tabs on top, venue filter chips, then the
   day's activities. Follows the artists-overlay visual language. */
.schedule-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(5, 2, 18, 0.78);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 28px 14px;
  animation: fade-in 0.18s ease-out both;
}
.schedule-overlay[hidden] { display: none; }

.schedule-panel {
  width: min(640px, 100%);
  max-height: min(88vh, 820px);
  border-radius: 22px;
  padding: 14px 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.schedule-header {
  display: flex;
  align-items: center;
  gap: 8px;
}
.schedule-title {
  flex: 1 1 auto;
  font-size: 16px;
  font-weight: 800;
  letter-spacing: 0.3px;
  color: var(--text);
  margin: 0;
}
.schedule-close {
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.06);
  color: var(--text);
  cursor: pointer;
}

/* Day tabs — one row, horizontally scrollable on narrow screens. */
.schedule-days {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  padding-bottom: 2px;
  flex-shrink: 0;
}
.schedule-days::-webkit-scrollbar { display: none; }
.schedule-day {
  flex: 1 0 auto;
  min-width: 52px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 7px 8px 6px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-muted);
  cursor: pointer;
  font-family: "Heebo", sans-serif;
}
.schedule-day-wd { font-size: 10px; letter-spacing: 0.4px; }
.schedule-day-num { font-size: 16px; font-weight: 800; line-height: 1.1; color: var(--text); }
.schedule-day-today {
  font-size: 8px;
  font-weight: 700;
  color: #39ff14;
}
.schedule-day.active {
  background: rgba(254, 180, 71, 0.14);
  border-color: rgba(254, 180, 71, 0.55);
  color: var(--text);
}

/* Venue chips */
.schedule-venues {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  flex-shrink: 0;
}
.schedule-venues::-webkit-scrollbar { display: none; }
.schedule-venue-chip {
  flex: 0 0 auto;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.schedule-venue-chip.active {
  background: rgba(254, 180, 71, 0.14);
  border-color: rgba(254, 180, 71, 0.55);
  color: var(--text);
}

/* Activity rows */
.schedule-list {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 2px;
}
.schedule-empty {
  padding: 32px 12px;
  text-align: center;
  color: var(--text-muted);
  font-size: 13px;
}
.schedule-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  text-align: start;
  padding: 9px 12px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.07);
  background: rgba(255, 255, 255, 0.03);
  color: var(--text);
  font-family: "Heebo", sans-serif;
  cursor: default;
}
button.schedule-row { cursor: pointer; }
button.schedule-row:active { background: rgba(255, 255, 255, 0.07); }
.schedule-row.is-past { opacity: 0.45; }
.schedule-row.is-live {
  border-color: rgba(57, 255, 20, 0.45);
  background: rgba(57, 255, 20, 0.06);
}
.schedule-row.is-next {
  border-color: rgba(254, 180, 71, 0.4);
}
.schedule-row.cat-class,
.schedule-row.cat-workshop {
  border-style: dashed;
}
.schedule-row-time {
  flex: 0 0 auto;
  min-width: 44px;
  font-size: 13px;
  font-weight: 800;
  line-height: 1.35;
  color: var(--accent, var(--sun-yellow));
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.schedule-row-time-end {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-muted);
}
.schedule-row-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.schedule-row-name {
  font-size: 14px;
  font-weight: 700;
  line-height: 1.3;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.schedule-row-meta {
  font-size: 11px;
  color: var(--text-muted);
  line-height: 1.4;
}
.schedule-row-desc {
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.5;
  display: none;
  margin-top: 4px;
}
.schedule-row.is-expanded .schedule-row-desc { display: block; }
.schedule-row-live {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: #39ff14;
}
.schedule-row-live .live-now-dot {
  position: static;
  transform: none;
  width: 6px;
  height: 6px;
}
.schedule-row-next {
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--sun-yellow);
}
.schedule-row-go {
  flex: 0 0 auto;
  align-self: center;
  font-size: 20px;
  color: var(--text-muted);
}
[dir="rtl"] .schedule-row-go { transform: scaleX(-1); }

/* Artist card: one row per scheduled set (multi-venue artists get 2-3). */
.artist-set-times {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
}
.artist-set-times .artist-set-time {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.set-time-venue {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.4px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.14);
  white-space: nowrap;
}

/* Zambu programme-block chip on the artist meta line. */
.meta-item--section {
  border-color: rgba(254, 180, 71, 0.35);
  color: var(--sun-yellow);
}

/* Desktop: give the timetable a little more room. */
@media (min-width: 900px) {
  .schedule-panel { width: min(720px, 100%); }
  .schedule-row-name { font-size: 15px; }
}
