/* Band Cloud — dark poster
   Palette is gel-lit stage, not neon: accents sit ~15% off pure hue and every
   one clears 4.5:1 on --ground. */

/* ---------------------------------------------------------------- themes ---
   Token groups, in the order they matter:

   --w0..--w5   the wall's tonal ramp, indexed by tier. How often a band was
                seen drives its *lightness*, not just its size, so one-off names
                recede toward the ground and repeats come forward. With a single
                foreground color, size works alone and a dense wall reads as a
                sheet of white.
   --a1..--a5   wall accent hues, mixed toward that tier's own ramp value at
                paint time, so a rare color on a small word is a whisper.
   --ochre/--teal/--indigo   semantic UI only (dates, badges, links).
*/

:root {
  --ground:    #0b0a0a;
  --ground-2:  #141211;
  --bone:      #ece7de;
  --bone-dim:  #8a837a;
  --hairline:  #2a2724;

  /* Every step clears 4.5:1 on --ground. The ramp wants to go darker at the
     bottom, but the smallest tiers hold most of the words, so a prettier floor
     would put the majority of the page under AA. */
  --w0: #807b74;   /*  4.7:1 */
  --w1: #938d84;   /*  6.0   */
  --w2: #a9a298;   /*  7.8   */
  --w3: #c7c0b5;   /* 11.0   */
  --w4: #dcd6cb;   /* 13.7   */
  --w5: #ece7de;   /* 16.1   */

  --a1: #b8604a;
  --a2: #9c8560;
  --a3: #6f8579;
  --a4: #6f7f96;
  --a5: #8a7484;

  --ochre:     #c9974f;
  --teal:      #559c92;
  --indigo:    #7d8cbe;

  /* Background wash intensity. Capped here rather than inside the draw loop,
     so the wall stays legible over whatever haze.js paints. */
  --haze-intensity: 0.55;

  /* Two wall faces. `--display` is declared outright in each state rather than
     aliasing another custom property — the layout engine reads this value back
     with getComputedStyle to measure glyph advances, so it has to resolve to a
     real font stack, not a var() reference. */
  --display: "Oswald", "Avenir Next Condensed", "HelveticaNeue-CondensedBold",
             "Haettenschweiler", "Arial Narrow", system-ui, sans-serif;
  --ui: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;

  --ease: cubic-bezier(.2, .7, .3, 1);
}

/* Tungsten bulbs on a theater front. One hue family — all the variation lives in
   temperature and value, so the ramp reads as a filament warming up rather than
   as five different colors. Accents are cold, like a blown bulb. */
:root[data-theme="marquee"] {
  --ground:    #0a0806;
  --ground-2:  #141009;
  --bone:      #f6e3c4;
  --bone-dim:  #93795a;
  --hairline:  #2b2116;

  --w0: #917750;   /*  4.7:1 */
  --w1: #a78858;   /*  6.0   */
  --w2: #c29c5c;   /*  7.8   */
  --w3: #dfb26a;   /* 10.2   */
  --w4: #f3ca88;   /* 13.0   */
  --w5: #ffd9a0;   /* 14.9   */

  --a1: #e8eef5;
  --a2: #a8bdd4;
  --a3: #ff9d5c;
  --a4: #c2703a;
  --a5: #d8c07a;

  --ochre:     #e0b464;
  --teal:      #a8bdd4;
  --indigo:    #d8c07a;
}

:root[data-face="marker"] {
  --display: "Permanent Marker", "Bradley Hand", "Segoe Print", "Comic Sans MS", cursive;
}

/* Self-hosted, no external requests. Google Fonts' `latin` subset (U+0000–00FF)
   — covers every band and venue name in the data with room for future ones.
   Licenses in assets/fonts/LICENSES.md. */
@font-face {
  font-family: "Oswald";
  font-style: normal;
  font-weight: 300 700;      /* variable axis */
  font-display: swap;
  src: url("fonts/oswald-latin-var.woff2") format("woff2");
}
@font-face {
  font-family: "Permanent Marker";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/permanent-marker-latin.woff2") format("woff2");
}

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

html {
  -webkit-text-size-adjust: 100%;
  background: var(--ground);
  overflow-x: clip;
  /* Hold the gutter so gaining or losing a scrollbar can't change the width the
     layout was solved against. */
  scrollbar-gutter: stable;
}

body {
  background: var(--ground);
  color: var(--bone);
  font-family: var(--ui);
  overflow-x: clip;               /* words bleed past both edges by design */
  min-height: 100svh;
}

.skip {
  position: fixed;
  top: 0; left: 50%;
  transform: translate(-50%, -120%);
  z-index: 60;
  background: var(--bone);
  color: var(--ground);
  padding: .6rem 1.1rem;
  border-radius: 0 0 .4rem .4rem;
  font-size: .8125rem;
  text-decoration: none;
}
.skip:focus { transform: translate(-50%, 0); }

/* ------------------------------------------------------------------ haze --- */

#haze {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  opacity: var(--haze-intensity);
}

/* -------------------------------------------------------------- masthead --- */

.masthead {
  position: relative;
  z-index: 2;
  padding: clamp(1.25rem, 3vw, 1.9rem) clamp(1.1rem, 4vw, 2.75rem) 0;
  display: flex;
  flex-direction: column;
  gap: .3rem;
  pointer-events: none;
}

.masthead h1 {
  font-family: var(--display);
  font-size: clamp(.9375rem, 1.5vw, 1.0625rem);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--bone);
}

.stats {
  display: flex;
  align-items: center;
  gap: .55rem;
  font-size: clamp(.75rem, 1.2vw, .8125rem);
  letter-spacing: .06em;
  color: var(--bone-dim);
  font-variant-numeric: tabular-nums;
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.chip {
  /* .masthead is pointer-events:none so the wall stays clickable through it —
     the interactive things inside it have to opt back in. */
  pointer-events: auto;
  display: grid;
  place-items: center;
  width: 1.55rem;
  height: 1.55rem;
  flex: none;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  background: none;
  color: var(--bone-dim);
  cursor: pointer;
  transition: color 160ms var(--ease), border-color 160ms var(--ease), rotate 400ms var(--ease);
}
.chip svg { width: 13px; height: 13px; }
.chip:hover, .chip:focus-visible { color: var(--bone); border-color: var(--bone-dim); }
#shuffle:active { rotate: 180deg; }

/* Set in whichever face you'd switch *to*, so the control previews its result.
   font-family is assigned from JS; only the sizing lives here. */
.chip-face {
  font-size: .8125rem;
  line-height: 1;
  letter-spacing: -.02em;
  padding-bottom: .06em;
}

@media (prefers-reduced-motion: reduce) {
  .chip { transition: color 160ms var(--ease), border-color 160ms var(--ease); }
  #shuffle:active { rotate: none; }
}

/* ------------------------------------------------------------------ wall --- */

.wall {
  position: relative;
  z-index: 1;
  width: 100%;
  margin-top: clamp(1.1rem, 3vw, 2rem);
  /* Height is set inline by the layout engine. */
}

.wall.is-measuring { visibility: hidden; }

/* Bottom bleed on screens where the whole wall fits. Clipping only the block
   axis keeps the left/right overhang intact — `clip` is what allows one axis to
   stay visible while the other is cut. */
.wall.is-clipped {
  overflow-y: clip;
  overflow-x: visible;
}

.word {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  white-space: nowrap;
  line-height: 1;
  font-family: var(--display);
  color: var(--bone);
  text-decoration: none;
  will-change: transform, opacity;
  transition: color 200ms var(--ease), opacity 220ms var(--ease);

  /* Parallax rides on the `translate` property so it composes with the layout
     `transform` instead of fighting it. --px/--py are set once on the wall and
     inherit, so a pointer move is one style write, not one per word.

     Only the horizontal axis is depth-scaled, and --dp is per ROW (see render).
     Depth per *word* is what made this glitchy: neighbors in the same row slid
     at different rates, up to 0.75× amplitude of relative movement, which is
     more than the few px of gap between small adjacent words — so they collided
     as the pointer moved. Rows slide as rigid units now, and the vertical
     component is uniform, so nothing ever moves relative to anything it could
     touch. */
  translate: calc(var(--px, 0px) * var(--dp, 0)) var(--py, 0px);
}

/* Spotlight: hovering anywhere in the wall recedes everything else.
   Pure CSS — no neighbor computation, and it reads better than a radius.

   Deliberately NO geometry change on hover. A scale here jitters badly: with
   transform-origin at 0 0 the box grows right and down, moving its own hit-edge
   out from under the cursor, which drops hover, which shrinks it back, which
   re-enters hover. On a wall this dense the grown box also steals hover from the
   word beside it. The lift is a value lift instead — the hovered word climbs to
   the top of the tonal ramp, which suits a design where lightness already
   carries the hierarchy. */
@media (hover: hover) and (pointer: fine) {
  .wall:hover .word { opacity: .4; }
  .wall .word:hover {
    opacity: 1;
    color: var(--hl, var(--w5));
  }
}
.word { transition: color 180ms var(--ease), opacity 200ms var(--ease); }

.word:focus-visible {
  opacity: 1;
  outline: 2px solid var(--bone);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Search filtering */
.wall.is-filtered .word { opacity: .16; }
.wall.is-filtered .word.is-match { opacity: 1; color: var(--ochre); }

/* Entrance — staggered outward from the centre of the viewport. */
@keyframes settle {
  from { opacity: 0; transform: var(--t0); }
  to   { opacity: 1; transform: var(--t1); }
}
.wall.is-entering .word {
  opacity: 0;
  animation: settle 620ms var(--ease) both;
  animation-delay: var(--d, 0ms);
}

/* Wall recedes behind an open panel. */
body.panel-open .wall,
body.panel-open .masthead { filter: blur(2px); opacity: .5; }
.wall, .masthead { transition: filter 260ms var(--ease), opacity 260ms var(--ease); }

/* ------------------------------------------------------------ search bar --- */

/* Above the scrim: the search bar stays reachable while a panel is open, so you
   can always search your way out of a detail view. */
.searchbar {
  position: fixed;
  z-index: 60;
  left: 50%;
  transform: translateX(-50%);
  top: max(1.1rem, env(safe-area-inset-top));
  width: min(26rem, calc(100vw - 2rem));
}

/* Wide enough for both: masthead keeps the left, search moves out of its way. */
@media (min-width: 62rem) {
  .searchbar {
    left: auto;
    transform: none;
    right: clamp(1.1rem, 4vw, 2.75rem);
    top: clamp(1.1rem, 3vw, 1.7rem);
    width: 22rem;
    transition: right 320ms var(--ease);
  }
  /* Slide clear of the panel rather than hide — search stays a way out. */
  body.panel-open .searchbar { right: calc(min(30rem, 100vw) + 1.5rem); }
}

.search-shell {
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .55rem .75rem;
  border: 1px solid var(--hairline);
  border-radius: 2rem;
  background: color-mix(in srgb, var(--ground) 82%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  transition: border-color 200ms var(--ease);
}
.searchbar:focus-within .search-shell { border-color: color-mix(in srgb, var(--bone) 45%, transparent); }

.search-icon { width: 15px; height: 15px; flex: none; color: var(--bone-dim); }

#search-input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  color: var(--bone);
  font: inherit;
  font-size: .9375rem;
  outline: none;
}
#search-input::placeholder { color: var(--bone-dim); }

.search-key {
  flex: none;
  font-family: var(--ui);
  font-size: .6875rem;
  color: var(--bone-dim);
  border: 1px solid var(--hairline);
  border-radius: .25rem;
  padding: .05rem .3rem;
  line-height: 1.4;
}
.searchbar:focus-within .search-key { opacity: 0; }

.results {
  list-style: none;
  margin-top: .4rem;
  max-height: min(24rem, 60svh);
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--hairline);
  border-radius: .85rem;
  background: color-mix(in srgb, var(--ground-2) 94%, transparent);
  backdrop-filter: blur(18px) saturate(1.2);
  -webkit-backdrop-filter: blur(18px) saturate(1.2);
  padding: .3rem;
}
.results[hidden] { display: none; }

.results li[role="option"] {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  padding: .5rem .6rem;
  border-radius: .5rem;
  cursor: pointer;
  font-size: .875rem;
  line-height: 1.3;
}
.results li[aria-selected="true"] { background: color-mix(in srgb, var(--bone) 12%, transparent); }
.results .r-name { color: var(--bone); }
.results .r-name b { font-weight: 700; color: var(--ochre); }
.results .r-meta {
  margin-left: auto;
  font-size: .75rem;
  color: var(--bone-dim);
  white-space: nowrap;
  letter-spacing: .03em;
}
.results .r-empty { padding: .7rem .6rem; font-size: .875rem; color: var(--bone-dim); }
.results .r-group {
  padding: .5rem .6rem .25rem;
  font-size: .6875rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--bone-dim);
}

/* ----------------------------------------------------------------- panel --- */

.scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--ground) 70%, transparent);
  animation: fade 200ms var(--ease) both;
}
@keyframes fade { from { opacity: 0 } to { opacity: 1 } }

.panel {
  position: fixed;
  z-index: 50;
  top: 0; right: 0;
  height: 100svh;
  width: min(30rem, 100vw);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--ground-2);
  border-left: 1px solid var(--hairline);
  padding: clamp(1.5rem, 4vw, 2.5rem);
  animation: slide-in 320ms var(--ease) both;
}
@keyframes slide-in { from { transform: translateX(2rem); opacity: 0 } to { transform: none; opacity: 1 } }

@media (max-width: 34rem) {
  .panel {
    top: auto; bottom: 0; right: 0; left: 0;
    width: 100%;
    height: min(86svh, 100svh);
    border-left: 0;
    border-top: 1px solid var(--hairline);
    border-radius: 1rem 1rem 0 0;
  }
  @keyframes slide-in { from { transform: translateY(2rem); opacity: 0 } to { transform: none; opacity: 1 } }
  .searchbar { top: auto; bottom: max(1rem, env(safe-area-inset-bottom)); }
  .results { position: absolute; bottom: 100%; margin-bottom: .4rem; margin-top: 0; width: 100%; }
  /* Here the panel is a bottom sheet and would sit right on top of the bar. */
  body.panel-open .searchbar { display: none; }
}

.panel-close {
  position: absolute;
  top: 1rem; right: 1rem;
  width: 2rem; height: 2rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  background: none;
  color: var(--bone-dim);
  cursor: pointer;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.panel-close:hover, .panel-close:focus-visible { color: var(--bone); border-color: var(--bone-dim); }
.panel-close svg { width: 14px; height: 14px; }

.panel-kicker {
  font-size: .6875rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--bone-dim);
  margin-bottom: .5rem;
}

#panel-title {
  font-family: var(--display);
  font-size: clamp(1.75rem, 5.5vw, 2.5rem);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -.005em;
  margin-bottom: .5rem;
  text-wrap: balance;
}

.panel-sub {
  color: var(--bone-dim);
  font-size: .9375rem;
  padding-bottom: 1.5rem;
  margin-bottom: .5rem;
  border-bottom: 1px solid var(--hairline);
}

.show {
  padding: 1.25rem 0;
  border-bottom: 1px solid var(--hairline);
}
.show:last-child { border-bottom: 0; }

.show-date {
  font-size: .8125rem;
  letter-spacing: .06em;
  color: var(--ochre);
  font-variant-numeric: tabular-nums;
  margin-bottom: .3rem;
}
.show-date.is-unknown { color: var(--bone-dim); font-style: italic; letter-spacing: .02em; }
.show-date .approx { color: var(--bone-dim); font-style: italic; letter-spacing: .02em; text-transform: none; }

.show-venue {
  font-family: var(--display);
  font-size: 1.3125rem;
  font-weight: 600;
  line-height: 1.2;
}
.show-venue a { color: var(--bone); text-decoration: none; border-bottom: 1px solid transparent; }
.show-venue a:hover, .show-venue a:focus-visible { border-bottom-color: var(--bone-dim); }

.show-city { color: var(--bone-dim); font-size: .875rem; margin-top: .15rem; }
.show-city .assumed { opacity: .7; font-style: italic; }

.badge {
  display: inline-block;
  margin-top: .5rem;
  font-size: .6875rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: .2rem .5rem;
  border-radius: .25rem;
  border: 1px solid color-mix(in srgb, var(--teal) 45%, transparent);
  color: var(--teal);
  text-decoration: none;
}
.badge:hover, .badge:focus-visible { border-color: var(--teal); }

.show-note {
  margin-top: .55rem;
  font-size: .875rem;
  color: var(--bone-dim);
  font-style: italic;
}

.show-with {
  margin-top: .6rem;
  font-size: .875rem;
  color: var(--bone-dim);
  line-height: 1.5;
}
.show-tour {
  font-style: italic;
  opacity: .8;
}
.show-tour::before { content: "— "; }

/* --------------------------------------------------------------- indexes --- */

.idx-list { display: flex; flex-direction: column; }

.idx {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .1rem .75rem;
  padding: .7rem 0;
  border-bottom: 1px solid var(--hairline);
  text-decoration: none;
  color: var(--bone);
}
.idx:last-child { border-bottom: 0; }
.idx:hover .idx-name, .idx:focus-visible .idx-name { color: var(--ochre); }

.idx-name {
  font-family: var(--display);
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.2;
  transition: color 160ms var(--ease);
}
.idx-meta { grid-column: 1; font-size: .8125rem; color: var(--bone-dim); }
.idx-count {
  grid-column: 2; grid-row: 1 / span 2;
  align-self: center;
  font-size: .75rem;
  color: var(--bone-dim);
  white-space: nowrap;
  letter-spacing: .03em;
}

.stats a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--hairline);
  pointer-events: auto;   /* .masthead is pointer-events:none */
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.stats a:hover, .stats a:focus-visible { color: var(--bone); border-bottom-color: var(--bone-dim); }
.show-with a { color: var(--indigo); text-decoration: none; border-bottom: 1px solid transparent; }
.show-with a:hover, .show-with a:focus-visible { border-bottom-color: currentColor; }

.show-links { margin-top: .55rem; font-size: .8125rem; }
.show-links a { color: var(--bone-dim); }

.noscript {
  position: relative;
  z-index: 5;
  padding: 2rem;
  max-width: 34rem;
  color: var(--bone-dim);
  line-height: 1.6;
}
.noscript a { color: var(--indigo); }

/* --------------------------------------------------------- reduced motion --- */

@media (prefers-reduced-motion: reduce) {
  .wall.is-entering .word { animation-duration: 200ms; animation-delay: 0ms !important; }
  .panel, .scrim { animation-duration: 1ms; }
  .word { transition-duration: 1ms; translate: none; }
}
