/* =========================================================================
   SIMON SAYS / ELECTRONIC MUSIC ON DOPAMINE

   Black page, white type, one photograph running edge to edge. The page is
   the photograph's own black band continued: the picture's stage front and
   the page ground are both #000000, so there is no seam between them and no
   edge to defend. The only white surface on the site is the photograph's
   backlit haze at the top of the hero, and the nav while it sits inside it.

   Type rules, and they are the whole system
     · Two families, and the pairing is the identity:
         Unbounded  DISPLAY ONLY. Section titles, menu links, DOPAMINE, the
                    disc labels. Circular bowls, drawn wide, black weight.
         Inter      everything else. It says nothing, which is the job.
     · Three sizes: --fs for every piece of running text, --fs-display for the
                    section titles, --fs-sub for the display type that is not
                    a title: the city on a date slab, the role on a contact
                    slab. At one display size a slab read as a second band and
                    the section title stopped being the thing that opened the
                    section. There is no fourth, with one exception that is
                    declared where it is used: the tour slab on a phone runs
                    its own two steps, because the global scale bottoms out at
                    18px on a 390px screen and a poster line has to be read at
                    arm's length, not at reading distance. See section 17.
     · One colour:  --ink. No greys, no faded secondaries. Where something has
                    to sit back it does it on tracking, never on tone.
     · Emphasis is weight, and the switch to Unbounded. Nothing else.
   ========================================================================= */

/* ------------------------------ 1. FONTS ------------------------------ */
/* Both families ship as ONE variable woff2 per subset, so every weight the
   site uses comes out of a single download per family. unicode-range means an
   English page pulls the two latin files and never touches latin-ext. */
@font-face {
  font-family: "Unbounded";
  src: url("../fonts/Unbounded-latin.woff2") format("woff2");
  font-weight: 200 900; font-style: normal; font-display: swap;
  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: "Unbounded";
  src: url("../fonts/Unbounded-latin-ext.woff2") format("woff2");
  font-weight: 200 900; font-style: normal; font-display: swap;
  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;
}
@font-face {
  font-family: "Inter";
  src: url("../fonts/Inter-latin.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
  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: "Inter";
  src: url("../fonts/Inter-latin-ext.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
  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;
}

/* ------------------------------ 2. TOKENS ----------------------------- */
:root {
  --paper: #000000;                 /* the photograph's black band, continued */
  --ink:   #ffffff;
  --line:  rgba(255, 255, 255, .18);   /* rules only, never type */

  /* The one white surface left on the site: the photograph's own backlit haze
     at the top of the hero. The logotype, the moto and the nav are ink inside
     it and white everywhere else, and these two tokens are how they say so. */
  --haze:     #ffffff;
  --haze-ink: #111111;

  --fg: var(--ink);          /* white on black, everywhere but inside the haze */
  --bg: var(--paper);

  --display: "Unbounded", "Segoe UI", system-ui, -apple-system, sans-serif;
  --text:    "Inter", "Segoe UI", system-ui, -apple-system, Helvetica, Arial, sans-serif;

  /* The three sizes. Nothing else on the site may set a font-size. Unbounded is
     a wide face, so the display size is smaller here than it was in Poppins
     and the titles still cross more of the measure.

     --fs-sub is the same face and the same weight at a little over half the
     title, which is far enough down to be read as subordinate at a glance and
     still far enough above --fs to stay display type. Every piece of Unbounded
     that is not a section title takes it, so the slabs are uniform with each
     other as well as smaller than the band. */
  --fs: clamp(13px, 1.05vw, 15px);
  --fs-display: clamp(1.85rem, 5vw, 3.5rem);
  --fs-sub: clamp(1.15rem, 2.9vw, 2rem);

  /* One gutter for the whole site. Nothing overrides it. */
  --gutter: clamp(22px, 5vw, 88px);
  --measure: 1440px;
  /* The full bleed rows (bands, slabs, notes) run edge to edge and inset
     their own content, so a rule crosses the whole window while the type on
     it still starts on the same line as everything in a .wrap. One expression
     does both: the gutter on a narrow screen, and the gutter plus whatever
     the measure leaves over on a wide one. */
  --edge: max(var(--gutter), 50% - var(--measure) / 2 + var(--gutter));
  --transport-h: clamp(54px, 6.2vh, 74px);
  /* No --nav-h. There is no bar at the top of this site (§9 below).
     --sect-y came down with it: the sections are separated by space alone and
     every band already opens with its own rule and its own generous padding,
     so the old 112px was a second gap stacked on a first one. */
  --sect-y: clamp(40px, 4.6vw, 72px);
  --head-gap: clamp(30px, 4.5vw, 64px);   /* section head to its content */
  --ease: cubic-bezier(.16, .84, .28, 1);
  /* Two faster curves, used by the controls and nothing else. --ease-snap
     leaves at speed and lands hard; --ease-back overshoots and settles, which
     is what makes a small movement read as a kick rather than a drift. */
  --ease-snap: cubic-bezier(.2, .9, .25, 1);
  --ease-back: cubic-bezier(.34, 1.7, .5, 1);
}

/* ------------------------------ 3. BASE ------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%; scroll-behavior: smooth;
  background: var(--paper); color-scheme: dark;
}
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--text);
  font-weight: 400;
  font-size: var(--fs);
  line-height: 1.7;
  letter-spacing: .01em;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  /* The transport is fixed over the bottom of the window, so the page has to
     end above it rather than under it. */
  padding-bottom: var(--transport-h);
}
body.is-locked { overflow: hidden; }

img, svg, picture, video { display: block; max-width: 100%; }
img {
  height: auto;
  /* The photography is not free to take: no drag-out, no long-press "Save
     image" on iOS, and pointer-events:none puts the right-click on the
     container so the browser offers no image entry in the menu. Nothing on
     this site needs a click on an <img>, so this costs no behaviour. */
  pointer-events: none;
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
ul { list-style: none; margin: 0; padding: 0; }
::selection { background: var(--ink); color: var(--paper); }
/* currentColor, not --ink: the nav sits on the photograph's white haze on the
   home page, where a white ring would not be there at all. */
:focus-visible { outline: 1.5px solid currentColor; outline-offset: 3px; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: fixed; top: 8px; left: 8px; z-index: 200;
  transform: translateY(-200%);
  background: var(--ink); color: var(--paper);
  padding: 10px 16px; letter-spacing: .14em; text-transform: uppercase;
}
.skip-link:focus { transform: none; }

/* ------------------------------ 4. TYPE ------------------------------- */
.wrap { width: 100%; margin: 0 auto; padding-inline: var(--gutter); max-width: var(--measure); }

/* The only display size on the site, and the only place Unbounded is allowed
   to run at size. Titles are uppercased in PHP. */
.h-section {
  font-family: var(--display);
  font-weight: 900;
  font-size: var(--fs-display);
  line-height: 1.04;
  letter-spacing: -.01em;
  margin: 0;
  text-wrap: balance;
}

/* Small uppercase label. Same size and colour as everything else: it sits back
   on tracking alone. */
.label { text-transform: uppercase; letter-spacing: .2em; line-height: 1.7; }

/* --------------------------- 5. THE MOTO RULE -------------------------- */
/* "Electronic music on" sets up in Inter, tracked wide and quiet. "Dopamine"
   lands in Unbounded Black. It is the one line on the site where both families
   sit on the same baseline, and the whole pairing is legible in it. */
.moto {
  display: inline-flex; flex-wrap: wrap; align-items: baseline;
  justify-content: center; gap: .5em;
  text-transform: uppercase;
  line-height: 1.5;
}
.moto__lead {
  font-family: var(--text);
  font-weight: 400; letter-spacing: .2em;
  /* The tracking hangs a gap off the last letter; pull the word back so the
     optical space before DOPAMINE matches the space between the others. */
  margin-inline-end: -.2em;
}
.moto__dop { font-family: var(--display); font-weight: 900; letter-spacing: .01em; }

/* ---------------------------- 6. BUTTONS ------------------------------ */
/* The button is a slab with thickness, not a rectangle with a hover state.

   It has four parts and they only work together: a face that fills, two edges
   that are its extrusion, a mark that stands off the face, and a lean that
   turns the other three into one object. main.js feeds --btn-rx / --btn-ry from
   the pointer, so the slab leans towards wherever it is being touched. With no
   JS, with a keyboard, or with reduced motion the CSS defaults below still give
   it one fixed lean, and nothing is lost but the tracking.

   The two streaming CTAs are NOT this. They are records, in section 7. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .7em;
  padding: 1.15em 2em;
  border: 1px solid var(--fg);
  color: var(--fg);
  font-family: var(--text);
  font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
  text-align: center; line-height: 1.2;
  position: relative;
  --btn-rx: 0deg;      /* lean: top towards the viewer when negative */
  --btn-ry: 0deg;      /* lean: left edge towards the viewer when negative */
  --btn-lift: 0px;
  --btn-mx: 50%;       /* where the light lands, tracked with the pointer */
  --btn-my: 0%;
  --depth: 11px;       /* how thick the slab is */
  /* The children have to live in the slab's own space, or the edges fold flat
     and the mark cannot stand off the face. */
  transform-style: preserve-3d;
  transform: perspective(760px)
             translateY(var(--btn-lift))
             rotateX(var(--btn-rx))
             rotateY(var(--btn-ry));
  /* The fill is a background, not a layer, and that is what makes the rest
     possible. It used to be a ::before at z-index -1 clipped by overflow:
     hidden, and overflow: hidden forces transform-style: flat, which is exactly
     what a slab cannot have. A background paints behind the label by
     definition, needs no clipping and no stacking context, and it frees both
     pseudo-elements to be the edges.
     Layer one is the light, layer two is the fill. The light is currentColor,
     which on hover is already --bg, so it is black light on the white fill,
     with no second token and no third colour anywhere. */
  background-image:
    radial-gradient(80% 120% at var(--btn-mx) var(--btn-my),
                    color-mix(in srgb, currentColor 18%, transparent), transparent 68%),
    linear-gradient(var(--fg), var(--fg));
  background-repeat: no-repeat;
  background-position: 50% 100%;
  /* Both layers start at nothing. The light is drawn in currentColor, so left
     at full size it would sit on an unhovered button as an 18% smudge across
     its face; it has to arrive with the fill, not wait behind it. */
  background-size: 100% 0, 100% 0;
  /* The label flips faster than the fill arrives, so the fill catches up to it
     rather than the two crossfading into grey halfway. The transform is snap
     and not overshoot because the pointer retargets it on every move, and an
     overshoot curve under continuous retargeting wobbles. */
  transition: color .16s var(--ease-snap),
              background-size .26s var(--ease-snap),
              transform .18s var(--ease-snap);
  will-change: transform;
}
/* The two edges, hinged on the face's own top and bottom and folded back into
   the page. backface-visibility does the choosing: whichever edge is turned
   towards the viewer is the one that draws, so the slab shows its top when it
   leans back and its underside when it leans forward, and CSS never has to know
   which way the pointer went. Head on, both are edge on and invisible. */
.btn::before, .btn::after {
  content: ""; position: absolute; left: -1px; right: -1px;
  height: var(--depth);
  background: var(--fg);
  backface-visibility: hidden;
  opacity: 0; transition: opacity .16s var(--ease-snap);
}
.btn::before { bottom: 100%; transform: rotateX(90deg); transform-origin: 50% 100%; }
.btn::after  { top: 100%;    transform: rotateX(-90deg); transform-origin: 50% 0; }

/* Hover and focus: the face fills, the slab lifts and leans, the edges appear. */
.btn:hover, .btn:focus-visible {
  color: var(--bg);
  background-size: 100% 100%, 100% 100%;
  --btn-lift: -5px;
  --btn-rx: -9deg;
}
.btn:hover::before, .btn:hover::after,
.btn:focus-visible::before, .btn:focus-visible::after { opacity: 1; }
/* The mark leaves the face. Under preserve-3d it really is in front of the
   slab, so it parallaxes across the lean instead of merely growing, which is
   the whole difference between a drawn button and an object. */
.btn__icon { transition: transform .3s var(--ease-back); }
.btn:hover .btn__icon, .btn:focus-visible .btn__icon { transform: translateZ(26px) scale(1.06); }
/* Pressed, the slab goes back INTO the page rather than down the screen, and
   the edges close as it goes. No easing and almost no duration: a press has to
   answer under the finger or the button feels broken. */
.btn:active {
  --btn-lift: 0px; --btn-rx: 0deg; --btn-ry: 0deg;
  transform: perspective(760px) translateZ(-16px) scale(.99);
  transition-duration: .07s;
}
.btn:active::before, .btn:active::after { opacity: 0; }
.btn:active .btn__icon { transform: translateZ(8px); transition-duration: .07s; }

.btn--full { width: 100%; }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .45; pointer-events: none; }
.btn__icon { width: 1.35em; height: 1.35em; flex: none; fill: currentColor; }
.btn-row { display: flex; flex-wrap: wrap; gap: clamp(10px, 1.2vw, 16px); }

.link {
  text-transform: uppercase; font-weight: 700; letter-spacing: .18em;
  display: inline-flex; align-items: center; gap: .6em;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 100% 1px; background-repeat: no-repeat; background-position: 0 100%;
  padding-bottom: .4em;
  transition: background-size .45s var(--ease);
}
.link:hover { background-size: 0% 1px; background-position: 100% 100%; }

/* --------------------- 7. THE STREAMING TRANSPORT ---------------------
   The two streaming links are not in the page. They are pinned to the bottom
   edge of the window on every page, the way a deck's transport is pinned to
   the front of the desk: always there, never scrolled past, and never sized
   by whatever room the photograph happens to leave.

   That is the third thing they have been. A button row standing in the
   photograph's black band is what the base site does with its two, and the
   records that replaced it inherited the same problem, because the band is
   about 16% of the window at 21:9 and everything had to be measured to fit
   inside it. A transport has its own ground and answers to nothing.

   Two parts: a meter that is pure decoration and hidden from assistive tech,
   and the links, which are the whole content. */
/* Above the menu, not under it: the key that closes the panel lives in here,
   so the bar has to stay on top of the thing it opened. */
.transport {
  position: fixed; inset: auto 0 0 0; z-index: 96;
  height: var(--transport-h);
  /* The height is a token, so the phone can change it on scroll (§17). */
  transition: height .34s var(--ease);
  display: flex; align-items: stretch;
  background: rgba(0, 0, 0, .92);
  -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
  border-top: 1px solid var(--line);
}
/* The meter is the key, and the key is the whole navigation of the site. It
   is the left segment of the transport, the same height as the two links
   beside it, and it inverts while the panel is open so the bar says which
   state it is in without needing a second mark. */
.transport__key {
  flex: none;
  display: flex; align-items: center; gap: clamp(10px, 1.4vw, 18px);
  padding-inline: var(--edge) clamp(16px, 2.2vw, 34px);
  transition: color .2s var(--ease-snap), background-color .2s var(--ease-snap);
}
.transport__key:hover, .transport__key:focus-visible,
body.is-menu-open .transport__key { color: var(--paper); background: var(--ink); }
.transport__label { text-transform: uppercase; letter-spacing: .24em; }
.transport__label--open { display: none; }
body.is-menu-open .transport__label--shut { display: none; }
body.is-menu-open .transport__label--open { display: inline; }
/* Five bars on five different durations, which is all it takes to stop them
   reading as one object moving. scaleY off a bottom origin rather than a
   height, so it animates on the compositor and never reflows the bar. */
.eq { display: flex; align-items: flex-end; gap: 2px; height: 16px; flex: none; }
.eq span {
  width: 2px; height: 100%; background: currentColor;
  transform-origin: 50% 100%;
  animation: eq 820ms var(--ease) infinite alternate;
}
.eq span:nth-child(2) { animation-duration: 560ms; }
.eq span:nth-child(3) { animation-duration: 1040ms; }
.eq span:nth-child(4) { animation-duration: 700ms; }
.eq span:nth-child(5) { animation-duration: 900ms; }
@keyframes eq { from { transform: scaleY(.18); } to { transform: scaleY(1); } }
/* Open, the meter stops and stands at full height. The bars are not playing
   any more: they have gone up the screen and become the panel. */
body.is-menu-open .eq span { animation: none; transform: scaleY(1); }

.transport__links { display: flex; flex: 1 1 auto; min-width: 0; }
/* Equal segments, each the full height of the bar so the whole thing is one
   tap target from top to bottom. The fill rises from the floor, which is the
   direction the meter moves in. */
.transport__link {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; justify-content: center; gap: .8em;
  border-left: 1px solid var(--line);
  font-family: var(--display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .16em;
  background-image: linear-gradient(var(--ink), var(--ink));
  background-repeat: no-repeat; background-position: 50% 100%;
  background-size: 100% 0;
  transition: color .16s var(--ease-snap), background-size .3s var(--ease-snap);
}
.transport__link:hover, .transport__link:focus-visible {
  color: var(--paper);
  background-size: 100% 100%;
}
.transport__link span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The mark is held back and slides in with the fill: idle it is a quiet
   platform badge, hovered it arrives. */
.transport__icon {
  width: 1.25em; height: 1.25em; flex: none; fill: currentColor;
  opacity: .5; transform: translateX(-4px);
  transition: opacity .2s var(--ease-snap), transform .3s var(--ease-back);
}
.transport__link:hover .transport__icon,
.transport__link:focus-visible .transport__icon { opacity: 1; transform: none; }

/* ------------------------- 8. THE ICON ROW ---------------------------- */
/* One row of marks, used in the footer and in the menu. It is what replaces
   the base site's columns of text links. */
.iconrow { display: flex; flex-wrap: wrap; align-items: center; gap: clamp(16px, 2.2vw, 28px); }
.iconrow__link {
  display: inline-flex; align-items: center; gap: .7em;
  opacity: .6; transition: opacity .3s var(--ease);
}
.iconrow__link:hover { opacity: 1; }
.iconrow__icon { width: 22px; height: 22px; flex: none; fill: currentColor; }
.iconrow--labelled { gap: 14px clamp(18px, 2.6vw, 34px); }
.iconrow--labelled span { text-transform: uppercase; letter-spacing: .18em; }

/* ------------------------------ 9. THE MENU ---------------------------
   THERE IS NO TOP BAR. Two sections and a logotype the size of the hero never
   needed a fixed strip of chrome over the photograph, and the burger that came
   with it was the base site's shape of navigation wearing a different icon.
   Both are gone, and with them .nav, .nav--stuck, .nav__burger and --nav-h.

   The key is the meter in the transport (§7), which is on screen at every
   scroll position on every page. And what it opens is the meter itself: THE
   FIVE BARS GROW UP THE SCREEN AND BECOME THE PANEL.

   That is the whole animation, and it is why there is no background on .menu.
   The columns are the ground. They tile the window at 1fr each, rise off a
   bottom origin out of the bar they are drawn in, and carry their own
   durations and delays so they arrive like a meter rather than like a blind
   coming down. The links wait for them. */
.menu {
  position: fixed; inset: 0; z-index: 95;
  visibility: hidden;
  transition: visibility 0s linear .73s;   /* outlasts the slowest column */
}
body.is-menu-open .menu { visibility: visible; transition-delay: 0s; }

.menu__bars { position: absolute; inset: 0; display: flex; }
.menu__bars span {
  flex: 1 1 0;
  /* 1px of overlap. At 1fr each, subpixel rounding leaves hairline seams
     between the columns and the page shows through them. */
  margin-inline: -.5px;
  background: var(--paper);
  transform: scaleY(0); transform-origin: 50% 100%;
  /* Not --ease. The page's own curve is heavily front loaded, which is right
     for something arriving and wrong for something CLIMBING: on --ease the
     columns were at 93% of their height in the first quarter of the movement
     and the whole stagger disappeared into the first two frames. This one
     leaves slowly, travels, and settles, so the bars read as a meter going up
     rather than as a panel appearing. */
  transition: transform var(--d, .54s) cubic-bezier(.5, .02, .25, 1) var(--l, 0s);
}
.menu__bars span:nth-child(1) { --d: .5s;  --l: .06s; }
.menu__bars span:nth-child(2) { --d: .64s; --l: 0s;   }
.menu__bars span:nth-child(3) { --d: .54s; --l: .11s; }
.menu__bars span:nth-child(4) { --d: .7s;  --l: .03s; }
.menu__bars span:nth-child(5) { --d: .58s; --l: .08s; }
body.is-menu-open .menu__bars span { transform: scaleY(1); }

/* Flush left on the page gutter, not centred, and clearing the transport at
   the foot because the bar it came out of is still there. */
.menu__inner {
  position: relative; z-index: 1;
  height: 100%;
  padding-top: clamp(48px, 10vh, 120px);
  padding-bottom: calc(var(--transport-h) + clamp(24px, 5vh, 56px));
  display: flex; flex-direction: column; justify-content: space-between;
}
.menu__list { display: grid; gap: .1em; }
.menu__list li { overflow: hidden; }
/* The links come up behind their own mask and wait for the columns: a link
   climbing through a panel that is still arriving reads as two animations
   fighting rather than one. */
.menu__link {
  display: block;
  font-family: var(--display); font-weight: 900;
  font-size: var(--fs-display); line-height: 1.16;
  letter-spacing: -.01em; text-transform: uppercase;
  transform: translateY(105%);
  transition: transform .5s var(--ease-back) .3s;
}
body.is-menu-open .menu__link { transform: translateY(0); }
.menu__list li:nth-child(2) .menu__link { transition-delay: .36s; }
.menu__list li:nth-child(3) .menu__link { transition-delay: .42s; }
.menu__list li:nth-child(4) .menu__link { transition-delay: .48s; }
.menu__foot {
  display: grid; gap: clamp(20px, 4vh, 34px); justify-items: start;
  opacity: 0; transform: translateY(14px);
  transition: opacity .4s var(--ease) .44s, transform .5s var(--ease) .44s;
}
body.is-menu-open .menu__foot { opacity: 1; transform: none; }
.menu__moto { justify-content: flex-start; }

/* ------------------------------ 10. HERO ------------------------------ */
/* The photograph runs edge to edge and scales with the viewport. It already
   holds a white sky, the figure and a black stage front, so the logotype goes
   in its white and the records go in its black; the gradients below only
   guarantee contrast, they do not create the bands.

   What changed with the black page: the stage front and the page ground are
   now the same colour, so the bottom of the picture does not end anywhere. The
   foot's gradient runs into the page instead of onto a white edge, and the
   sections below read as the same surface the records are standing on.

   Two crops, switched at 900px: 1.42 for landscape, 0.576 for portrait. A 9:16
   photograph cannot fill a landscape hero without losing either the raised hand
   or the black band, so the wide crop is a different window on the same
   negative, not the same image squeezed. See context.md §5. */
.hero { position: relative; background: var(--paper); min-height: 100svh; }
@supports not (height: 100svh) { .hero { min-height: 100vh; } }

.hero__frame {
  position: relative;
  width: 100%;
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: space-between;
}
@supports not (height: 100svh) { .hero__frame { min-height: 100vh; } }

.hero__photo { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.hero__photo picture { display: contents; }
.hero__photo img { width: 100%; height: 100%; object-fit: cover; object-position: center; }

.hero__head {
  position: relative; z-index: 2;
  padding: clamp(26px, 5vh, 72px) var(--gutter) clamp(40px, 8vh, 110px);
  display: grid; justify-items: center; gap: clamp(12px, 1.8vh, 22px);
  text-align: center;
  /* The one white block on the site, and it is white because the photograph
     is: this paints over the backlit haze rather than introducing a colour.
     Painted by the block it carries, not by a layer with a percentage height:
     sized by its content the band stays exactly as tall as the logotype needs,
     and every pixel left over goes to the photograph, at any window height. */
  color: var(--haze-ink);
  background: linear-gradient(180deg,
    #fff 0%, #fff 56%, rgba(255,255,255,.85) 74%,
    rgba(255,255,255,.42) 89%, rgba(255,255,255,0) 100%);
}
.hero__logo { color: var(--haze-ink); width: min(80%, 620px); max-height: 16svh; height: auto; }
.hero__moto { color: var(--haze-ink); }

/* There is no .hero__foot any more. The two buttons that stood in the
   photograph's black band are the transport now, so the picture runs out of
   the bottom of the window into a page of exactly the same black, and the
   gradient that used to guarantee contrast under them has nothing left to
   protect. What that retires with it: the measured fade, the 16% ceiling on
   the band, and the whole argument about which band pays for the crop at
   21:9. The crop itself is untouched, and so is object-position below. */

@media (min-width: 900px) {
  /* Where the black band comes back on a landscape screen.

     The wide crop is baked 20% white / 60% figure / 20% black at an aspect of
     1.42, and `cover` on a wider viewport crops the difference off the top and
     the bottom. Centred, that crop is split evenly and both bands pay for it,
     which is fine at 1.42 and ruinous by 16:9: measured on the rendered page,
     the picture was solid black from 88.5% of the frame at 16:9 (about 136px at
     1080p), 91.5% at 1.97 (68px), and 99.4% on a 21:9 monitor, where the black
     band is gone altogether and the records would stand on the foot's gradient
     with bright picture underneath it. That is the "compressed" look, and it is
     also the grey wash above waiting to happen, because the foot's fade then
     sits across pixels that are not black yet.

     75% pays for the crop out of the top instead. The two bands are not equal
     and never were: the white one is backlit haze reading 250 and .hero__head
     paints its own white over it, so the frame can start lower with nothing
     lost; the black one cannot be faked over a bright picture at all. Above the
     figure there is only haze until 34% of the crop (measured: the raised hand
     up there peaks at 165 on white, three pixels wide, and the head gradient
     was already covering that region), so the top has room to give.

     What it buys, per the same arithmetic: the black band holds at 17 to 19.5%
     of the frame from 1.42 all the way out to 21:9, instead of collapsing from
     16% to nothing. Near enough to the 20% the crop was built with, at every
     landscape aspect rather than one.

     Tied to the 900px breakpoint because that is where hero_picture() switches
     to the wide file. Below it the portrait crop is taller than the viewport is
     narrow, so `cover` crops horizontally and this has no effect either way. */
  .hero__photo img { object-position: 50% 75%; }
  /* Measured off the rendered pixels for the record CTAs that used to stand
     here, and kept because it is the reason object-position is 75%: the first
     row that is black across the FULL width, and the band that leaves.

       1440x900   81.0%   171px      1920x1080  81.1%   204px
       1024x768   80.0%   154px      2560x1080  83.8%   175px
       1440x700   82.1%   126px

     Nothing stands in it now, so nothing has to fit inside it. It is the
     bottom fifth of the picture, and the page carries on in the same black. */
}

@media (max-width: 899.98px) {
  /* The portrait crop, pushed in. Below 900px hero_picture() serves the tall
     file at 0.576, and a phone is narrower than that: at 390x844 `cover`
     matches the HEIGHT and crops 96px off the sides, so the whole negative is
     on screen top to bottom and the figure sits smaller than it does on a
     laptop. This scales past `cover` to close that gap.

     Anchored at the BOTTOM, for the same reason the landscape rule pays out of
     the top. The two edges are not worth the same: the bottom of the picture
     is the black band that hands over to the page, and the page under it is
     the identical #000000, so that edge has to stay put or the handover breaks
     and the sections below stop reading as the surface the records stand on.
     The top is haze, and .hero__head paints its own white gradient over it, so
     what the zoom takes from up there was never visible as picture. At 1.22 on
     a 390x844 screen that is 186px off the top, still inside the head block,
     which stands about 280px tall at that height.

     The side crop is the edge that runs out first, not the top: 1.22 takes
     101px off each side against cover's 48, so the figure is the thing to
     check before pushing this further, not the haze.

     --hero-drop then slides the picture back down. Pinning the bottom kept the
     handover safe but it also scaled the black band with everything else, and
     the band is 30% of this crop (context.md 208: 36 white / 34 figure / 30
     black; hero-1280 is fully dark from 0.6992). At 1.22 that is 310px on a
     390x844 screen, 37% of the window given to black before the page has even
     started. Dropping 13svh runs the excess off the bottom edge instead:

       390x844   310px (37%)  ->  200px (24%),  top crop 186 -> 76px
       430x932   342px (37%)  ->  221px (24%),  top crop 205 -> 84px
       360x640   235px (37%)  ->  152px (24%),  top crop 143 -> 58px

     In svh because the band is a fixed fraction of a picture that is itself
     sized off the window, so the correction has to scale with it.

     Two limits, and the drop sits between them. It must stay under the black
     band, or the window ends on bright picture and the handover to the page
     breaks; and it must stay under the top crop, or the picture's own top edge
     comes down past y=0 and .hero exposes --paper, which is WHITE, above it.
     At 1.22 the top crop is 22svh, so that is the ceiling on --hero-drop, and
     raising the zoom raises it. Lower the zoom and this has to come down too.

     Two numbers: --hero-zoom, --hero-drop. 1 and 0 are exactly `cover`. */
  .hero__photo { --hero-zoom: 1.22; --hero-drop: 13svh; }
  .hero__photo img {
    transform: translateY(var(--hero-drop)) scale(var(--hero-zoom));
    transform-origin: 50% 100%;
  }
}

/* --------------------------- 11. SECTIONS -----------------------------
   ONE rhythm, and one rule about rules. Both were rewritten when the section
   head was.

   THERE IS NO SECTION HEAD. A title over a lede over a filter row over a
   table is the base site's page, and re-skinning it was never going to stop
   it reading as the base site's page. A section opens with a BAND: one full
   bleed rule and one title, nothing else. The lede that used to sit under the
   title is a note AFTER the content now, where it explains the list instead
   of introducing a table.

   Spacing: every gap between two sections is exactly --sect-y, set as a
   margin on the section rather than as padding on both sides of the boundary.

   Separators: a rule means "a list starts here", and now that the lists run
   edge to edge so do the rules. Every band, slab and note is full bleed and
   insets its own content with --edge, so a rule crosses the whole window
   while the type on it still starts on the measure. */
.section { position: relative; }
main > .section { margin-top: var(--sect-y); }
/* The hero hands straight over to the first band. It used to be a full
   --sect-y and the band's own top padding on top of that, which on a laptop
   was 164px of nothing between the bottom of the photograph and TOUR DATES,
   and because the picture and the page are the same black it read as a hole
   rather than as space. The band's rule now arrives just under the fold: you
   scroll a thumb's width and the list has started. */
.hero + .section { margin-top: clamp(8px, 1.2vw, 18px); }
/* The footer brings its own top padding and its own rule, so the last section
   hands over to it directly rather than adding a second gap. */
main > .section:last-child { margin-bottom: 0; }
/* Nothing is fixed at the top any more, so an anchor only needs a breath. */
.section, .hero, .footer { scroll-margin-top: 12px; }

/* The band. One rule, one title, the full width of the window. */
.band {
  margin: 0;
  border-top: 1px solid var(--line);
  padding: clamp(30px, 3.6vw, 52px) var(--edge) clamp(22px, 2.8vw, 38px);
  font-family: var(--display); font-weight: 900;
  font-size: var(--fs-display); line-height: 1;
  letter-spacing: -.01em; text-transform: uppercase;
  text-wrap: balance;
}
/* A band that does something. It is the same object as the title above it,
   at running size, and it fills on hover because unlike a date it can be
   pressed. This is what replaced the bordered button under the table. */
.band--action {
  display: block; width: 100%; text-align: left;
  font-size: var(--fs); letter-spacing: .2em;
  padding-block: clamp(20px, 2.4vw, 30px);
  transition: background-color .28s var(--ease-snap), color .28s var(--ease-snap);
}
.band--action:hover, .band--action:focus-visible { background: var(--ink); color: var(--paper); }
.band--action[hidden] { display: none; }

/* A note. Small type on the same left edge as everything else, used for what
   used to be a lede and for the sample and diagnostic lines. */
.note {
  margin: clamp(20px, 2.6vw, 34px) 0 0;
  padding-inline: var(--edge);
  display: flex; flex-wrap: wrap; gap: 8px 22px; align-items: baseline;
  max-width: 100%;
}
.note code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
/* The empty list. It carries the band's own weight because with no dates it
   is the only thing in the section. */
.note--empty {
  text-transform: uppercase; letter-spacing: .2em;
  border-top: 1px solid var(--line);
  margin: 0; padding-block: clamp(30px, 5vw, 60px);
}

/* ----------------------------- 12. TOUR -------------------------------
   A date is a SLAB, not a row of a table. The base site sets its dates in
   three columns (date, city, venue) with a rule under each, and so did this
   one until the whole component was thrown out.

   What a slab is: the city big, in the display face, on the right; the number
   of the date small on the left; the venue and the day small underneath. Two
   lines, four pieces, running the full width of the window. It reads as a
   line off a poster, which is what a date on a DJ's site is.

   There is no hover state, and that is deliberate and inherited: the rows
   carry no ticket link, and a highlight that follows the cursor advertises a
   click that leads nowhere. */
.gigs { border-bottom: 1px solid var(--line); }
/* Two columns: when on the left, where on the right. The date and the venue
   stack in the first, the city stands in the second and holds both rows.
   There is no numbering. 01 / 02 down the left edge was a list index dressed
   as design, a tour is not a countdown, and with it gone the date takes the
   left anchor it should have had in the first place. */
.gig {
  border-top: 1px solid var(--line);
  padding: clamp(18px, 2.2vw, 30px) var(--edge);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, auto);
  gap: clamp(2px, .5vw, 8px) clamp(20px, 4vw, 64px);
  align-items: center;
}
.gig.is-hidden { display: none; }
.gig__city {
  grid-column: 2; grid-row: 1 / span 2;
  justify-self: end; text-align: right; align-self: center;
  font-family: var(--display); font-weight: 900;
  font-size: var(--fs-sub); line-height: 1;
  letter-spacing: -.01em; text-transform: uppercase;
}
.gig__date {
  grid-column: 1; grid-row: 1;
  font-weight: 700; text-transform: uppercase; letter-spacing: .12em;
  font-variant-numeric: tabular-nums;
}
.gig__date span { font-weight: 400; letter-spacing: .16em; margin-inline-start: .8em; }
.gig__venue { grid-column: 1; grid-row: 2; text-transform: uppercase; letter-spacing: .18em; }

/* ----------------------------- 13. MERCH ------------------------------
   Inherited whole from the Charles B build and switched off in config.php.
   Left intact so flipping sections.merch brings it back as it was. */
.merch-grid {
  display: grid; gap: clamp(16px, 2.2vw, 30px);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
}
.card { display: flex; flex-direction: column; height: 100%; }
.card__media {
  position: relative; overflow: hidden;
  background: #141414; aspect-ratio: 4 / 5;
  box-shadow: inset 0 0 0 1px var(--line);
}
.card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform .8s var(--ease); }
.card:hover .card__media img { transform: scale(1.04); }
.card__tag {
  position: absolute; top: 12px; left: 12px; z-index: 2;
  background: var(--ink); color: var(--paper);
  letter-spacing: .18em; text-transform: uppercase; padding: .45em .75em;
}
/* Column layout + margin-top:auto keeps every ADD TO BAG on one line across a
   row of cards, whatever the product name wraps to. */
.card__body { display: flex; flex-direction: column; gap: 10px; padding-top: 16px; flex: 1 1 auto; }
.card__body > .btn { margin-top: auto; }
.card__row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.card__title {
  margin: 0; min-width: 0; overflow-wrap: anywhere;
  font-size: inherit; font-weight: 700; text-transform: uppercase; letter-spacing: .1em;
}
.card__price { font-weight: 700; white-space: nowrap; flex: none; }
.card__sizes { border: 0; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.size {
  border: 1px solid var(--line); padding: .5em .85em;
  text-transform: uppercase; letter-spacing: .14em; line-height: 1; cursor: pointer;
  transition: background-color .28s var(--ease), color .28s var(--ease), border-color .28s var(--ease);
}
.size:has(input:checked) { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.size--out { opacity: .35; cursor: not-allowed; text-decoration: line-through; }
/* Fallback for engines without :has(): JS mirrors the state on the label. */
.size.is-checked { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.card__note { text-transform: uppercase; letter-spacing: .14em; min-height: 1.2em; }

/* ---------------------------- 14. CONTACT -----------------------------
   The same slab as a date, carrying an address instead of a city: the role
   big in the display face, the address and the note small under it, full
   bleed, one after the other. Not two cards side by side in a grid, which is
   the base site's shape and the thing that needed a separator rule to hold it
   together in the first place. */
.reach {
  border-top: 1px solid var(--line);
  padding: clamp(26px, 3vw, 42px) var(--edge);
  display: grid; gap: clamp(10px, 1.2vw, 16px); justify-items: start;
}
.reach:last-child { border-bottom: 1px solid var(--line); }
.reach__role {
  margin: 0;
  font-family: var(--display); font-weight: 900;
  font-size: var(--fs-sub); line-height: 1.02;
  letter-spacing: -.01em; text-transform: uppercase;
  text-wrap: balance;
}
.reach__mail {
  font-weight: 700; letter-spacing: .06em; overflow-wrap: anywhere;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 100% 1px; background-repeat: no-repeat; background-position: 0 100%;
  padding-bottom: .18em;
  transition: background-size .45s var(--ease);
}
.reach__mail:hover { background-size: 0% 1px; background-position: 100% 100%; }
/* No address yet: it must not look like a link, and it must not look broken. */
.reach__mail--pending { margin: 0; background: none; font-weight: 400; text-transform: uppercase; letter-spacing: .18em; }
.reach__note { margin: 0; max-width: 44ch; }

/* ----------------------------- 15. FOOTER -----------------------------
   Deliberately not the base site's four-column link grid: one row of icons,
   one rule, one line of small print. */
.footer { padding-block: clamp(32px, 3.6vw, 54px) clamp(20px, 2.4vw, 30px); border-top: 1px solid var(--line); }
.footer__top {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: clamp(20px, 3vw, 36px);
  padding-bottom: clamp(22px, 2.6vw, 34px);
}
.footer__bar {
  border-top: 1px solid var(--line); padding-top: clamp(16px, 1.8vw, 22px);
  display: flex; flex-wrap: wrap; gap: 12px clamp(20px, 3vw, 40px);
  justify-content: space-between; align-items: baseline;
  text-transform: uppercase; letter-spacing: .16em;
}
.footer__links { display: flex; flex-wrap: wrap; gap: 12px clamp(18px, 2.4vw, 32px); }
.footer__links a { opacity: .6; transition: opacity .3s var(--ease); }
.footer__links a:hover { opacity: 1; }

/* --------------------------- 16. UTILITIES ---------------------------- */
.reveal { opacity: 0; transform: translateY(18px); transition: opacity .9s var(--ease), transform .9s var(--ease); }
.reveal.is-in { opacity: 1; transform: none; }
.reveal--d1 { transition-delay: .07s; }
.reveal--d2 { transition-delay: .14s; }
.reveal--d3 { transition-delay: .21s; }

/* Page shell for the legal and status pages: the same left edge as everything
   else, one column, one measure. */
.page { padding: clamp(64px, 9vw, 130px) 0 clamp(60px, 8vw, 120px); }
.page__body { max-width: 74ch; display: grid; gap: 1.3em; margin-top: clamp(28px, 4vw, 52px); }
.page__body p { margin: 0; }
.page__body h2 {
  font-size: inherit; font-weight: 700; text-transform: uppercase; letter-spacing: .16em;
  margin: 1.8em 0 0;
}
.page__body a { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.page__body ul { padding-left: 1.15em; display: grid; gap: .55em; list-style: disc; }
.page__body strong { font-weight: 700; }
.page__actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 2em; }

/* Company identity on the legal notice: label column, value column, one row
   each, on the same left edge as the prose. */
.legal-id { margin: 0; display: grid; gap: .1em .9em; grid-template-columns: 1fr; }
@media (min-width: 620px) { .legal-id { grid-template-columns: 20ch minmax(0, 1fr); } }
.legal-id dt { text-transform: uppercase; letter-spacing: .18em; align-self: baseline; opacity: .6; }
.legal-id dd { margin: 0 0 .8em; font-weight: 700; }
@media (min-width: 620px) { .legal-id dt { margin-bottom: .8em; } }
.legal-updated {
  border-top: 1px solid var(--line); padding-top: 1.4em; margin-top: 1.8em;
  text-transform: uppercase; letter-spacing: .16em; opacity: .6;
}
/* An open item on a legal page, flagged rather than quietly missing. */
.legal-todo { border-bottom: 1px dashed currentColor; }

/* --------------------------- 17. RESPONSIVE --------------------------- */
@media (max-width: 760px) {
  /* The slab folds to one column and everything holds the left edge. Sending
     the city to the right of a 360px screen leaves it a word per line.

     THE ORDER CHANGES WITH THE COLUMN, and so does which piece is the
     headline. Across two columns the city can stand on the right and hold both
     rows, and the date reads fine small on the left because the eye takes the
     whole slab at once. Stacked, it cannot: the three pieces are read top to
     bottom, in order, and the first one is the one that gets read. On a phone
     that has to be WHEN. A date you have to hunt for under a city name is a
     poster you cannot use.

     So the display treatment moves from the city to the date, and the rows run
     date, city, venue.

     THE DATE AND THE CITY ARE ONE PAIR, in the same face at the same size: the
     two halves of WHEN and WHERE, which is what the slab is for. The desktop
     slab says that across a column, the city standing to the right of the date
     at display size; stacked, the only way to say the same thing is to put
     them on consecutive rows and let them match. Splitting them by size said
     the city was a lesser fact than the day of the month, which it is not.

     They still separate, and they do it on WEIGHT, which is the rule for the
     whole site: the date at 900, the city at 700, the venue at 400. Three
     steps, in the order asked for, on one lever instead of three.

     THE PHONE SLAB RUNS ITS OWN LADDER, and this is the one place on the site
     that sets a size outside the global three. The reason is that the global
     scale is built for a desktop window and collapses on a phone: at 390px
     --fs-sub floors at 18.4px and --fs at 13px, five pixels apart, which is
     not a hierarchy, it is a rounding error. A date slab is read across a room
     or at the end of an arm, not at reading distance, so both steps move up
     and apart.

     THE DATE IS DERIVED FROM THE BAND, NOT SIZED IN vw, and that is the whole
     point of this token. Stated in vw it was a second, independent curve, and
     two curves that are supposed to hold a ratio do not hold it: --fs-display
     is FLAT at its 29.6px floor everywhere under 592px, while a vw date keeps
     climbing, so between 484 and 592px the date came out at 30px against a
     29.6px title and the slab outshouted the band that opened the section.

       width   vw date   title   derived date
        360      22.3     29.6      20.7
        390      24.2     29.6      20.7
        480      29.8     29.6      20.7   <- was BIGGER than the title
        592      30.0     29.6      20.7
        760      30.0     38.0      26.6

     As a fraction of --fs-display it cannot drift: whatever the band does at
     any width, the date is --slab-date-ratio of it, and the band stays the
     biggest thing in the section by construction rather than by luck.

     One number to tune it: --slab-date-ratio. Raise it and the date grows with
     the title still above it; at 1 they would be the same size, which is the
     thing this is here to prevent.

     --slab-line hangs off the date for the same reason, one link further down
     the chain. Left as its own vw clamp it capped at 18px while the date sat
     flat at 20.7, and from 430 to 592px the two were 2.7px apart: a step you
     can measure and cannot see. Derived, the venue is always --slab-line-ratio
     of the date, so the rows keep their distances at every width and the whole
     ladder answers to --fs-display. Only the venue reads it now; the city sits
     on --slab-date with the date.

     Declared on .gig, not in :root, because they are this component on this
     breakpoint and nothing else should reach for them.

     The venue is well up from running size too. It is third in importance, not
     a footnote, and it is the piece that answers the question the date and the
     city raise.

     TRACKING COMES DOWN with the size going up. .18em is what made small
     uppercase legible at 13px; at 16px+ it is the thing standing between the
     reader and the word, and 'clearly readable' is the whole point of the
     step. The city takes the date's own -.01em, because matching the face and
     the size and then tracking it differently is not matching it; the venue
     keeps almost none. */
  .gig {
    --slab-date-ratio: .7;
    --slab-date: calc(var(--fs-display) * var(--slab-date-ratio));
    --slab-line-ratio: .78;
    --slab-line: calc(var(--slab-date) * var(--slab-line-ratio));
    grid-template-columns: minmax(0, 1fr);
    gap: 3px;
  }
  .gig__date {
    grid-column: 1; grid-row: 1; margin-top: 0;
    font-family: var(--display); font-weight: 900;
    font-size: var(--slab-date); line-height: 1.05;
    letter-spacing: -.01em;
  }
  /* The day and the year sit at the SAME size as the number now: one date, one
     size, read as one line. They are still the quieter half of it, and they do
     that the way everything else on this site does it, on weight. */
  .gig__date span {
    font-weight: 400; letter-spacing: .02em; margin-inline-start: .55em;
  }
  .gig__city {
    grid-column: 1; grid-row: 2; margin-top: .15em;
    justify-self: start; text-align: left;
    font-family: var(--display); font-weight: 700;
    font-size: var(--slab-date); line-height: 1.05;
    letter-spacing: -.01em;
  }
  .gig__venue {
    grid-column: 1; grid-row: 3;
    font-size: var(--slab-line); line-height: 1.25;
    letter-spacing: .03em;
  }
}
@media (max-width: 700px) {
  /* Two products per line on phones, whatever the screen width. */
  .merch-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 12px; }
}
@media (max-width: 760px) {
  /* ON THE PHONE ONLY, the bar is taller while the page is at the top.

     At the top of a phone screen the transport is the only thing on the page
     asking to be touched: the hero is a photograph, the logotype is not a
     link, and there is nothing else above the fold. So it stands tall there,
     and drops to its running height as soon as the page moves, where it has
     to share the screen with the list it is sitting under. Nothing inside it
     resizes: the type, the marks and the meter hold still and the segments
     get deeper, which is what makes it read as one bar changing posture
     rather than as a control that grew.

     It is a token change, so .transport's height and the padding-bottom that
     keeps the page clear of it move together and cannot disagree. */
  :root { --transport-h: clamp(92px, 12.6vh, 120px); }
  body.is-scrolled { --transport-h: clamp(64px, 7.4vh, 84px); }
}
@media (max-width: 620px) {
  /* The word goes, the bars stay. They are the key, so unlike the old LISTEN
     label they cannot be dropped: the bars ARE the button, and they are also
     the only thing on the site that says which state the panel is in. */
  .transport__label { display: none; }
  .transport__key { padding-inline: var(--gutter) clamp(14px, 4vw, 22px); }
  .transport__link { letter-spacing: .1em; gap: .6em; }
}
@media (max-width: 560px) {
  .footer__top { flex-direction: column; align-items: flex-start; }
  .footer__bar { flex-direction: column; gap: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  .menu__link { transform: none; }
  .menu__foot { opacity: 1; transform: none; }
  /* The bar still changes height with the scroll, it just does not slide
     there: a height that eases under a finger that is already scrolling is
     motion the query asked not to have. */
  .transport { transition: none; }
  /* No climb at all. Snapping the columns to full height is the same picture
     arrived at by the animation this query asked not to happen, so the panel
     paints its own ground instead and the bars do not take part. */
  .menu { background: var(--paper); }
  .menu__bars { display: none; }
  /* The duration reset above makes these instant, not absent. A slab that snaps
     to a lean the moment the pointer touches it is exactly what this query is
     asked to stop, so the movement itself goes rather than its timing: no lean,
     no lift, no edges, no mark standing off the face. The fill still arrives,
     because a colour change is not motion. main.js checks the same query and
     never starts tracking the pointer. */
  .btn, .btn:hover, .btn:focus-visible, .btn:active { transform: none; }
  .btn::before, .btn::after { display: none; }
  .btn .btn__icon, .btn:hover .btn__icon, .btn:focus-visible .btn__icon { transform: none; }
  /* The meter stops rather than running instantly, which is what the global
     duration reset above would otherwise do to it. The bars stay drawn: they
     are the transport's mark, not its animation. The fill on a link still
     arrives, because a colour change is not motion. */
  .eq span { animation: none; transform: scaleY(.5); }
  .transport__icon, .transport__link:hover .transport__icon { transform: none; }
}

@media print {
  .menu, .transport { display: none; }
  body { background: #fff; color: #000; padding-bottom: 0; }
}
