/* ----------------------------------------------------------------- fonts */
/* Self-hosted woff2 from Google Fonts. Latin + latin-ext only; the
   unicode-range split means latin-ext is fetched only if a page needs it. */

/* Space Grotesk — headings, nav and UI labels. Variable weight axis, 300-700. */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/assets/fonts/space-grotesk-var-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/assets/fonts/space-grotesk-var-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Staatliches — titles and navigation. A single 400 weight, and the design is
   caps-only, so anything set in it renders as capitals whatever the source case. */
@font-face {
  font-family: 'Staatliches';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/staatliches-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Staatliches';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/staatliches-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---------------------------------------------------------------- tokens */

:root {
  /* Very dark, with a faint blue-purple cast rather than a flat #000. */
  --bg: #131319;
  --bg-raised: #1c1c28;
  --line: #262633;
  --text: #ececef;
  --muted: #9a9aa6;
  --accent: #4d9fff;        /* electric blue — links, rules, UI */
  --accent-hover: #85bcff;
  /* The accents are all light, so filled controls put near-black text on them
     rather than white. */
  --accent-strong: #4d9fff;
  --accent-text: #12121b;

  /* Section headings cycle through these three. */
  --title-1: #4d9fff;      /* blue */
  --title-2: #9ae6b8;      /* mint green */
  --title-3: #fbc7c4;      /* blush */

  --wrap: 74rem;
  --gap: 1.5rem;
  --radius: 2px;

  --font: 'Inter', ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-display: 'Staatliches', 'Space Grotesk', ui-sans-serif, system-ui, "Segoe UI", Roboto, sans-serif;
  --font-ui: var(--font-display);
  /* Kept for the few places that need mixed case and real weights. */
  --font-grotesk: 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* --------------------------------------------------------------- baseline */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }
body { overflow-x: clip; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: clamp(1rem, 0.97rem + 0.15vw, 1.0625rem);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }

a { color: inherit; text-decoration-color: var(--accent); text-underline-offset: 0.2em; }
a:hover { text-decoration-thickness: 2px; text-decoration-color: var(--accent-hover); }

/* Space Grotesk carries a real 600, so headings can take weight without the
   browser synthesising a fake bold. Slight negative tracking keeps the big
   sizes from feeling loose. */
/* Staatliches ships a single 400 weight, so headings must ask for 400 or the
   browser synthesises a fake bold. Condensed caps also need a little positive
   tracking rather than the negative tracking a grotesque wants. */
h1, h2, h3 { font-family: var(--font-display); font-weight: 400; line-height: 1.05; letter-spacing: 0.015em; margin: 0 0 0.5em; }
h1 { font-size: clamp(2rem, 1.5rem + 2.2vw, 3rem); }
h2 { font-size: clamp(1.35rem, 1.2rem + 0.8vw, 1.75rem); }

p, ul, ol { margin: 0 0 1.1em; }

blockquote {
  margin: 1.5em 0;
  padding-left: 1.1rem;
  border-left: 3px solid var(--accent);
  color: var(--muted);
  font-style: italic;
}

code { font-size: 0.9em; background: var(--bg-raised); padding: 0.15em 0.4em; border-radius: 4px; }

.muted, .small { color: var(--muted); }
.small { font-size: 0.875rem; }

.wrap { width: min(100% - 2.5rem, var(--wrap)); margin-inline: auto; padding-inline: 0.75rem;}

/* Off-screen but readable by search engines and screen readers alike. Not
   display:none, which would remove it from the accessibility tree and from the
   text Google counts. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: -9999px;
  background: var(--accent);
  color: var(--accent-text);
  padding: 0.6rem 1rem;
  z-index: 10;
}
.skip-link:focus { left: 1rem; top: 1rem; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ----------------------------------------------------------------- header */

.site-header {
  position: sticky;
  top: 0;
  z-index: 5;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px);
}

/* Equal-width side columns keep the logo optically centred even though the two
   nav groups hold different numbers of links. */
.header-inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 0.75rem 1.5rem;
  padding-block: 0.9rem;
}

.brand {
  display: block;
  text-decoration: none;
  line-height: 0;
}

/* Three-line wordmark, so it sits taller than a text brand would.
   aspect-ratio holds the box before the PNG lands, since the markup no longer
   carries a height attribute. */
.brand img {
  height: clamp(2.75rem, 4.5vw, 3.5rem);
  width: auto;
  aspect-ratio: 223 / 168;
}

.nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--font-ui);
  font-size: 1.0625rem;
  font-weight: 400;
  letter-spacing: 0.06em;
}
/* space-evenly spreads each group across its half, so the links sit at even
   intervals either side of the logo rather than bunching at the edges. */
.nav-left, .nav-right { justify-content: space-evenly; }

.nav a { text-decoration: none; color: var(--muted); }
.nav a:hover { color: var(--text); }
.nav a[aria-current="page"] { color: var(--text); box-shadow: inset 0 -2px 0 var(--accent); }

/* Below the point where three columns stop fitting, stack: logo first, then
   both nav groups centred on one row. */
@media (max-width: 46rem) {
  .header-inner {
    grid-template-columns: auto auto;
    justify-content: center;
    row-gap: 0.5rem;
  }
  .brand {
    grid-column: 1 / -1;
    grid-row: 1;
    margin-inline: auto;
  }
  .brand img { height: 3rem; }
  .nav-left { grid-column: 1; grid-row: 2; }
  .nav-right { grid-column: 2; grid-row: 2; }
}

@media (max-width: 30rem) {
  .brand img { height: 2.5rem; }
  .nav { gap: 0.25rem 1rem; font-size: 0.8125rem; }
}

/* ------------------------------------------------------------------- main */

main { padding-block: clamp(2rem, 5vw, 3.5rem) 4rem; }

.page-head { margin-bottom: 2.5rem; }
.lede { color: var(--muted); font-size: 1.1rem; max-width: 46ch; margin: 0; }

/* ------------------------------------------------------------------- hero */

/* Escapes the centred column and runs the full viewport width. Works because
   the parent is centred: half the parent's width back, half the viewport out. */
.bleed {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
}

/* Art and type share one grid cell, so the wordmark sits on the drawing rather
   than under it. The drawing is white line art on transparency, so it needs no
   frame — only a scrim so the type stays readable where the lines are dense. */
.hero {
  position: relative;
  isolation: isolate;
  display: grid;
  /* Centred in a box that is padded at the bottom, which lands the drawing above
     the true vertical middle without pinning it to the top. */
  align-items: center;
  min-height: 95vh;
  /* Cancels main's top padding and then some, so the drawing sits close under
     the header instead of floating below it. */
  margin-top: calc((clamp(1rem, 5vw, 2rem) + 1.25rem) * -1);
}

/* Falling ash over the hero, drawn on a canvas by /assets/js/ash.js.

   image-rendering: pixelated is the whole point: the canvas backing store is
   sized in CSS pixels, so on a retina screen the browser doubles each fleck
   into a hard 2x2 block rather than resampling it into a soft dot.

   No z-index — ash, art and type share one grid cell and paint in DOM order,
   so the canvas sits behind the drawing without dropping below the scrim. */
/* Absolutely positioned, NOT a grid item, and this matters.
   As a grid item its height: 100% resolved against an auto-height row, so it
   fell back to the canvas's intrinsic aspect ratio — which ash.js sets from the
   element's own measured box. That closed a loop: box height -> canvas
   width/height attributes -> intrinsic aspect -> row height -> box height.
   Resizing narrow then wide again fed a tall aspect into a wide box and the hero
   grew far past the viewport. Out of flow, it cannot influence layout at all. */
.hero-ash {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
  pointer-events: none;
}

.hero-art {
  grid-area: 1 / 1;
  align-self: center;
  justify-self: center;
  position: relative;
  z-index: 1;

  /* Bounded on both axes with auto width and height, so the drawing scales down
     to whichever limit bites first and never overflows a narrow viewport.
     Sizing off height alone made it 1.32x as wide as it was tall, which ran
     past the screen edge and got silently cropped by overflow-x: clip.
     svh, not vh, so mobile browser chrome cannot push it over either. */
  width: auto;
  height: auto;
  max-width: min(100%, 60rem);
  max-height: calc(100svh - 12rem);
}

.hero::after {
  content: "";
  grid-area: 1 / 1;
  z-index: -1;
  background: linear-gradient(to top, var(--bg) 2%, color-mix(in srgb, var(--bg) 70%, transparent) 38%, transparent 78%);
}

.hero-type {
  grid-area: 1 / 1;
  align-self: end;
  /* Outside the padding the drawing is centred within. */
  margin-bottom: -14svh;
  width: min(100% - 2.5rem, var(--wrap));
  margin-inline: auto;
  padding-bottom: clamp(1.5rem, 5vw, 3.5rem);
}

/* The page's h1: huge, tight, and deliberately overlapping the drawing. */
.hero-title {
  font-size: clamp(3rem, 12vw, 9.5rem);
  font-weight: 700;
  line-height: 0.86;
  letter-spacing: -0.04em;
  text-transform: uppercase;
  margin: 0 0 clamp(0.75rem, 2vw, 1.25rem);
}

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  margin: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--muted);
}
.hero-meta-next { color: var(--accent); }

@media (max-width: 46rem) {
  .hero { min-height: min(62svh, 28rem); }
  /* The header stacks below this width, so leave more room above the drawing.
     object-position did nothing here: there is no object-fit to position. */
  .hero-art { max-height: calc(100svh - 10rem); }
}

.eyebrow { color: var(--accent); text-transform: uppercase; letter-spacing: 0.14em; font-size: 0.8125rem; margin: 0 0 0.6rem; }

.btn {
  display: inline-block;
  padding: 0.7rem 1.4rem;
  border-radius: var(--radius);
  background: var(--accent-strong);
  color: var(--accent-text);
  border: 1px solid var(--accent-strong);
  font-weight: 600;
  text-decoration: none;
}
.btn:hover { filter: brightness(1.1); }
.btn-ghost { background: transparent; color: var(--text); border-color: var(--line); }
.btn-ghost:hover { border-color: var(--text); filter: none; }

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

/* Each section sits on a faintly tinted block, tinted with an accent other than
   its own title colour so the two never double up. The tint lives on a
   pseudo-element so it can be rotated a fraction of a degree while the text
   stays perfectly level — skewing the type itself would just look broken. */
.panel {
  position: relative;
  isolation: isolate;
  margin-top: 3rem;
  /* Vertical padding kept tighter than horizontal: the tinted block only needs
     enough headroom to read as a block, and the section's own margin-top already
     provides the separation between panels. */
  padding: clamp(0.9rem, 1.6vw, 1.35rem) clamp(0.85rem, 1.6vw, 1.15rem);

  --panel-tint: var(--title-2);
  --panel-tilt: -0.2deg;
  --panel-tilt-alt: 0.5deg;
}

.panel::before {
  content: "";
  position: absolute;
  inset: -0.7rem -1.1rem;
  z-index: -1;
  border-radius: var(--radius);
  /* The wash is the tint at 11%; the two edges are the same colour at full
     strength, so each section is keyed to one accent. */
  background: color-mix(in srgb, var(--panel-tint) 5%, var(--bg));
  /* border: 5px solid var(--panel-tint) ; */
  transform: rotate(var(--panel-tilt));
  box-shadow: 4px 4px rgba(0,0,0,0.3);
  border-top: 1px solid rgba(255,255,255,0.1);

  /* The animation is always attached but held paused, and only runs while the
     section is hovered. Pausing rather than removing it means the block stops
     where it is and picks up from there, instead of snapping back to its
     resting angle the moment the pointer leaves. */
  animation: panel-drift 3.4s ease-in-out infinite alternate;
  animation-play-state: paused;
}
.panel:hover::before,
.panel:focus-within::before {
  /* animation-play-state: running; */
}

/* Each block swings roughly four degrees, through level and out the other side,
   so hovering visibly pulls the section in and out of alignment. */

/* Alternate the lean so the page zigzags rather than drifting one way. */
.panel:nth-of-type(even) { --panel-tilt: .6deg; --panel-tilt-alt: -0.2deg; }
.panel:nth-of-type(3n) { --panel-tilt: -0.4deg; --panel-tilt-alt: 0.8deg; }

/* Phone screens have no room to spare either side, and the tinted block reads
   fine with the text much closer to its edge. */
@media (max-width: 40rem) {
  .panel { padding: 0.7rem 0.6rem; }
  .panel::before {
    /* The block sits less far outside the panel too, so the tint does not eat
       into the page margin. */
    inset: -0.4rem -0.5rem;
    /* Square on a phone. At this width a fraction of a degree across a narrow
       block reads as a mistake rather than as a decision, and the corners eat
       into what little margin there is. */
    transform: none;
    animation: none;
  }
}

/* Drifting blocks behind text are exactly what reduced-motion is for. */
@media (prefers-reduced-motion: reduce) {
  .panel::before { animation: none; }
}

/* One accent per section, driving both its border and its heading. nth-of-type
   counts the <section> elements, so a section hidden by an {{#if}} just shifts
   the cycle; only the alternation matters, not which section gets which. */
main > section:nth-of-type(3n + 2) { --panel-tint: var(--title-1); }
main > section:nth-of-type(3n + 1) { --panel-tint: var(--title-3); }
.duo > section:nth-of-type(1) { --panel-tint: var(--title-2); }
.duo > section:nth-of-type(2) { --panel-tint: var(--title-3); }

/* Section headings read as sentences ("Our next shows are"), so they work
   better as big italic display headings than as small tracked caps. Space
   Grotesk ships no italic file, so this is a synthetic oblique. */
.panel-title, main h2 {
  font-size: clamp(1.35rem, 1.1rem + 1.3vw, 1.9rem);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  /* Same colour as the container's border, so each section reads as one keyed
     unit. The fallback covers headings outside a .panel, on About and Contact,
     which have no tint of their own. */
  color: var(--panel-tint, var(--title-1));
  margin-bottom: 1rem;
}

/* Shows and the latest release, side by side. The two columns are siblings
   inside a wrapper rather than sections of main, so they need their own step in
   the colour cycle. */
.duo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(2rem, 5vw, 4rem);
  margin-top: clamp(3.5rem, 8vw, 6.5rem);
  /* stretch, so both tinted blocks end up the same height whichever column has
     more in it. */
  align-items: stretch;
}
/* Each column is then a flex column centring its own contents, so a short list
   sits level with the middle of a taller neighbour rather than at its top. */
.duo > .panel {
  margin-top: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Half-width columns are too narrow for the three-across row, so the date takes
   its own line the way it does on a phone. */
.duo .show { grid-template-columns: 1fr auto; }
.duo .show-date { grid-column: 1 / -1; }

@media (max-width: 52rem) {
  .duo { grid-template-columns: minmax(0, 1fr); gap: clamp(2.5rem, 8vw, 4rem); }
}

/* Release names are content, not section labels, so they stay upright. */
.release-title {
  font-size: clamp(1.35rem, 1.2rem + 0.8vw, 1.75rem);
  font-style: normal;
  color: var(--text);
  margin-bottom: 0.2em;
}
.panel-more { margin: 1rem 0 0; font-size: 0.9375rem; }

/* ------------------------------------------------------------------ shows */

.shows { list-style: none; margin: 0; padding: 0; }

/* Rules run to the viewport edge while the row content stays on the measure,
   which is what stops the page reading as one narrow column. */


.show {
  display: grid;
  grid-template-columns: 9.5rem 1fr auto;
  align-items: center;
  gap: 0.35rem 1.25rem;
  padding: 1rem 0.25rem;
}
.show-date { color: var(--muted); font-variant-numeric: tabular-nums; font-size: 0.9375rem; }
.show-time { color: var(--accent); }
.show-note { color: var(--muted); }
.show-cta {
  justify-self: end;
  font-size: 0.875rem;
  font-weight: 600;
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-decoration: none;
  white-space: nowrap;
}
.show-cta:hover { border-color: var(--accent); color: var(--accent); }
.show-cta.is-off { color: var(--muted); border-style: dashed; }
.show-cta.is-off:hover { color: var(--muted); border-color: var(--line); }
.is-sold-out .show-where { opacity: 0.65; }
.shows.is-past .show { padding-block: 0.6rem; color: var(--muted); }

@media (max-width: 40rem) {
  .show { grid-template-columns: 1fr auto; }
  .show-date { grid-column: 1 / -1; }
}

/* ------------------------------------------------------- releases/feature */

/* Single column — the Spotify embed carries the artwork, so there is no cover
   image to sit alongside the text. */
.release, .feature {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(1.25rem, 4vw, 2.5rem);
  align-items: start;
  padding-block: 2.5rem;
  border-top: 1px solid var(--line);
}
.feature { border-top: 0; padding-top: 0; }
.release:first-of-type { border-top: 0; padding-top: 0; }

.release-cover, .feature-cover {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
}
.release-body > p, .feature-body > p { max-width: 52ch; }

.embed { margin: 1.25rem 0; max-width: 34rem; }
.embed iframe { display: block; width: 100%; border: 0; border-radius: var(--radius); }

.tracklist {
  margin: 1.25rem 0 0;
  padding: 0;
  list-style: none;
  counter-reset: track;
  columns: 2;
  column-gap: 2rem;
  max-width: 40rem;
  font-size: 0.9375rem;
}
.tracklist li {
  counter-increment: track;
  break-inside: avoid;
  padding: 0.2rem 0;
  color: var(--muted);
}
.tracklist li::before {
  content: counter(track) ". ";
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.credits { margin-top: 1rem; max-width: 52ch; }

@media (max-width: 34rem) {
  .tracklist { columns: 1; }
}

/* ----------------------------------------------------------------- quotes */

.quotes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
  gap: 1rem;
}
/* No card: a hairline and the type do the work. */
.quote {
  margin: 0;
  padding: 1.1rem 0 0;
  border-top: 1px solid var(--line);
}
.quote p { margin: 0 0 0.7rem; font-size: 1.125rem; line-height: 1.45; color: var(--text); }
.quote cite { font-style: normal; font-size: 0.8125rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); }

@media (max-width: 40rem) {
  .release, .feature { grid-template-columns: 1fr; }
  .release-cover, .feature-cover { max-width: 15rem; }
}

/* ------------------------------------------------------------------ picks */

.picks {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
  gap: 1rem;
}
.pick-group {
  padding: 1.1rem 0 0;
  border-top: 1px solid var(--line);
}
.pick-group-title {
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 600;
  margin: 0 0 0.9rem;
}
.pick-group-note { text-transform: none; letter-spacing: 0; font-weight: 400; }
.pick-group-note::before { content: "· "; }

.pick-list { list-style: none; margin: 0; padding: 0; }
.pick {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.6rem 0;
  border-top: 1px solid var(--line);
}
.pick:first-child { border-top: 0; padding-top: 0; }
.pick:last-child { padding-bottom: 0; }

/* Square, to match the release covers. Optional — no image, no gap. */
.pick-thumb {
  flex: 0 0 auto;
  width: 3rem;
  height: 3rem;
  object-fit: cover;
}
/* min-width:0 so long names wrap instead of forcing the card wider. */
.pick-body { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.pick-name { font-weight: 600; line-height: 1.35; }
.pick-name a { text-decoration: none; }
.pick-name a:hover { color: var(--accent); }
.pick-by { font-size: 0.875rem; color: var(--muted); }
.pick-detail { font-size: 0.9375rem; color: var(--muted); line-height: 1.45; }

.link-row.is-compact { margin-top: 0.45rem; gap: 0.35rem 0.4rem; }
.link-row.is-compact a { padding: 0.2rem 0.6rem; font-size: 0.8125rem; }

/* ------------------------------------------------ tile grid (friends, venues) */

.tile-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
  gap: 0.7rem 1rem;
}
.tile { position: relative; display: flex; flex-direction: column; gap: 0rem; }
.tile-media {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  margin-bottom: 0.3rem;
}
/* A wordmark or product cutout rather than a photo: fit it whole, do not crop. */
.tile-media.is-contain { object-fit: contain; padding: 0.9rem; }

/* No photo: a tile carrying the name rather than an empty hole. */
.tile-media.is-empty {
  display: grid;
  place-items: center;
  padding: 0.5rem;
  text-align: center;
  background: var(--bg-raised);
}
.tile-media-fallback {
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.25;
  color: var(--muted);
  overflow-wrap: anywhere;
}
.tile-name { font-size: 0.9375rem; font-weight: 600; line-height: 1.3; }
.tile-name a { text-decoration: none; }

/* Hover colour cycles across the grid rather than every name going the same
   blue, matching the way the gear overlay and the section titles rotate. */
.tile { --tile-accent: var(--title-1); }
.tile:nth-child(3n + 2) { --tile-accent: var(--title-2); }
.tile:nth-child(3n) { --tile-accent: var(--title-3); }

.tile-name a { transition: color 140ms ease; }
.tile-name a:hover,
.tile-name a:focus-visible { color: var(--tile-accent); }
/* Stretches the name's link over the whole tile, so the picture is clickable. */
.tile-name a::after { content: ""; position: absolute; inset: 0; border-radius: var(--radius); }
.tile-meta { font-size: 0.8125rem; color: var(--muted); }

/* The band: same tile shape as friends and venues, but bigger cells and a
   named-up treatment, since these are the people rather than a list of others.
   Six members, so three across sits evenly on two rows at desktop width. */
.tile-grid.is-band {
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 9.5rem), 1fr));
  gap: 1.25rem 1.25rem;
}
.is-band .tile-name {
  font-size: 1.0625rem;
  margin-top: 0.15rem;
}
.is-band .tile-meta {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  line-height: 1.4;
}
/* Photographs are all live shots at different exposures; a shared duotone-ish
   wash would be a bigger change, so this just takes the edge off the brightest
   and lets the faces read as a set. */
.is-band .tile-media {
  filter: saturate(0.9) contrast(1.02);
}

/* On the Story page the grid follows a two-column block, so it needs the same
   breathing room the panels get on the homepage. */
.story .tile-grid.is-band { margin-bottom: clamp(2.5rem, 6vw, 4rem); }

/* Gear is a long list of small things, so a fixed six across keeps it compact
   instead of letting auto-fill give each item as much room as a band member. */
.tile-grid.is-gear {
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 0.6rem 0.8rem;
}

/* Gear labels sit under the picture by default, and are only lifted into a hover
   overlay on wider screens that actually have a pointer. On a phone there is no
   hover to reveal them with, and a label over a small photo is worse than one
   beneath it. */
.is-gear .tile-name { font-size: 0.8125rem; }
.is-gear .tile-meta { font-size: 0.75rem; }

@media (min-width: 40.0625rem) and (hover: hover) {
  /* Each tile is keyed to one of the three title colours, cycling across the
     grid. */
  .is-gear .tile {
    position: relative;
    overflow: hidden;
    display: block;
    --gear-accent: var(--title-1);
  }
  .is-gear .tile:nth-child(3n + 2) { --gear-accent: var(--title-2); }
  .is-gear .tile:nth-child(3n) { --gear-accent: var(--title-3); }

  /* The wash. Wipes up from the bottom rather than just fading, and sits under
     the label but over the picture. */
  .is-gear .tile::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    background: color-mix(in srgb, var(--gear-accent) 88%, transparent);
    /* 100% alone leaves a 1px sliver of colour showing along the bottom edge:
       the tile's height is fractional in a 1fr grid track, so translating by
       exactly its own height rounds a hair short. The extra 2px clears it at any
       device pixel ratio. */
    transform: translateY(calc(100% + 2px));
    transition: transform 260ms cubic-bezier(0.22, 0.61, 0.36, 1);
  }

  .is-gear .tile-name,
  .is-gear .tile-meta {
    position: absolute;
    z-index: 2;
    left: 0.55rem;
    right: 0.55rem;
    /* The accents are light, so the label goes near-black on top of them. */
    color: var(--accent-text);
    opacity: 0;
    transform: translateY(0.4rem);
    transition: opacity 180ms ease, transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
  }
  .is-gear .tile-name {
    bottom: 1.55rem;
    font-weight: 700;
    line-height: 1.15;
  }
  .is-gear .tile-meta {
    bottom: 0.55rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.625rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: color-mix(in srgb, var(--accent-text) 75%, transparent);
  }
  .is-gear .tile-name a { color: inherit; text-decoration: none; }

  /* focus-within as well as hover, so a keyboard user tabbing to the tile's link
     gets the same reveal rather than an invisible label. */
  .is-gear .tile:hover::before,
  .is-gear .tile:focus-within::before {
    transform: translateY(0);
  }
  .is-gear .tile:hover .tile-name,
  .is-gear .tile:hover .tile-meta,
  .is-gear .tile:focus-within .tile-name,
  .is-gear .tile:focus-within .tile-meta {
    opacity: 1;
    transform: translateY(0);
  }
  /* Label text lags the wash slightly, so it arrives onto the colour. */
  .is-gear .tile:hover .tile-name,
  .is-gear .tile:focus-within .tile-name { transition-delay: 90ms; }
  .is-gear .tile:hover .tile-meta,
  .is-gear .tile:focus-within .tile-meta { transition-delay: 130ms; }

  @media (prefers-reduced-motion: reduce) {
    .is-gear .tile::before,
    .is-gear .tile-name,
    .is-gear .tile-meta { transition-duration: 1ms; transition-delay: 0ms; }
  }
}

@media (max-width: 60rem) {
  .tile-grid.is-gear { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* Two across on a phone, with the names underneath where there is room for them. */
@media (max-width: 40rem) {
  .tile-grid.is-gear { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem 1rem; }
  .is-gear .tile-name { font-size: 0.9375rem; }
  .is-gear .tile-meta { font-size: 0.8125rem; }
}

@media (max-width: 40rem) {
  .tile-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 7.5rem), 1fr)); }
}

/* -------------------------------------------------------------- listening */

/* One row per member: who on the left, what they are playing and why on the
   right. Deliberately reuses the existing .pick / .pick-body pair, which is
   already a cover-beside-body flex row. */
.listening {
  display: flex;
  flex-direction: column;
  gap: clamp(1.75rem, 4vw, 2.75rem);
}

/* Single column, because the member portrait is currently commented out in
   index.md and the heading carries the name instead. To go back to portrait
   beside picks, restore:
     grid-template-columns: 8rem minmax(0, 1fr);
   and give .listening-title `grid-column: 1 / -1`. */
.listening-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  align-items: start;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}
.listening-row:first-child { padding-top: 0; border-top: 0; }

/* Spans both columns, so the name sits over the portrait and the picks alike.
   It also carries the member's name, which is why the portrait below no longer
   repeats it. */
.listening-title {
  grid-column: 1 / -1;
  font-size: clamp(1rem, 1rem + 0.5vw, 1rem);
  letter-spacing: -0.02em;
  margin: 0 0 0.35rem;
}
/* Same three-colour cycle as the section headings. */
.listening-row:nth-of-type(3n + 2) .listening-title { color: var(--title-2); }
.listening-row:nth-of-type(3n) .listening-title { color: var(--title-3); }

.listening-who { display: flex; flex-direction: column; gap: 0.3rem; }

.listening-face {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  filter: saturate(0.9) contrast(1.02);
}
.listening-role {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  line-height: 1.4;
  color: var(--muted);
  margin-top: 0.3rem;
}

.listening-picks { list-style: none; margin: 0; padding: 0; }

.listening .pick { gap: 1.35rem; padding: 1.35rem 0; align-items: start; }

.listening .pick-cover {
  flex: 0 0 auto;
  width: clamp(6rem, 18vw, 15rem);
  aspect-ratio: 1;
  object-fit: cover;
}

/* No artwork: a typographic sleeve rather than an empty hole. */
.listening .pick-cover.is-blank {
  display: grid;
  place-items: center;
  padding: 0.6rem;
  text-align: center;
  background: var(--bg-raised);
  border-bottom: 3px solid var(--title-2);
}
.pick-cover-text {
  font-family: var(--font-grotesk);
  font-weight: 600;
  font-size: 0.8125rem;
  line-height: 1.15;
  overflow-wrap: anywhere;
  color: var(--text);
}

.listening .pick-body { gap: 0.2rem; }

.pick-artist {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
}
.pick-album {
  font-family: var(--font-grotesk);
  font-weight: 600;
  font-size: clamp(1.05rem, 0.95rem + 0.5vw, 1.35rem);
  line-height: 1.2;
}
.pick-album a { text-decoration: none; }
.pick-album a:hover { color: var(--accent); }

/* The write-up. Left rule in the accent so it reads as someone talking rather
   than as more body copy. */
.pick-words {
  margin: 0.6rem 0 0;
  padding-left: 0.9rem;
  border-left: 2px solid var(--title-3);
  font-size: 0.9375rem;
  line-height: 1.55;
  max-width: 60ch;
}

.pick-link {
  margin: 0.7rem 0 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}
.pick-link a { text-decoration: none; color: var(--accent); }
.pick-link a:hover { color: var(--accent-hover); }

@media (max-width: 40rem) {
  .listening-row { grid-template-columns: minmax(0, 1fr); gap: 0.9rem; }
  /* Stacked, the portrait would be enormous at full column width. */
  .listening-who { flex-direction: row; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
  .listening-face { width: 3.5rem; }
  .listening-role { flex: 1 1 8rem; }
  /* Cover above the write-up rather than beside it: side by side, a paragraph
     this long would be squeezed into about 20 characters a line. */
  .listening .pick { flex-direction: column; gap: 0.9rem; }
  .listening .pick-cover { width: min(100%, 14rem); }
}

/* ------------------------------------------------------- links & contact */

.link-row { display: flex; flex-wrap: wrap; gap: 0.5rem 0.75rem; list-style: none; margin: 1rem 0 0; padding: 0; }
.link-row a {
  display: inline-block;
  padding: 0.35rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 0.875rem;
  text-decoration: none;
}
.link-row a:hover { border-color: var(--accent); color: var(--accent); }

.contact-list { list-style: none; margin: 0 0 2rem; padding: 0; }
.contact-list li { padding: 0.85rem 0; border-bottom: 1px solid var(--line); }
.contact-list strong { display: block; font-size: 0.8125rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); font-weight: 600; }

.form { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: flex-end; max-width: 30rem; }
.form label { flex-basis: 100%; font-size: 0.875rem; color: var(--muted); }
.form input {
  flex: 1 1 14rem;
  padding: 0.65rem 0.85rem;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: inherit;
  font: inherit;
}
.form button {
  padding: 0.65rem 1.3rem;
  background: var(--accent);
  color: var(--accent-text);
  border: 0;
  border-radius: var(--radius);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

/* ------------------------------------------------------------------ story */

/* Scoped to bodyClass: story in the page front matter, so the About page can
   run its own layout without affecting anything else. */

.story .page-head,
.story main > p,
.story main > blockquote {
  max-width: 62ch;
}

/* Two columns of prose. Equal-height rows by default, which is what lets the
   second column's photograph sit flush with the bottom of the first. */
.story-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.5rem, 4vw, 3.5rem);
  margin-bottom: clamp(2.5rem, 6vw, 4rem);
}

.story-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.story-col > :last-child { margin-bottom: 0; }

/* Column headings are inside a narrow measure, so they do not need the full
   section-heading size. */
.story-col h2 {
  font-size: clamp(1.1rem, 1rem + 0.5vw, 1.35rem);
  margin-top: 0.5rem;
}

.story-fig { margin: 0 0 1.5rem; }

/* auto top margin pushes it to the foot of its column, so it lands level with
   the bottom of the taller column beside it. */
.story-fig.is-bottom {
  margin: 2rem 0 0;
  padding-top: 0;
}

/* Same rotated-block language as the homepage sections, alternating accents. */
.story-fig img {
  width: 100%;
  border-bottom: 4px solid var(--title-2);
  transform: rotate(-1.1deg);
}
.story-fig.is-bottom img {
  border-bottom-color: var(--title-3);
  transform: rotate(1.2deg);
}

@media (max-width: 46rem) {
  .story-cols { grid-template-columns: minmax(0, 1fr); gap: 0; }
  /* Stacked, there is no taller neighbour to align with. */
  .story-fig.is-bottom { margin: 1.5rem 0; }
}

/* ----------------------------------------------------- press & tech spec */

.spec-facts, .spec-needs { list-style: none; margin: 1.1rem 0 0; padding: 0; }
.spec-facts li, .spec-needs li {
  padding: 0.55rem 0;
  border-top: 1px solid var(--line);
}
.spec-label {
  display: inline-block;
  min-width: 11rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
}

/* Tables can outgrow a phone, so they scroll in their own box rather than
   forcing the page sideways. */
.table-scroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

.spec-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
  min-width: 34rem;
}
.spec-table th {
  text-align: left;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.625rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
  padding: 0 0.75rem 0.5rem 0;
  border-bottom: 1px solid var(--line);
}
.spec-table td {
  padding: 0.5rem 0.75rem 0.5rem 0;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.spec-table tr:hover td { background: color-mix(in srgb, var(--title-1) 6%, transparent); }
.spec-ch {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
  color: var(--title-1);
  white-space: nowrap;
}

/* Stage plot: absolute positions come from percentages in tech.json, so the
   layout is editable as data rather than hard-coded here. */
.plot {
  position: relative;
  aspect-ratio: 3 / 2;
  min-height: 18rem;
  margin-top: 1.25rem;
  border: 1px solid var(--line);
  background:
    repeating-linear-gradient(to right, color-mix(in srgb, var(--line) 55%, transparent) 0 1px, transparent 1px 2.5rem),
    repeating-linear-gradient(to bottom, color-mix(in srgb, var(--line) 55%, transparent) 0 1px, transparent 1px 2.5rem);
}
.plot-edge {
  position: absolute;
  left: 0;
  right: 0;
  text-align: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.625rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--muted);
}
.plot-edge-up { top: 0.5rem; }
.plot-edge-down {
  bottom: 0.5rem;
  color: var(--title-3);
}

.plot-pos {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.45rem 0.7rem;
  min-width: 6.5rem;
  text-align: center;
  background: var(--bg-raised);
  border-bottom: 3px solid var(--title-2);
}
.plot-pos:nth-of-type(3n) { border-bottom-color: var(--title-1); }
.plot-pos:nth-of-type(3n + 1) { border-bottom-color: var(--title-3); }
.plot-who { font-family: var(--font-display); font-size: 1rem; line-height: 1.1; letter-spacing: 0.03em; }
.plot-what {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.5625rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  line-height: 1.3;
}

@media (max-width: 46rem) {
  .plot { aspect-ratio: 1 / 1; }
  .plot-pos { min-width: 0; padding: 0.35rem 0.5rem; }
  .plot-what { display: none; }
  .spec-label { min-width: 0; display: block; }
}

/* Press pack */

/* Marked out so it is obvious what is meant to be lifted verbatim. */
.press-quotable {
  max-width: 62ch;
  padding-left: 0.9rem;
  border-left: 2px solid var(--title-2);
}
.press-sub {
  font-size: 0.6875rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
  margin: 1.6rem 0 0.6rem;
}

.press-lineup, .press-releases { list-style: none; margin: 0; padding: 0; }
.press-lineup li, .press-releases li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.75rem;
  align-items: baseline;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--line);
}
.press-release-link { font-size: 0.8125rem; }

.press-shots {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  gap: 1.25rem;
}
.press-shots img { width: 100%; }
.press-shots .press-shot-contain {
  aspect-ratio: 1;
  object-fit: contain;
  background: var(--bg-raised);
  padding: 1.5rem;
}
.press-shot-meta {
  display: block;
  margin-top: 0.5rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.625rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  line-height: 1.4;
}

/* ----------------------------------------------------------------- footer */

.site-footer { border-top: 1px solid var(--line); padding-block: 2rem 3rem; }
.footer-inner { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; justify-content: space-between; }
.social { display: flex; flex-wrap: wrap; gap: 0.75rem 1.25rem; list-style: none; margin: 0; padding: 0; font-size: 0.9375rem; }
.social a { text-decoration: none; color: var(--muted); }
.social a:hover { color: var(--text); }
.site-footer p { margin: 0; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
