/* =========================================================================
   Downstroke — site layer
   New components and motion for index.html / musik.html.
   Loaded AFTER css/design-system.css. Tokens live there; nothing is
   redefined here. Transform + opacity only, never a blanket transition, no glows.
   ========================================================================= */

/* ---- 1. Intro curtain --------------------------------------------------- */
/* Hidden by default: without JS (or under reduced motion) it never appears.
   js/intro.js puts .intro-active on <html> to arm it. */
.intro {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: none;
  place-items: center;
  background: var(--bg);
  transform: translateY(0);
  will-change: transform;
}
html.intro-active .intro,
html.intro-leaving .intro { display: grid; }
html.intro-leaving .intro {
  transform: translateY(-100%);
  transition: transform 0.6s var(--ease);
}
html.intro-active body { overflow: hidden; }

.intro__mark { width: clamp(140px, 24vw, 260px); height: auto; }
.intro__bar {
  transform-box: fill-box;
  transform-origin: bottom;
  transform: scaleY(0);
}
html.intro-active .intro__bar,
html.intro-leaving .intro__bar {
  animation: intro-bar 0.42s var(--ease) both;
}
html .intro__bar:nth-child(1) { animation-delay: 0ms; }
html .intro__bar:nth-child(2) { animation-delay: 60ms; }
html .intro__bar:nth-child(3) { animation-delay: 120ms; }
html .intro__bar:nth-child(4) { animation-delay: 180ms; }
html .intro__bar:nth-child(5) { animation-delay: 240ms; }
html .intro__bar:nth-child(6) { animation-delay: 300ms; }
@keyframes intro-bar {
  from { transform: scaleY(0); }
  to { transform: scaleY(1); }
}

/* While the curtain is up the hero holds its pre-reveal state. */
html.intro-active .hero .reveal { opacity: 0; transform: translateY(28px); }

/* Without JS nothing can reveal itself — show everything as plain markup. */
.no-js .intro { display: none; }
.no-js .reveal { opacity: 1; transform: none; transition: none; }
.no-js .reveal-cover .cover { display: none; }
.no-js .reveal-cover img { transform: none; }

/* ---- 1b. Bracket buttons ------------------------------------------------ */
/* design-system.css styles a.bracket; the record toggles are real buttons. */
button.bracket {
  position: relative;
  cursor: pointer;
  transition: color 0.3s var(--ease), transform 0.3s var(--ease);
}
button.bracket::before,
button.bracket::after { transition: color 0.3s var(--ease), transform 0.3s var(--ease); }
button.bracket:hover { color: var(--accent); }
button.bracket:hover::before { color: var(--accent); transform: translateX(-3px); }
button.bracket:hover::after { color: var(--accent); transform: translateX(3px); }
button.bracket:active { transform: translateY(1px); }

/* Tap target: padding lifts the 21px label to a 45px hit area, the matching
   negative margin keeps every bracket where the layout put it. Rules that set
   a bracket's margin subtract --hit. */
a.bracket,
button.bracket {
  --hit: 0.75rem;
  padding-block: var(--hit);
  margin-block: calc(var(--hit) * -1);
}

/* ---- 2. Scroll progress ------------------------------------------------- */
.progress {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  height: 2px;
  pointer-events: none;
}
.progress__bar {
  display: block;
  height: 100%;
  background: var(--accent);
  transform-origin: left center;
  transform: scaleX(var(--progress, 0));
}

/* ---- 3. Text motion ----------------------------------------------------- */
/* 3a. Per-letter hero rise. Lines are masks; letters start below the mask. */
/* The mask is grown by its own negative margins so the box it occupies stays
   exactly the line height: the extra head-room keeps umlauts (HÖR) and the
   extra foot-room keeps descenders from being clipped. */
.hero__line {
  display: block;
  overflow: hidden;
  padding: 0.3em 0 0.1em;
  margin: -0.3em 0 -0.1em;
}
/* Letters are inline-blocks — without this the browser may break mid-word. */
.hero__word { display: block; white-space: nowrap; }
.hero__copy .display-xl .char,
.char { display: inline-block; }
.is-split .char {
  transform: translateY(calc(110% + 0.45em));
  transition: transform 0.9s var(--ease);
  transition-delay: calc(var(--i, 0) * 35ms);
}
.is-split.is-in .char { transform: translateY(0); }

/* 3b. Headline line rise — JS wraps the headline in a mask. */
.rise {
  display: block;
  overflow: hidden;
  padding: 0.45em 0 0.18em;
  margin: -0.45em 0 -0.18em;
}
.rise__inner {
  display: block;
  transform: translateY(calc(100% + 0.7em));
  transition: transform 0.9s var(--ease);
}
.is-in > .rise > .rise__inner,
.rise.is-in > .rise__inner { transform: translateY(0); }

/* 3c. Quote — word by word. */
.word {
  display: inline-block;
  opacity: 0.15;
  transition: opacity 0.6s var(--ease);
  transition-delay: calc(var(--i, 0) * 60ms);
}
.is-in .word { opacity: 1; }

/* ---- 4. Image cover reveal ---------------------------------------------- */
/* A canvas-coloured panel wipes up off the figure while the photo settles. */
.reveal-cover { position: relative; }
.reveal-cover .cover {
  position: absolute;
  inset: 0;
  z-index: 5;
  background: var(--bg);
  transform-origin: top;
  transform: scaleY(1);
  transition: transform 1.1s var(--ease);
  pointer-events: none;
}
.reveal-cover.is-in .cover { transform: scaleY(0); }
.reveal-cover img,
.reveal-cover video { transform: scale(1.12); }
.reveal-cover.is-in img,
.reveal-cover.is-in video { transform: scale(1); }
.reveal-cover.is-in.media--hover:hover img,
.reveal-cover.is-in.media--hover:hover video { transform: scale(1.04); }

/* ---- 5. Marquee scroll nudge -------------------------------------------- */
/* Wrapper so the ticker's own loop (on .marquee__track) stays untouched. */
.marquee-nudge {
  display: block;
  transform: translate3d(var(--marquee-shift, 0px), 0, 0);
}

/* ---- 6. Row hover — content shift + the row's lead figure turns red ----- */
/* Ledger: the number. Concerts: the date. Records: the year fills red (§9). */
.gig,
.ledger__row,
.record__head,
.videos__row { position: relative; }

.gig > *,
.ledger__row > *,
.record__head > *,
.videos__row > * { transition: transform 0.3s var(--ease); }
.ledger__row > .ledger__num,
.gig > .gig__date { transition: transform 0.3s var(--ease), color 0.3s var(--ease); }
.gig__year { transition: color 0.3s var(--ease); }

.gig:hover > *,
.ledger__row:hover > *,
.record:hover .record__head > *,
.videos__row:hover > * { transform: translateX(8px); }

@media (hover: hover) {
  .ledger__row:hover .ledger__num,
  .gig:hover .gig__date,
  .gig:hover .gig__year { color: var(--accent); }
}

/* ---- 7. Section heads (shared) ------------------------------------------ */
.head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
  margin-bottom: clamp(2rem, 4vw, 3rem);
}
.head .display-md,
.head .display-lg { margin-top: 1rem; }
/* Wide enough for a one- or two-line display-md head — at 0.9 line-height a
   third line puts umlauts into the line above. */
.head__copy { max-width: min(100%, 52rem); }
.head__copy .body { margin-top: 1.25rem; }

/* ---- 8. Videos ---------------------------------------------------------- */
.videos {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(1.5rem, 3vw, 3rem);
  align-items: start;
}
.videos__aside { position: sticky; top: calc(var(--nav-h) + 1rem); }
.videos__stage {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  box-shadow: var(--shadow);
}
.videos__stage img { transition: opacity 0.35s var(--ease), transform 0.8s var(--ease); }
.videos__stage.is-swapping img { opacity: 0; }
.videos__play {
  position: absolute;
  z-index: 3;
  left: 50%;
  top: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: clamp(3.25rem, 5vw, 4.25rem);
  height: clamp(3.25rem, 5vw, 4.25rem);
  margin: 0;
  background: var(--accent);
  color: var(--accent-ink);
  border: 0;
  transform: translate(-50%, -50%);
  transition: transform 0.3s var(--ease), background-color 0.3s var(--ease);
}
.videos__play svg { width: 34%; height: 34%; }
.videos__play:hover { background: var(--accent-hover); transform: translate(-50%, -50%) scale(1.06); }
.videos__play:active { transform: translate(-50%, -50%) scale(0.97); }
.videos__frame { position: absolute; inset: 0; z-index: 4; display: none; background: var(--bg); }
.videos__frame iframe { width: 100%; height: 100%; border: 0; display: block; }
.videos__stage.is-playing .videos__frame { display: block; }
.videos__note { margin-top: 0.85rem; }

.videos__list { list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--line); }
.videos__row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 1rem;
  border-bottom: 1px solid var(--line);
  transition: background-color 0.3s var(--ease);
}
.videos__row:hover { background: rgba(244, 243, 239, 0.025); }
.videos__btn {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0.25rem 1rem;
  width: 100%;
  padding: 1.1rem 0;
  text-align: left;
}
.videos__num { font-size: var(--text-label); font-weight: 700; letter-spacing: 0.1em; color: var(--ink-faint); transition: color 0.3s var(--ease); }
.videos__title {
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: 112%;
  font-size: clamp(1.05rem, 1.5vw, 1.35rem);
  line-height: 1;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  transition: color 0.3s var(--ease);
}
.videos__composer { grid-column: 2; font-size: var(--text-label); letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-muted); }
.videos__dur { grid-row: 1; grid-column: 3; font-size: var(--text-label); font-weight: 700; letter-spacing: 0.1em; color: var(--ink-muted); }
.videos__btn .bars { grid-row: 2; grid-column: 3; justify-self: end; height: 0.8rem; opacity: 0; transition: opacity 0.3s var(--ease); }
.videos__btn:hover .videos__title { color: var(--accent); }
.videos__btn:active { transform: translateY(1px); }
.videos__ext { padding-left: 0.75rem; }

/* Small active numeral: --ramp-4 keeps 4.5:1 on the dark rows; --accent is only 3.6:1. */
.videos__btn[aria-current="true"] .videos__num { color: var(--ramp-4); }
.videos__btn[aria-current="true"] .bars { opacity: 1; }
.videos__btn[aria-current="true"] .bars span { animation: bar 0.9s var(--ease) infinite alternate; }
.videos__btn[aria-current="true"] .bars span:nth-child(1) { animation-duration: 0.72s; }
.videos__btn[aria-current="true"] .bars span:nth-child(2) { animation-duration: 0.98s; animation-delay: -0.2s; }
.videos__btn[aria-current="true"] .bars span:nth-child(3) { animation-duration: 0.84s; animation-delay: -0.4s; }
.videos__btn[aria-current="true"] .bars span:nth-child(4) { animation-duration: 1.06s; animation-delay: -0.1s; }
.videos__btn[aria-current="true"] .bars span:nth-child(5) { animation-duration: 0.78s; animation-delay: -0.3s; }

/* ---- 9. Records (discography) ------------------------------------------- */
.records { list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--line); }
.record { border-bottom: 1px solid var(--line); }
.record__head {
  display: grid;
  grid-template-columns: 11rem minmax(0, 1fr) auto;
  align-items: start;
  gap: 1rem 2rem;
  padding-block: 1.75rem;
}
.record__year { position: relative; display: block; line-height: 0.9; }
.record__year .display-md { display: block; }
/* Red fill with a red stroke of the outline's width, so no white edge of
   the outline copy shows around the letters. */
.record__year-solid {
  position: absolute;
  inset: 0;
  color: var(--accent);
  -webkit-text-stroke: 1.5px var(--accent);
  opacity: 0;
  transition: opacity 0.3s var(--ease);
}
.record:hover .record__year-solid { opacity: 1; }
.record__title { display: block; }
.record__text { font-size: 0.92rem; color: var(--ink); line-height: 1.6; max-width: 48ch; margin-top: 0.6rem; }
.record__actions { display: flex; flex-direction: column; align-items: flex-end; gap: 0.75rem; }
.record__panel { display: none; padding-bottom: 1.75rem; }
.record__panel.is-open { display: block; opacity: 0; transform: translateY(12px); transition: opacity 0.6s var(--ease), transform 0.6s var(--ease); }
.record__panel.is-open.is-shown { opacity: 1; transform: translateY(0); }
.record__panel iframe { display: block; width: 100%; border: 0; background: var(--surface); }
.record__panel .meta { display: block; margin-top: 0.75rem; }

/* ---- 9b. Band gallery ---------------------------------------------------- */
/* Four photos in a 7/5 then 5/7 zig-zag. Rows share one height so the
   seams line up; phones fall back to a single 3:2 column. */
.gallery {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-rows: clamp(14rem, 28vw, 26rem);
  gap: clamp(0.75rem, 1.5vw, 1.25rem);
}
.gallery__item { grid-column: span 5; margin: 0; }
.gallery__item--wide { grid-column: span 7; }

/* ---- 10. Line-up list ---------------------------------------------------- */
.lineup { list-style: none; padding: 0; margin-top: 2rem; border-top: 1px solid var(--line); max-width: 34rem; }
.lineup li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: 0.85rem;
  border-bottom: 1px solid var(--line);
}
.lineup__name { font-family: var(--font-display); font-weight: 800; font-stretch: 112%; font-size: 1rem; letter-spacing: -0.01em; text-transform: uppercase; }

/* Jobs list under the stage statement: a bracket label, then the lineup
   rows pulled up close to it. */
.statement__label { display: flex; width: fit-content; margin-top: 2rem; }
.lineup--jobs { margin-top: 0.85rem; }

/* ---- 10b. Setlist ---------------------------------------------------------- */
/* The six tunes of the full set. A click starts the set at that tune, or
   jumps there if it is already playing; js/site.js marks the tune that is
   playing (aria-current) from the player's reported time. */
.setlist { margin-top: clamp(2rem, 4vw, 3rem); }
.setlist__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  margin-bottom: 1rem;
}
.setlist__list { list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--line); }
.setlist__list li { position: relative; border-bottom: 1px solid var(--line); }
.setlist__list li::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.6s var(--ease);
}
.setlist__song {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 1rem;
  width: 100%;
  padding: 0.85rem 0;
  text-align: left;
}
.setlist__no,
.setlist__time {
  font-size: var(--text-label);
  font-weight: 700;
  letter-spacing: 0.1em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  transition: color 0.3s var(--ease);
}
.setlist__title {
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: 112%;
  font-size: clamp(0.95rem, 1.2vw, 1.1rem);
  line-height: 1.1;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  transition: color 0.3s var(--ease), transform 0.3s var(--ease);
}
.setlist__song:hover .setlist__title { color: var(--accent); transform: translateX(8px); }
.setlist__song:hover .setlist__no { color: var(--accent); }
.setlist__song:active .setlist__title { transform: translateX(12px); }
.setlist__song[aria-current="true"] .setlist__no,
.setlist__song[aria-current="true"] .setlist__title { color: var(--accent); }
.setlist__list li:has([aria-current="true"])::after { transform: scaleX(1); }

/* ---- 10c. Tracks ----------------------------------------------------------- */
/* Self-hosted recordings, in the row language of the videos and the setlist:
   play square, title in the display face, composer and time in meta. The
   whole row is the button (stretched ::after). The current row turns red,
   its underline is the progress, and the range input on that line seeks
   (js/site.js). Nothing loads before the first click (preload none). */
.tracks { list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--line); }
.track {
  position: relative;
  display: grid;
  grid-template-columns: 2.25rem minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.35rem 1rem;
  padding-block: 0.95rem;
  border-bottom: 1px solid var(--line);
}
/* Progress, inset by half the seek handle so its end meets the handle. */
.track::after {
  content: "";
  position: absolute;
  left: 0.35rem;
  right: 0.35rem;
  bottom: -1px;
  height: 1px;
  background: var(--accent);
  transform: scaleX(var(--p, 0));
  transform-origin: left center;
  pointer-events: none;
}
.track__btn {
  grid-column: 1 / 3;
  grid-row: 1;
  display: grid;
  grid-template-columns: subgrid;
  align-items: center;
  padding: 0;
  text-align: left;
}
.track__btn::after { content: ""; position: absolute; inset: 0; }
.track__btn:focus-visible { outline: none; }
.track:has(.track__btn:focus-visible) { outline: 2px solid var(--accent); outline-offset: 4px; }
.track__btn:active .track__icon { transform: translateY(1px); }

.track__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid var(--line-strong);
  color: var(--ink);
  transition: background-color 0.3s var(--ease), border-color 0.3s var(--ease), color 0.3s var(--ease), transform 0.3s var(--ease);
}
.track__icon svg { width: 0.8rem; height: 0.8rem; fill: currentColor; }
.track__pause { display: none; }
.track.is-playing .track__play { display: none; }
.track.is-playing .track__pause { display: block; }

.track__title {
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: 112%;
  font-size: clamp(1.05rem, 1.5vw, 1.35rem);
  line-height: 1;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  transition: color 0.3s var(--ease), transform 0.3s var(--ease);
}
.track__by {
  grid-column: 2;
  grid-row: 2;
  font-size: var(--text-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  transition: transform 0.3s var(--ease);
}
.track__time {
  grid-column: 3;
  grid-row: 1;
  justify-self: end;
  font-size: var(--text-label);
  font-weight: 700;
  letter-spacing: 0.1em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--ink-muted);
}
.track .bars {
  grid-column: 3;
  grid-row: 2;
  justify-self: end;
  height: 0.8rem;
  opacity: 0;
  transition: opacity 0.3s var(--ease);
}

.track:hover .track__icon { background: var(--ink); border-color: var(--ink); color: var(--bg); }
.track:hover .track__title { color: var(--accent); }
.track:hover .track__title,
.track:hover .track__by { transform: translateX(8px); }

.track.is-current .track__title { color: var(--accent); }
.track.is-current .track__icon { border-color: var(--accent); color: var(--accent); }
.track.is-current .track__time { color: var(--ink); }
.track.is-playing .track__icon,
.track.is-playing:hover .track__icon { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.track.is-current .bars { opacity: 1; }
.track.is-playing .bars span { animation: bar 0.9s var(--ease) infinite alternate; }
.track.is-playing .bars span:nth-child(1) { animation-duration: 0.72s; }
.track.is-playing .bars span:nth-child(2) { animation-duration: 0.98s; animation-delay: -0.2s; }
.track.is-playing .bars span:nth-child(3) { animation-duration: 0.84s; animation-delay: -0.4s; }
.track.is-playing .bars span:nth-child(4) { animation-duration: 1.06s; animation-delay: -0.1s; }
.track.is-playing .bars span:nth-child(5) { animation-duration: 0.78s; animation-delay: -0.3s; }

/* Seek: a range input lying on the row's bottom line, current row only.
   Its track is invisible; the handle is a small red square. */
.track__seek {
  position: absolute;
  z-index: 2;
  left: 0;
  right: 0;
  bottom: -0.6rem;
  width: 100%;
  height: 1.2rem;
  margin: 0;
  padding: 0;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
  visibility: hidden;
}
.track.is-current .track__seek { visibility: visible; }
.track__seek:focus-visible { outline: none; }
.track__seek::-webkit-slider-runnable-track { height: 1.2rem; background: transparent; border: 0; }
.track__seek::-moz-range-track { height: 1.2rem; background: transparent; border: 0; }
.track__seek::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 0.7rem;
  height: 0.7rem;
  margin-top: 0.25rem;
  border: 0;
  border-radius: 0;
  background: var(--accent);
  transition: transform 0.3s var(--ease);
}
.track__seek::-moz-range-thumb { width: 0.7rem; height: 0.7rem; border: 0; border-radius: 0; background: var(--accent); }
.track__seek:hover::-webkit-slider-thumb { transform: scale(1.3); }
.track__seek:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--ink); outline-offset: 2px; }
.track__seek:focus-visible::-moz-range-thumb { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Visually hidden, still read: composer prefixes and similar. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .track.is-playing .bars span { animation: none; transform: scaleY(0.7); }
  .track__title,
  .track__by,
  .track__icon { transition: none; }
}

/* ---- 11. Contact block extras ------------------------------------------- */
/* Two halves: the ask on the left, the address and number set large on the
   right, bottom-aligned. Phones stack them. */
.contact-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(2.5rem, 6vw, 4.5rem);
}
/* Wide enough that no uppercase word is clipped by the headline mask. */
.contact-head { max-width: min(100%, 46rem); }
.contact-head .display-lg { margin-top: 1rem; text-wrap: balance; }
.contact-head .lead { margin-top: 1.5rem; }
.contact-meta {
  margin-top: clamp(2.5rem, 6vw, 4.5rem);
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
.contact-meta a { justify-self: start; transition: color 0.3s var(--ease); }
.contact-meta a:hover { color: var(--accent); }
.contact-meta a:active { transform: translateY(1px); }

.contact-lines { list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--line); }
.contact-lines li { border-bottom: 1px solid var(--line); }
.contact-line {
  display: grid;
  gap: 0.6rem;
  padding-block: 1.5rem;
  transition: color 0.3s var(--ease);
}
.contact-line__label { display: inline-flex; align-items: center; gap: 0.6rem; }
.contact-line__label svg { width: 1.1rem; height: 1.1rem; }
.contact-line__value {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3em;
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: 112%;
  font-size: clamp(1.1rem, 5.6vw, 2.5rem);
  line-height: 1;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}
.contact-line__value svg {
  flex: none;
  width: 0.6em;
  height: 0.6em;
  transition: transform 0.3s var(--ease);
}
.contact-line:hover { color: var(--accent); }
.contact-line:hover .contact-line__value svg { transform: translate(3px, -3px); }
.contact-line:active { transform: translateY(1px); }

/* Desktop: eyebrow and headline run across both halves; under them the
   lead sits left and the contact lines right, bottom-aligned. */
@media (min-width: 901px) {
  .contact-split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: clamp(2rem, 5vw, 5rem);
    row-gap: 0;
  }
  .contact-head { display: contents; }
  .contact-head > .bracket { grid-column: 1 / -1; justify-self: start; }
  .contact-head > .display-lg { grid-column: 1 / -1; }
  .contact-head > .lead { grid-column: 1; grid-row: 3; align-self: start; }
  .contact-lines { grid-column: 2; grid-row: 3; align-self: start; margin-top: 1.5rem; }
  .contact-line__value { font-size: clamp(1.4rem, 2.7vw, 2.3rem); }
}

/* ---- 11b. Looping clip ---------------------------------------------------- */
/* A muted 8s clip in a statement figure. js/site.js plays it while it is on
   screen; the bracket button pauses it for good (WCAG 2.2.2). */
/* Top-right: the headline overlaps the clip's bottom edge on phones.
   .media > beats button.bracket's position: relative. */
.media > .media__toggle {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 6;
  margin: 0;
  padding: 0.75rem;
  background: var(--bg);
  color: var(--ink);
}
.no-js .media__toggle { display: none; }
/* 16:9 clip in a 4:3 frame: keep Markus (right of centre) in shot. */
[data-loop] video { object-position: 78% 50%; }

/* ---- 12. Hero variants --------------------------------------------------- */
.hero--sm { min-height: 80dvh; }
.hero--sm .hero__stage { align-items: end; }
.hero__copy .lead { margin-top: 1.75rem; }
.hero { grid-template-columns: minmax(0, 1fr); }

/* ---- 13. Statement / section helpers ------------------------------------ */
/* A five-line paragraph is never right-aligned — the block moves, the text
   stays left-read. */
.statement__copy .body { margin-top: 1.5rem; max-width: 46ch; text-align: left; }
.statement--flip .statement__copy .body { margin-left: auto; }
.statement__copy .lineup { text-align: left; }
.statement--flip .statement__copy .lineup { margin-left: auto; width: 100%; }
.section-stack > * + * { margin-top: clamp(2.5rem, 6vw, 4.5rem); }

/* A statement's second row (Wylroots Garden): the line-up under the copy,
   the recordings under the photo. Two equal halves like the statement, so
   the list starts and ends on the photo's edges; the copy side keeps a
   gutter by margin, which keeps its hairlines inside. The second line-up's
   statement follows a row and gets the larger gap. */
.statement-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
}
.statement-row .lineup { margin-top: 0; }
.statement-row > :first-child { margin-right: clamp(2rem, 5vw, 5rem); }
.statement-row--flip > :first-child { margin-right: 0; }
.statement-row--flip > :last-child {
  margin-left: clamp(2rem, 5vw, 5rem);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}
.statement-row--flip .lineup { width: 100%; }
.statement-row--flip .statement__links { justify-content: flex-end; }
.section-stack > .statement-row + .statement { margin-top: clamp(5rem, 11vw, 9rem); }

@media (max-width: 900px) {
  .statement-row { grid-template-columns: minmax(0, 1fr); row-gap: 2.5rem; }
  .statement-row > :first-child,
  .statement-row--flip > :last-child { margin: 0; }
  .statement-row--flip > :last-child { align-items: stretch; }
  .statement-row--flip .statement__links { justify-content: flex-start; }
}

/* ---- 14. Menu / footer ---------------------------------------------------- */
.menu__links a[aria-current="page"] { color: var(--accent); }
/* Same as the hero: the foot row's nowrap bracket must not widen the grid. */
.menu { grid-template-columns: minmax(0, 1fr); }
.footer__legal { display: flex; gap: 1.25rem; flex-wrap: wrap; justify-content: flex-end; }
.footer__legal a { transition: color 0.3s var(--ease); }
.footer__legal a:hover { color: var(--accent); }
.footer__legal a:active { transform: translateY(1px); }
.footer__address { display: block; margin-top: 0.5rem; }
.footer__inner { align-items: end; }

/* ---- 14b. Language switch ------------------------------------------------ */
/* Nav: the language link rides in the same right-hand cluster as the menu
   button and borrows its mix-blend-mode so it reads over any photo. It stays
   in currentColor — no accent fill up here. */
.nav__actions { display: flex; align-items: center; gap: 1.5rem; }
.nav__lang {
  pointer-events: auto;
  color: var(--ink);
  mix-blend-mode: difference;
  opacity: 0.6;
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.nav__lang:hover,
.nav__lang:focus-visible { opacity: 1; }
.nav__lang:active { transform: translateY(1px); }

/* Menu foot + footer: DE — EN, current language in --ink, the other muted. */
.lang-pair { display: inline-flex; align-items: baseline; gap: 0.5rem; white-space: nowrap; }
.lang-pair__now { color: var(--ink); }
.lang-pair__sep { color: var(--ink-faint); }
a.lang-pair__link,
.social__link {
  display: inline-block;
  color: var(--ink-muted);
  transition: color 0.3s var(--ease), transform 0.3s var(--ease);
}
a.lang-pair__link:hover,
a.lang-pair__link:focus-visible,
.social__link:hover,
.social__link:focus-visible { color: var(--accent); }

/* Small text links: an invisible hit area grows the tap target to 44px
   without touching layout or alignment. */
.nav__lang,
a.lang-pair__link,
.social__link,
.footer__legal a,
.contact-meta a { position: relative; }
.nav__lang::after,
a.lang-pair__link::after,
.social__link::after,
.footer__legal a::after,
.contact-meta a::after {
  content: "";
  position: absolute;
  inset: -0.875rem 0;
}
/* EN / DE are two letters wide: widen their hit area to 44px as well. */
.nav__lang::after,
a.lang-pair__link::after { inset: -0.875rem -0.8rem; }
/* The nav brand is 27px tall: same invisible hit area, grown to 44px. */
.nav__brand { position: relative; }
.nav__brand::after { content: ""; position: absolute; inset: -0.55rem 0; }
a.lang-pair__link:active,
.social__link:active { transform: translateY(1px); }

/* English fallback for the generated gig list. */
.gigs:lang(en):empty::after { content: "No dates announced. Enquiries welcome."; }

/* ---- 14c. Nav state + logo motion ---------------------------------------- */
/* Nav mark: js/site.js feeds one --eq-n per bar from scroll velocity. At rest
   every value is 1 and the mark is the frozen web arrangement. */
.nav__brand .logo__bar { transition: transform 0.16s linear; }
.nav__brand .logo__bar:nth-child(1) { transform: scaleY(var(--eq-1, 1)); }
.nav__brand .logo__bar:nth-child(2) { transform: scaleY(var(--eq-2, 1)); }
.nav__brand .logo__bar:nth-child(3) { transform: scaleY(var(--eq-3, 1)); }
.nav__brand .logo__bar:nth-child(4) { transform: scaleY(var(--eq-4, 1)); }
.nav__brand .logo__bar:nth-child(5) { transform: scaleY(var(--eq-5, 1)); }
.nav__brand .logo__bar:nth-child(6) { transform: scaleY(var(--eq-6, 1)); }

/* Footer mark: while the footer is in view the bars breathe like a meter.
   Each bar has its own period and depth; the class comes and goes with the
   IntersectionObserver, so nothing runs off-screen. */
@keyframes logo-eq {
  from { transform: scaleY(1); }
  to { transform: scaleY(var(--dip, 0.6)); }
}
[data-logo-live].is-live .logo__bar { animation: logo-eq 1s var(--ease) infinite alternate; }
[data-logo-live].is-live .logo__bar:nth-child(1) { --dip: 0.62; animation-duration: 1.15s; }
[data-logo-live].is-live .logo__bar:nth-child(2) { --dip: 0.55; animation-duration: 0.9s; animation-delay: -0.35s; }
[data-logo-live].is-live .logo__bar:nth-child(3) { --dip: 0.7; animation-duration: 1.3s; animation-delay: -0.6s; }
[data-logo-live].is-live .logo__bar:nth-child(4) { --dip: 0.58; animation-duration: 1.05s; animation-delay: -0.2s; }
[data-logo-live].is-live .logo__bar:nth-child(5) { --dip: 0.66; animation-duration: 0.95s; animation-delay: -0.5s; }
[data-logo-live].is-live .logo__bar:nth-child(6) { --dip: 0.5; animation-duration: 1.2s; animation-delay: -0.8s; }

/* ---- 14d. Strings ----------------------------------------------------------- */
/* Six hairlines, thin to thick, full width: the six strings from the mark,
   laid flat. Each is plucked once (a decaying vertical wobble, staggered
   like a downstroke) whenever the band scrolls into view. Two placements:
   the band under the hero (replaces the old marquee) and the footer. */
.strings {
  display: grid;
  gap: clamp(0.55rem, 1.2vw, 0.9rem);
}
.strings--band { padding-block: clamp(2.5rem, 6vw, 5rem); }
/* The band already holds the gap under the hero; the next section only
   adds a short lead-in instead of a full section top. */
.strings--band + .section { padding-top: clamp(1.5rem, 3vw, 3rem); }
.strings--footer {
  padding-block: clamp(1.5rem, 4vw, 3rem) 0;
  border-top: 1px solid var(--line);
}
.strings span {
  display: block;
  height: 1px;
  background: var(--ink);
  opacity: 0.22;
  transform-origin: center;
  transform: translateY(0);
}
.strings span:nth-child(2) { height: 1px; opacity: 0.28; }
.strings span:nth-child(3) { height: 2px; opacity: 0.3; }
.strings span:nth-child(4) { height: 2px; opacity: 0.36; }
.strings span:nth-child(5) { height: 3px; opacity: 0.42; }
.strings span:nth-child(6) { height: 3px; opacity: 0.5; background: var(--accent); }
@keyframes pluck {
  0%   { transform: translateY(0); }
  12%  { transform: translateY(-5px); }
  28%  { transform: translateY(4px); }
  44%  { transform: translateY(-2.6px); }
  60%  { transform: translateY(1.6px); }
  76%  { transform: translateY(-0.8px); }
  100% { transform: translateY(0); }
}
.strings.is-strummed span { animation: pluck 1.1s cubic-bezier(0.2, 0.7, 0.3, 1) both; }
.strings.is-strummed span:nth-child(1) { animation-delay: 0ms; }
.strings.is-strummed span:nth-child(2) { animation-delay: 55ms; }
.strings.is-strummed span:nth-child(3) { animation-delay: 110ms; }
.strings.is-strummed span:nth-child(4) { animation-delay: 165ms; }
.strings.is-strummed span:nth-child(5) { animation-delay: 220ms; }
.strings.is-strummed span:nth-child(6) { animation-delay: 275ms; }
/* Footer mark: larger than the 160px default, so the name closes the page. */
.footer__logo .logo { width: clamp(160px, 17vw, 240px); }
/* The extra 1.2rem keeps the gap the © line used to fill. */
.footer__social { margin-top: 1.7rem; }

/* ---- 14e. Skip link ------------------------------------------------------ */
/* Off-canvas until it has keyboard focus, then a plain accent chip under the
   nav. Sits above the nav so it is never hidden by the solid bar. */
.skip {
  position: fixed;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 70;
  padding: 0.75rem 0.9rem;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: var(--text-label);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0;
  transform: translateY(-150%);
  transition: transform 0.3s var(--ease), opacity 0.3s var(--ease);
}
.skip:focus-visible { opacity: 1; transform: none; outline-color: var(--ink); }

/* ---- 14e2. Statement link row ------------------------------------------- */
/* Two brackets side by side under a statement (trio site + booking).
   Flipped statements push the row to the right like the rest of the copy. */
.statement__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  margin-top: 1.5rem;
}
.statement__links .bracket { margin-top: calc(var(--hit, 0px) * -1); }
.statement--flip .statement__links { justify-content: flex-end; }

/* ---- 14f. Legal pages ---------------------------------------------------- */
/* Impressum / legal notice: no hero, so the first section clears the fixed
   nav itself. Rows reuse the ledger. */
.legal { padding-top: calc(var(--nav-h) + var(--section-y)); }
.legal .ledger__text { line-height: 1.7; }
.legal .ledger__text a { color: var(--ink); transition: color 0.3s var(--ease); }
.legal .ledger__text a:hover,
.legal .ledger__text a:focus-visible { color: var(--accent); }
.legal .ledger__text a:active { transform: translateY(1px); }

/* ---- 14g. Playable strings ---------------------------------------------- */
/* js/site.js plucks a single string when the pointer crosses it (mouse) or
   taps near it (touch). --amp scales with how fast the pointer moved, so a
   slow drag is a soft pick and a fast swipe is a hard strum. The brief
   scaleY reads as the string blurring while it rings. The doubled selector
   beats the stagger delays of the scroll-in strum above. */
@keyframes pluck-amp {
  0%   { transform: translateY(0) scaleY(1); }
  10%  { transform: translateY(calc(var(--amp, 1) * -6px)) scaleY(2.2); }
  24%  { transform: translateY(calc(var(--amp, 1) * 5px)) scaleY(1.8); }
  40%  { transform: translateY(calc(var(--amp, 1) * -3.2px)) scaleY(1.5); }
  56%  { transform: translateY(calc(var(--amp, 1) * 2px)) scaleY(1.25); }
  74%  { transform: translateY(calc(var(--amp, 1) * -1px)) scaleY(1.1); }
  100% { transform: translateY(0) scaleY(1); }
}
.strings span.is-plucked,
.strings.is-strummed span.is-plucked {
  animation: pluck-amp 0.95s cubic-bezier(0.2, 0.7, 0.3, 1) both;
  animation-delay: 0ms;
}

/* ---- 14h. Hero name — frets under the pointer ---------------------------- */
/* Once the letters have risen, the one under a mouse lifts and turns red and
   its neighbours follow halfway, like a string pressed onto a fret. Uses the
   individual `translate` property so the rise transition on `transform`
   (and its per-letter delay) is never touched. Hover devices only. */
@media (hover: hover) and (pointer: fine) {
  .is-split.is-in .char {
    transition:
      transform 0.9s var(--ease) calc(var(--i, 0) * 35ms),
      translate 0.55s var(--ease-spring) 0s,
      color 0.3s var(--ease) 0s;
  }
  .is-split.is-in .char:hover {
    translate: 0 -0.09em;
    color: var(--accent);
    transition-duration: 0.9s, 0.2s, 0.15s;
  }
  .is-split.is-in .char:hover + .char,
  .is-split.is-in .char:has(+ .char:hover) { translate: 0 -0.04em; }
}

/* ---- 14i. Page transitions ----------------------------------------------- */
/* Same-origin page loads cross-fade instead of cutting to black. Opacity
   only, so the fixed nav (identical on both pages) stays put. Browsers
   without cross-document view transitions just load the page as before. */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: vt-out 0.22s var(--ease-in) both; }
::view-transition-new(root) { animation: vt-in 0.45s var(--ease) 0.08s both; }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; } }

/* ---- 15. Responsive ------------------------------------------------------ */
@media (max-width: 1024px) {
  .videos { grid-template-columns: 1fr; }
  .videos__aside { position: static; }
  .record__head { grid-template-columns: 6.5rem minmax(0, 1fr) auto; }
}

@media (max-width: 900px) {
  .head { flex-direction: column; align-items: flex-start; gap: 1rem; }
  .gig:hover > *,
  .ledger__row:hover > *,
  .record:hover .record__head > *,
  .videos__row:hover > * { transform: none; }
  .hero__stage { grid-template-columns: minmax(0, 1fr); }
  .statement--flip .statement__copy .body { margin-left: 0; }
}

@media (max-width: 768px) {
  .record__head { grid-template-columns: minmax(0, 1fr); row-gap: 0.75rem; padding-block: 1.5rem; }
  .record__actions { flex-direction: row; align-items: center; justify-content: flex-start; gap: 1.25rem; }
  .videos__btn,
  .setlist__song { grid-template-columns: 2rem minmax(0, 1fr) auto; column-gap: 0.75rem; }
  .videos__row { grid-template-columns: 1fr; row-gap: 0.25rem; padding-bottom: 0.9rem; }
  .videos__ext { padding-left: 2.75rem; }
  .lineup li { flex-direction: column; gap: 0.25rem; }
  .gallery { grid-template-columns: minmax(0, 1fr); grid-auto-rows: auto; }
  .gallery__item,
  .gallery__item--wide { grid-column: auto; aspect-ratio: 3 / 2; }
  .footer__legal { justify-content: flex-start; }
}

/* ---- 16. Reduced motion --------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .intro { display: none !important; }
  .progress { display: none; }
  .is-split .char,
  .rise__inner,
  .word { transform: none; opacity: 1; transition: none; }
  .reveal-cover .cover { display: none; }
  .reveal-cover img,
  .reveal-cover.is-in img { transform: none; }
  .marquee-nudge { transform: none; }
  .videos__btn[aria-current="true"] .bars span { animation: none; transform: scaleY(0.7); }
  .gig > *, .ledger__row > *, .ledger__row > .ledger__num, .gig > .gig__date, .gig__year,
  .record__head > *, .videos__row > * { transition: none; }
  .nav__brand .logo__bar { transform: none; transition: none; }
  [data-logo-live].is-live .logo__bar { animation: none; }
  .strings.is-strummed span,
  .strings span.is-plucked { animation: none; }
  .setlist__title,
  .setlist__list li::after { transition: none; }
  .setlist__song:hover .setlist__title,
  .setlist__song:active .setlist__title { transform: none; }
  .is-split.is-in .char:hover,
  .is-split.is-in .char:hover + .char,
  .is-split.is-in .char:has(+ .char:hover) { translate: none; }
  @view-transition { navigation: none; }
}
