/* ============================================================================
   PsyShift — Component library
   Everything here is a self-contained, reusable block. Each one maps 1:1 to a
   future React component, so no component reaches outside itself for layout.
   ========================================================================== */

/* ─────────────────────────────────────────────────────────────────────────
   Buttons
   ───────────────────────────────────────────────────────────────────────── */
.btn {
  --btn-h: 42px;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  height: var(--btn-h);
  padding-inline: var(--s-5);
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: .005em;
  white-space: nowrap;
  border-radius: var(--r-full);
  transition: transform var(--dur-1) var(--ease),
              background var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease);
}
.btn svg { width: 17px; height: 17px; flex-shrink: 0; }
.btn:active { transform: scale(.97); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .45; pointer-events: none; }

.btn--primary {
  color: #fff;
  background: linear-gradient(135deg, var(--c-accent) 0%, var(--c-accent-dim) 100%);
  box-shadow: 0 6px 20px -8px rgba(124,92,255,.9), inset 0 1px 0 rgba(255,255,255,.22);
}
.btn--primary:hover {
  background: linear-gradient(135deg, var(--c-accent-bright) 0%, var(--c-accent) 100%);
  box-shadow: 0 10px 30px -8px rgba(124,92,255,1), inset 0 1px 0 rgba(255,255,255,.28);
}

.btn--second {
  color: #04241E;
  background: linear-gradient(135deg, var(--c-second) 0%, var(--c-second-dim) 100%);
  box-shadow: 0 6px 20px -8px rgba(46,211,183,.7), inset 0 1px 0 rgba(255,255,255,.3);
}
.btn--second:hover { filter: brightness(1.08); }

.btn--outline {
  color: var(--c-text);
  background: rgba(255,255,255,.03);
  border: 1px solid var(--c-line-strong);
}
.btn--outline:hover { background: rgba(255,255,255,.07); border-color: var(--c-line-accent); }

/* Destructive action. Magenta is the product's rare urgency colour, so a
   danger button is loud on purpose and must stay rare. */
.btn--danger {
  color: #fff;
  background: linear-gradient(135deg, var(--c-hot) 0%, #C7265F 100%);
  box-shadow: 0 6px 20px -8px rgba(255,77,143,.8), inset 0 1px 0 rgba(255,255,255,.25);
}
.btn--danger:hover { filter: brightness(1.08); }

.btn--ghost { color: var(--c-text-muted); }
.btn--ghost:hover { color: var(--c-text); background: rgba(255,255,255,.06); }

.btn--icon { width: var(--btn-h); padding: 0; border-radius: var(--r-full); }
.btn--sm   { --btn-h: 36px; padding-inline: var(--s-4); font-size: var(--t-xs); }
.btn--lg   { --btn-h: 52px; padding-inline: var(--s-8); font-size: var(--t-body); }
.btn--block { width: 100%; }

/* Saved / active toggle state on "Add to my trips". */
.btn.is-saved {
  color: var(--c-second);
  background: var(--c-second-wash);
  border-color: rgba(46,211,183,.4);
  box-shadow: none;
}

/* ─────────────────────────────────────────────────────────────────────────
   Chips & badges
   ───────────────────────────────────────────────────────────────────────── */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  font-size: var(--t-2xs);
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--c-text-soft);
  background: rgba(255,255,255,.055);
  border: 1px solid var(--c-line);
  border-radius: var(--r-full);
  white-space: nowrap;
}
.chip svg { width: 12px; height: 12px; opacity: .8; }
.chip--accent { color: var(--c-accent-bright); background: var(--c-accent-wash); border-color: var(--c-line-accent); }
.chip--teal   { color: var(--c-second);       background: var(--c-second-wash); border-color: rgba(46,211,183,.3); }
.chip--hot    { color: var(--c-hot);          background: var(--c-hot-wash);    border-color: rgba(255,77,143,.32); }
.chip--warn   { color: var(--c-warn);         background: var(--c-warn-wash);   border-color: rgba(255,180,77,.3); }
/* Dashed and muted: a state that is waiting on someone else, not an alert. */
.chip--tba    { color: var(--c-text-muted);   background: rgba(255,255,255,.04); border-style: dashed; }

/* A live dot for "happening soon" signals. */
.dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 0 currentColor;
  animation: pulse-dot 2.4s var(--ease-out) infinite;
}
@keyframes pulse-dot {
  0%   { box-shadow: 0 0 0 0 rgba(255,77,143,.55); }
  70%  { box-shadow: 0 0 0 7px rgba(255,77,143,0); }
  100% { box-shadow: 0 0 0 0 rgba(255,77,143,0); }
}

/* ─────────────────────────────────────────────────────────────────────────
   Surfaces
   ───────────────────────────────────────────────────────────────────────── */
.panel {
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  box-shadow: var(--e-2);
}
.panel--glass {
  background: var(--c-surface-glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}
.panel--pad { padding: var(--s-5); }

@media (min-width: 768px) { .panel--pad { padding: var(--s-6); } }

/* ─────────────────────────────────────────────────────────────────────────
   Avatar
   ───────────────────────────────────────────────────────────────────────── */
.avatar {
  display: grid;
  place-items: center;
  width: 38px; height: 38px;
  flex-shrink: 0;
  font-family: var(--font-display);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--c-text);
  background: linear-gradient(140deg, var(--c-accent-dim), var(--c-second-dim));
  border-radius: var(--r-full);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.22);
}
/* Forum members carry two hues like a festival poster, so a list of people
   is as recognisable at a glance as a list of festivals. */
.avatar--hue { background: linear-gradient(140deg, hsl(var(--h1) 52% 34%), hsl(var(--h2) 54% 30%)); }

.avatar--sm { width: 32px; height: 32px; font-size: var(--t-2xs); }
.avatar--lg { width: 56px; height: 56px; font-size: var(--t-body); }

/* ─────────────────────────────────────────────────────────────────────────
   Role badge — the only thing that distinguishes forum members from each
   other besides what they write. Deliberately quiet: a wall of coloured
   badges turns a conversation into a hierarchy chart.
   ───────────────────────────────────────────────────────────────────────── */
.rolebadge {
  display: inline-block;
  padding: 2px 8px;
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-full);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--c-text-muted);
}
.rolebadge--team { color: var(--c-accent-bright); border-color: var(--c-line-accent); background: var(--c-accent-wash); }
.rolebadge--mod  { color: var(--c-second);        border-color: rgba(46,211,183,.3); background: var(--c-second-wash); }

/* ─────────────────────────────────────────────────────────────────────────
   Generative festival artwork
   ------------------------------------------------------------------------
   No bitmaps anywhere in this product. Each festival carries two hues and a
   pattern key; those drive a layered mesh gradient plus a signature motif.
   The result is a distinct, reproducible "poster" per festival that costs
   nothing to load and can never 404.
   ───────────────────────────────────────────────────────────────────────── */
.poster {
  /* Every wrapper positions the poster to fill itself, so that is the
     component's own default. It previously relied on a per-wrapper rule,
     which silently collapsed the artwork to zero height anywhere a new
     container was introduced. The wrapper only needs position:relative. */
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* Deliberately low-chroma and dark. Earlier iterations used near-full
     saturation, which turned a grid of festivals into a bag of sweets — the
     exact psychedelic cliche this product avoids. Hue still identifies each
     festival; it just does so tonally instead of shouting. */
  background:
    radial-gradient(90% 120% at 18% 8%,  hsl(var(--h1) 56% 40% / .88) 0%, transparent 58%),
    radial-gradient(85% 110% at 88% 22%, hsl(var(--h2) 52% 31% / .80) 0%, transparent 60%),
    radial-gradient(120% 90% at 50% 110%, hsl(var(--h1) 42% 11% / .95) 0%, transparent 70%),
    linear-gradient(165deg, hsl(var(--h1) 38% 13%), hsl(var(--h2) 44% 7%));
  isolation: isolate;
}

/* Motif layer. */
.poster::before {
  content: "";
  position: absolute;
  inset: -10%;
  opacity: .32;
  mix-blend-mode: soft-light;
}

/* Bottom scrim so overlaid text always clears WCAG contrast. The faint blue
   cast is what pulls twenty different hues into one family. */
.poster::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg,
      rgba(10,12,24,.10) 0%,
      rgba(9,11,21,.40) 46%,
      rgba(6,7,14,.90) 100%),
    linear-gradient(150deg, rgba(124,92,255,.10), rgba(46,211,183,.06));
}

/* — Motif: concentric rings (goa / psychedelic geometry, restrained) — */
.poster[data-art="rings"]::before {
  background: repeating-radial-gradient(circle at 72% 26%,
    transparent 0 13px, rgba(255,255,255,.5) 13px 14px);
}
/* — Motif: sound waves — */
.poster[data-art="waves"]::before {
  background: repeating-linear-gradient(105deg,
    transparent 0 11px, rgba(255,255,255,.42) 11px 12px);
  filter: url(#wobble);
}
/* — Motif: topographic contour (travel / terrain) — */
.poster[data-art="topo"]::before {
  background:
    repeating-radial-gradient(ellipse 70% 42% at 30% 78%,
      transparent 0 15px, rgba(255,255,255,.4) 15px 16px);
}
/* — Motif: technical grid (techno) — */
.poster[data-art="grid"]::before {
  background:
    linear-gradient(rgba(255,255,255,.34) 1px, transparent 1px) 0 0 / 100% 26px,
    linear-gradient(90deg, rgba(255,255,255,.34) 1px, transparent 1px) 0 0 / 26px 100%;
  transform: perspective(420px) rotateX(52deg) scale(1.5);
  transform-origin: 50% 100%;
}
/* — Motif: particle field (ambient / downtempo) — */
.poster[data-art="dust"]::before {
  background:
    radial-gradient(rgba(255,255,255,.75) 1.2px, transparent 1.4px) 0 0 / 22px 22px,
    radial-gradient(rgba(255,255,255,.45) 1px, transparent 1.2px) 11px 11px / 34px 34px;
  opacity: .38;
}
/* — Motif: prism beams (main stage lights) — */
.poster[data-art="beams"]::before {
  background: conic-gradient(from 200deg at 50% 118%,
    transparent 0deg,     rgba(255,255,255,.5) 8deg,  transparent 16deg,
    transparent 26deg,    rgba(255,255,255,.36) 34deg, transparent 42deg,
    transparent 54deg,    rgba(255,255,255,.5) 62deg, transparent 70deg);
}

/* ─────────────────────────────────────────────────────────────────────────
   Festival card (grid variant)
   ───────────────────────────────────────────────────────────────────────── */
.fcard {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--e-2);
  transition: transform var(--dur-3) var(--ease-out),
              border-color var(--dur-2) var(--ease),
              box-shadow var(--dur-3) var(--ease-out);
}
@media (hover: hover) {
  .fcard:hover {
    transform: translateY(-5px);
    border-color: var(--c-line-accent);
    box-shadow: var(--e-4), 0 0 0 1px var(--c-line-accent);
  }
  .fcard:hover .fcard__art .poster { transform: scale(1.06); }
  .fcard:hover .fcard__cta { gap: 10px; color: var(--c-accent-bright); }
}

.fcard__art {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
}
.fcard__art .poster {
  position: absolute;
  inset: 0;
  transition: transform var(--dur-4) var(--ease-out);
}

/* Top-left month tag + top-right save button live above the artwork. */
.fcard__tags {
  position: absolute;
  z-index: 3;
  inset: var(--s-3) var(--s-3) auto;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-2);
  pointer-events: none;
}
.fcard__tags > * { pointer-events: auto; }

.datetag {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 46px;
  padding: 5px 8px 6px;
  background: rgba(8,10,17,.66);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255,255,255,.16);
  border-radius: var(--r-sm);
  line-height: 1;
}
.datetag strong {
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 700;
  color: #fff;
}
/* No start date to show: the tag keeps the slot rather than disappearing,
   so the cards in a row still line up. */
.datetag--tba { border-style: dashed; border-color: rgba(255,180,77,.45); }
.datetag--tba strong { color: var(--c-warn); }
.datetag--tba span { color: rgba(255,180,77,.78); }

.datetag span {
  margin-top: 3px;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: rgba(255,255,255,.72);
}

.savebtn {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  color: #fff;
  background: rgba(8,10,17,.6);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255,255,255,.16);
  border-radius: var(--r-full);
  transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease),
              transform var(--dur-2) var(--ease-out);
}
.savebtn svg { width: 17px; height: 17px; }
.savebtn:hover { background: rgba(124,92,255,.85); transform: scale(1.1); }
.savebtn.is-saved { color: var(--c-second); border-color: rgba(46,211,183,.5); }
.savebtn.is-saved svg { animation: pop var(--dur-3) var(--ease-out); }

@keyframes pop { 0% { transform: scale(.5); } 60% { transform: scale(1.25); } 100% { transform: scale(1); } }

/* Festival name sits on the artwork — big, confident, legible on the scrim. */
.fcard__title {
  position: absolute;
  z-index: 3;
  inset: auto var(--s-4) var(--s-4);
}
.fcard__title h3 {
  font-size: 1.28rem;
  font-weight: 700;
  color: #fff;
  letter-spacing: var(--track-display);
  text-shadow: 0 2px 18px rgba(0,0,0,.6);
}
.fcard__place {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 3px;
  font-size: var(--t-xs);
  color: rgba(255,255,255,.82);
}
.fcard__place svg { width: 13px; height: 13px; }

.fcard__body {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  flex: 1;
  padding: var(--s-4);
}

.fcard__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

.fcard__line {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-xs);
  color: var(--c-text-muted);
}
.fcard__line svg { width: 14px; height: 14px; color: var(--c-text-faint); flex-shrink: 0; }

/* Lineup preview — first three names, then a count. */
.lineup-peek {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  font-size: var(--t-xs);
  color: var(--c-text-soft);
}
.lineup-peek span {
  padding: 2px 8px;
  background: rgba(255,255,255,.045);
  border-radius: var(--r-xs);
}
.lineup-peek .more { color: var(--c-text-faint); background: none; padding-inline: 2px; }

.fcard__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-top: auto;
  padding-top: var(--s-3);
  border-top: 1px solid var(--c-line);
}
.price { display: flex; flex-direction: column; line-height: 1.15; }
.price strong {
  font-family: var(--font-display);
  font-size: 1.08rem;
  font-weight: 700;
}
.price span { font-size: var(--t-2xs); color: var(--c-text-faint); }

.fcard__cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--c-text-soft);
  transition: gap var(--dur-2) var(--ease), color var(--dur-2) var(--ease);
}
.fcard__cta svg { width: 15px; height: 15px; }

/* Whole card is a link target; the anchor covers it but sits below buttons. */
.fcard__link { position: absolute; inset: 0; z-index: 2; }

/* ── Festival card, list variant ──────────────────────────────────────── */
.fcard--list { flex-direction: row; }
.fcard--list .fcard__art {
  aspect-ratio: auto;
  width: 190px;
  flex-shrink: 0;
}
.fcard--list .fcard__title { display: none; }
.fcard--list .fcard__body { padding: var(--s-4) var(--s-5); }
.fcard--list .fcard__heading h3 { font-size: 1.15rem; }
.fcard--list .fcard__foot { border-top: none; padding-top: 0; }

@media (max-width: 639px) {
  .fcard--list { flex-direction: column; }
  .fcard--list .fcard__art { width: 100%; aspect-ratio: 16 / 7; }
}

/* ── Festival on hold — edition over, next dates unannounced ────────────
   Greyed, not hidden: the festival still exists and its page is still worth
   reading. The tape carries the state at a glance; the chip, the date line
   and the CTA repeat it in words, so nothing depends on colour alone.     */
.fcard.is-onhold { border-style: dashed; }
.fcard.is-onhold .fcard__art .poster { filter: grayscale(.85) brightness(1.18) contrast(.88); }
.fcard.is-onhold .fcard__art::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: rgba(10,12,19,.22);
}
.fcard.is-onhold .fcard__title h3 { color: var(--c-text-soft); text-shadow: none; }
.fcard.is-onhold .fcard__place { color: var(--c-text-muted); }
.fcard.is-onhold .fcard__heading h3 { color: var(--c-text-soft); }
.fcard.is-onhold .lineup-peek { color: var(--c-text-faint); }
.fcard.is-onhold .price strong,
.fcard.is-onhold .fcard__cta { color: var(--c-text-muted); }

/* Still a link, so it still answers to hover — just without the lift and the
   poster push that say "this one is coming up".                            */
@media (hover: hover) {
  .fcard.is-onhold:hover {
    transform: none;
    border-color: var(--c-line-strong);
    box-shadow: var(--e-2);
  }
  .fcard.is-onhold:hover .fcard__art .poster { transform: none; }
  .fcard.is-onhold:hover .fcard__cta { gap: 10px; color: var(--c-text-soft); }
}

/* Diagonal hazard tape. Clipped by .fcard__art (overflow:hidden), so the band
   runs edge to edge whatever the card's aspect ratio.                       */
.fcard__tape {
  position: absolute;
  z-index: 2;
  top: 43%;
  left: -10%;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 120%;
  padding: 6px 0;
  transform: translateY(-50%) rotate(-11deg);
  background: repeating-linear-gradient(135deg,
    rgba(255,180,77,.9) 0 13px,
    rgba(10,12,19,.92) 13px 26px);
  border-block: 1px solid rgba(255,255,255,.14);
  box-shadow: 0 12px 28px -14px rgba(0,0,0,.9);
}
.fcard__tape span {
  padding: 4px 12px;
  background: var(--c-bg-deep);
  border-radius: var(--r-xs);
  font-family: var(--font-display);
  font-size: var(--t-2xs);
  font-weight: 700;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--c-warn);
}

/* ─────────────────────────────────────────────────────────────────────────
   Artist card
   ───────────────────────────────────────────────────────────────────────── */
.acard {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--s-4);
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  transition: transform var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease),
              background var(--dur-2) var(--ease);
}
.acard:hover {
  transform: translateY(-3px);
  border-color: var(--c-line-accent);
  background: var(--c-surface-2);
}
.acard__face {
  position: relative;
  aspect-ratio: 1;
  margin-bottom: var(--s-3);
  border-radius: var(--r-md);
  overflow: hidden;
}
.acard__face .poster { position: absolute; inset: 0; }
/* Initials stand in for a photo; they read as deliberate, not missing. */
.acard__initials {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 5vw, 2.1rem);
  font-weight: 700;
  letter-spacing: .04em;
  color: rgba(255,255,255,.94);
  text-shadow: 0 2px 20px rgba(0,0,0,.5);
}
.acard__name { font-size: var(--t-body); font-weight: 600; }
.acard__genre { margin-top: 2px; font-size: var(--t-xs); color: var(--c-text-muted); }
.acard__foot {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--c-line);
  font-size: var(--t-2xs);
  color: var(--c-text-faint);
}

/* Small circular artist token used inside lineups. */
.atoken {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-3) var(--s-2) var(--s-2);
  background: rgba(255,255,255,.035);
  border: 1px solid var(--c-line);
  border-radius: var(--r-full);
  transition: background var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease);
}
.atoken:hover { background: rgba(255,255,255,.07); border-color: var(--c-line-accent); }
.atoken__orb {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  flex-shrink: 0;
  font-family: var(--font-display);
  font-size: var(--t-2xs);
  font-weight: 700;
  color: #fff;
  border-radius: var(--r-full);
  background: linear-gradient(140deg, hsl(var(--h1) 78% 56%), hsl(var(--h2) 74% 40%));
}
.atoken__text { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.atoken__text strong { font-size: var(--t-sm); font-weight: 600; }
.atoken__text em { font-style: normal; font-size: var(--t-2xs); color: var(--c-text-faint); }

/* ─────────────────────────────────────────────────────────────────────────
   Form controls
   ───────────────────────────────────────────────────────────────────────── */
.field { display: flex; flex-direction: column; gap: var(--s-2); }
.field__label {
  font-size: var(--t-2xs);
  font-weight: 600;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--c-text-faint);
}

.input, .select {
  width: 100%;
  height: 44px;
  padding-inline: var(--s-4);
  font-size: var(--t-sm);
  color: var(--c-text);
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.input::placeholder { color: var(--c-text-faint); }
.input:focus, .select:focus {
  outline: none;
  border-color: var(--c-line-accent);
  box-shadow: 0 0 0 4px var(--c-accent-wash);
}
.select {
  appearance: none;
  padding-right: var(--s-8);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238B94B0'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s-3) center;
  background-size: 18px;
  cursor: pointer;
}
.select option { background: var(--c-surface-2); color: var(--c-text); }

/* Range slider — used for price / distance / budget assumptions. */
.range { width: 100%; height: 22px; appearance: none; background: none; cursor: pointer; }
.range::-webkit-slider-runnable-track {
  height: 5px;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, var(--c-accent) var(--fill, 50%), var(--c-surface-3) var(--fill, 50%));
}
.range::-moz-range-track { height: 5px; border-radius: var(--r-full); background: var(--c-surface-3); }
.range::-moz-range-progress { height: 5px; border-radius: var(--r-full); background: var(--c-accent); }
.range::-webkit-slider-thumb {
  appearance: none;
  width: 17px; height: 17px;
  margin-top: -6px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid var(--c-accent);
  box-shadow: 0 2px 8px rgba(0,0,0,.5);
  transition: transform var(--dur-1) var(--ease);
}
.range::-moz-range-thumb {
  width: 17px; height: 17px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid var(--c-accent);
}
.range:active::-webkit-slider-thumb { transform: scale(1.2); }

/* Segmented control — view switchers, transport pickers, tabs. */
.segmented {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  border-radius: var(--r-full);
}
.segmented__btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding-inline: var(--s-4);
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--c-text-muted);
  border-radius: var(--r-full);
  transition: color var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}
.segmented__btn svg { width: 15px; height: 15px; }
.segmented__btn:hover { color: var(--c-text); }
.segmented__btn.is-active {
  color: var(--c-text);
  background: var(--c-surface-3);
  box-shadow: var(--e-1), inset 0 1px 0 rgba(255,255,255,.07);
}

/* Filter pill row — horizontally scrollable on small screens. */
.pillrow {
  display: flex;
  gap: var(--s-2);
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
  scroll-snap-type: x proximity;
}
.pillrow::-webkit-scrollbar { display: none; }

/* Inside a narrow panel a scrolling row reads as a clipped bug rather than an
   affordance, so short option sets wrap instead. */
.pillrow--wrap { flex-wrap: wrap; overflow: visible; }

.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding-inline: var(--s-4);
  flex-shrink: 0;
  scroll-snap-align: start;
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--c-text-muted);
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  border-radius: var(--r-full);
  transition: color var(--dur-2) var(--ease), background var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease);
}
.pill:hover { color: var(--c-text); border-color: var(--c-line-strong); }
.pill.is-active {
  color: #fff;
  background: var(--c-accent);
  border-color: transparent;
  box-shadow: 0 4px 14px -6px var(--c-accent);
}
.pill__count { font-size: 10px; opacity: .7; }

/* Checkbox / radio option rows in filter panels. */
.opt {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  font-size: var(--t-sm);
  color: var(--c-text-soft);
  cursor: pointer;
  transition: background var(--dur-1) var(--ease);
}
.opt:hover { background: rgba(255,255,255,.045); }
.opt input { position: absolute; opacity: 0; pointer-events: none; }
.opt__box {
  display: grid;
  place-items: center;
  width: 18px; height: 18px;
  flex-shrink: 0;
  border: 1.5px solid var(--c-line-strong);
  border-radius: 5px;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.opt__box svg { width: 12px; height: 12px; color: #fff; opacity: 0; transform: scale(.5); transition: all var(--dur-2) var(--ease-out); }
.opt input:checked + .opt__box { background: var(--c-accent); border-color: var(--c-accent); }
.opt input:checked + .opt__box svg { opacity: 1; transform: scale(1); }
.opt input:focus-visible + .opt__box { outline: 2px solid var(--c-accent-bright); outline-offset: 2px; }
.opt__count { margin-left: auto; font-size: var(--t-2xs); color: var(--c-text-faint); }

/* ─────────────────────────────────────────────────────────────────────────
   Stat tile
   ───────────────────────────────────────────────────────────────────────── */
.stat {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  padding: var(--s-4);
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
}
.stat__label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--t-2xs);
  font-weight: 600;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--c-text-faint);
}
.stat__label svg { width: 13px; height: 13px; }
.stat__value {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 1.1rem + 1vw, 1.75rem);
  font-weight: 700;
  letter-spacing: var(--track-tight);
  font-variant-numeric: tabular-nums;
}
.stat__note { font-size: var(--t-2xs); color: var(--c-text-muted); }

/* ─────────────────────────────────────────────────────────────────────────
   Charts (pure CSS / inline SVG — no chart library)
   ───────────────────────────────────────────────────────────────────────── */

/* Horizontal progress bar. */
.bar {
  height: 6px;
  background: var(--c-surface-3);
  border-radius: var(--r-full);
  overflow: hidden;
}
.bar i {
  display: block;
  width: var(--w, 0);   /* production: the fill arrives as data (--w), not as an inline width */
  height: 100%;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, var(--c-accent), var(--c-second));
  transition: width var(--dur-4) var(--ease-out);
}
/* Rated rather than measured: used by the password strength meter, where the
   colour is the message and the width alone would not carry it. */
.bar--weak i   { background: var(--c-hot); }
.bar--ok i     { background: var(--c-warn); }
.bar--strong i { background: var(--c-second); }

/* Donut. Rendered as SVG; the dash offset animates on mount. */

/* Legend shared by donut + stacked bar. */
.legend { display: flex; flex-direction: column; gap: 1px; }

/* ─────────────────────────────────────────────────────────────────────────
   Bottom sheet / drawer (mobile filters, settings)
   ───────────────────────────────────────────────────────────────────────── */
.sheet { position: fixed; inset: 0; z-index: 120; }
.sheet__scrim {
  position: absolute;
  inset: 0;
  background: rgba(4,5,9,.68);
  backdrop-filter: blur(4px);
  animation: fade var(--dur-2) var(--ease) both;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.sheet__panel {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  max-height: 86dvh;
  display: flex;
  flex-direction: column;
  background: var(--c-surface);
  border-top: 1px solid var(--c-line-strong);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  box-shadow: var(--e-4);
  animation: sheet-up var(--dur-3) var(--ease-out) both;
  padding-bottom: env(safe-area-inset-bottom);
}
@keyframes sheet-up { from { transform: translateY(100%); } to { transform: none; } }

.sheet__grab {
  width: 38px; height: 4px;
  margin: var(--s-3) auto 0;
  background: var(--c-surface-3);
  border-radius: var(--r-full);
}
.sheet__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5) var(--s-3);
}
.sheet__body { overflow-y: auto; padding: 0 var(--s-5) var(--s-5); }

/* On desktop the same component becomes a right-hand drawer. */
@media (min-width: 1024px) {
  .sheet__panel {
    top: 0; left: auto;
    width: min(420px, 92vw);
    max-height: none;
    border-radius: var(--r-xl) 0 0 var(--r-xl);
    border-top: none;
    border-left: 1px solid var(--c-line-strong);
    animation: sheet-in var(--dur-3) var(--ease-out) both;
  }
  @keyframes sheet-in { from { transform: translateX(100%); } to { transform: none; } }
  .sheet__grab { display: none; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Toasts
   ───────────────────────────────────────────────────────────────────────── */
.toasts {
  position: fixed;
  z-index: 200;
  left: var(--s-4);
  right: var(--s-4);
  bottom: calc(var(--bottomnav-h) + var(--s-4) + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  pointer-events: none;
}
@media (min-width: 1024px) {
  .toasts { left: auto; right: var(--s-6); bottom: var(--s-6); width: 340px; }
}

.toast {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--c-surface-2);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--e-3);
  font-size: var(--t-sm);
  pointer-events: auto;
  animation: toast-in var(--dur-3) var(--ease-out) both;
}
.toast.is-out { animation: toast-out var(--dur-2) var(--ease) both; }
@keyframes toast-in  { from { opacity: 0; transform: translateY(14px) scale(.97); } }
@keyframes toast-out { to   { opacity: 0; transform: translateY(8px) scale(.97); } }

.toast__icon {
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  flex-shrink: 0;
  border-radius: var(--r-full);
  background: var(--c-second-wash);
  color: var(--c-second);
}
.toast__icon svg { width: 15px; height: 15px; }
.toast__action { margin-left: auto; font-size: var(--t-xs); font-weight: 600; color: var(--c-accent-bright); }

/* ─────────────────────────────────────────────────────────────────────────
   Quick search results
   ───────────────────────────────────────────────────────────────────────── */
.quickresults {
  position: absolute;
  top: calc(100% + 8px);
  left: 0; right: 0;
  max-height: 60vh;
  overflow-y: auto;
  padding: var(--s-2);
  background: var(--c-surface-2);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--e-4);
  animation: reveal var(--dur-2) var(--ease-out) both;
}
.qr__group {
  padding: var(--s-2) var(--s-3) var(--s-1);
  font-size: var(--t-2xs);
  font-weight: 600;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--c-text-faint);
}
.qr__item {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  transition: background var(--dur-1) var(--ease);
}
.qr__item:hover, .qr__item:focus-visible { background: var(--c-accent-wash); }
.qr__orb {
  width: 34px; height: 34px;
  flex-shrink: 0;
  border-radius: var(--r-xs);
  background: linear-gradient(140deg, hsl(var(--h1) 76% 54%), hsl(var(--h2) 72% 38%));
}
.qr__text { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.qr__text strong { font-size: calc(var(--t-sm) * 1.125); font-weight: 600; }
.qr__text em { font-style: normal; font-size: calc(var(--t-2xs) * 1.125); color: var(--c-text-faint); }
.qr__text .flag { width: 18px; height: 12px; }   /* the flag grows with the line */
.qr__empty { padding: var(--s-4); text-align: center; font-size: var(--t-sm); color: var(--c-text-muted); }

/* ─────────────────────────────────────────────────────────────────────────
   Empty state
   ───────────────────────────────────────────────────────────────────────── */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s-3);
  padding: var(--s-12) var(--s-5);
  background:
    radial-gradient(80% 100% at 50% 0%, var(--c-accent-wash), transparent 70%),
    var(--c-surface);
  border: 1px dashed var(--c-line-strong);
  border-radius: var(--r-lg);
}
.empty__orb {
  display: grid;
  place-items: center;
  width: 62px; height: 62px;
  color: var(--c-accent-bright);
  background: var(--c-accent-wash);
  border: 1px solid var(--c-line-accent);
  border-radius: var(--r-full);
}
.empty__orb svg { width: 27px; height: 27px; }
.empty h3 { font-size: var(--t-h3); }
.empty p { max-width: 42ch; font-size: var(--t-sm); color: var(--c-text-muted); }

/* ─────────────────────────────────────────────────────────────────────────
   Data list (practical info, cost breakdowns)
   ───────────────────────────────────────────────────────────────────────── */
.dlist { display: flex; flex-direction: column; }
.dlist__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--c-line);
  font-size: var(--t-sm);
}
.dlist__row:last-child { border-bottom: none; }
.dlist__key { display: flex; align-items: center; gap: var(--s-3); color: var(--c-text-muted); }
.dlist__key svg { width: 16px; height: 16px; color: var(--c-text-faint); flex-shrink: 0; }
.dlist__val { font-weight: 500; text-align: right; font-variant-numeric: tabular-nums; }
.dlist__row--total {
  margin-top: var(--s-2);
  padding-top: var(--s-4);
  border-top: 1px solid var(--c-line-strong);
  border-bottom: none;
}
.dlist__row--total .dlist__key { color: var(--c-text); font-weight: 600; }
.dlist__row--total .dlist__val {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--c-second);
}

/* ═════════════════════════════════════════════════════════════════════════
   PRODUCTION ADDITIONS (server-rendered pages)
   ═══════════════════════════════════════════════════════════════════════ */

/* Sponsored tile (docs/features/advertising): a cell of the festival and
   artist grids, never mistaken for one of their cards — no poster, a dashed
   frame, and the label on top in the card title's place. */
.adtile {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  min-width: 0;
  padding: var(--s-4);
  background: var(--c-surface);
  border: 1px dashed var(--c-line-strong);
  border-radius: var(--r-lg);
}
.adtile__label {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--c-text-faint);
  font-size: var(--t-2xs);
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.adtile__label svg { width: 14px; height: 14px; }
.adtile__slot { display: block; flex: 1 1 auto; min-height: 120px; }
.adtile__slot--preview {
  display: grid;
  place-items: center;
  padding: var(--s-4);
  border-radius: var(--r-md);
  background: repeating-linear-gradient(135deg, var(--c-surface-2) 0 10px, transparent 10px 20px);
  color: var(--c-text-muted);
  font-size: var(--t-xs);
  text-align: center;
}
.adtile__note { color: var(--c-text-faint); font-size: var(--t-2xs); }

/* Hero banner on a discovery card (Phase 10b, docs/features/festival-discovery):
   the photo sits inside the generated poster, under its bottom scrim (::after),
   so the title and tags stay legible over any image. */
.poster--photo > .poster__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.poster--photo::before { display: none; }   /* no motif over a photo */
.qr__item.is-active { background: var(--c-accent-wash); box-shadow: inset 0 0 0 1px var(--c-line-accent); }
.qr__text strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* On a phone the search field is only a sliver of the top bar: the suggestions
   take the screen's width under it (the top bar, with its backdrop filter,
   is what a fixed box is placed against). */
@media (max-width: 639px) {
  .quickresults { position: fixed; top: calc(var(--topbar-h) + 4px); left: var(--gutter); right: var(--gutter); }
}
