/* ═══════════════════════════════════════════════════════════
   SPOOKY RIDDIM RETURNS — spookyriddim.com

   Two strands of visual language, held together:
     editorial ...... high-contrast serif display, all-mono
                      micro-labels, bracketed eyebrows,
                      segmented bordered cells, heavy grain
                      over a deep gradient
     cinematic ...... one hero object owns the screen,
                      chrome reduced to almost nothing

   Palette roles:
     Ghost Black ......... page
     Ink Navy / Bruise ... the gradient world (from the flyer)
     Bone White .......... type
     Cold Ghost Blue ..... micro-labels
     Acid Signal ......... interactive only — bullets, arrows,
                           active states. Never decoration.
   ═══════════════════════════════════════════════════════════ */


/* ── self-hosted type ─────────────────────────────────────
   Both families are SIL OFL 1.1, which grants the right to
   copy, embed and redistribute the font software, so the
   files live in the repo. (This folder was emptied once
   before over a personal-use-only face, which must never
   come back. These two are a different licence.)
   Four files, latin only, byte-for-byte what Google serves:
   no second origin, no render-blocking stylesheet on
   fonts.googleapis.com, and the files work from file:// .
   The unicode-range is Google's own latin range, kept so
   the browser skips a family it has no character for.    */
@font-face {
  font-family: 'Bodoni Moda';
  font-style: normal;
  font-weight: 400 600;   /* .sec__h 500, .row__c 'wght' 600 */
  font-display: swap;
  src: url('../fonts/bodoni-moda-latin-roman.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: 'Bodoni Moda';
  font-style: italic;
  font-weight: 400;       /* .foot__wm is the only italic on the site */
  font-display: swap;
  src: url('../fonts/bodoni-moda-latin-italic.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 Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/space-mono-latin-400.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;
}
/* Space Mono ships at 400 only. That holds because .sr-only pins
   font-weight:400 below — without that rule the hidden <h1> inherits the
   UA's bold and pulls a 700 file that is no longer here, leaving the
   browser to synthesise it. The two changes travel together.        */

:root {
  --black:  #050608;
  --bone:   #EFEDE6;
  --blue:   #6BAEC8;
  --navy:   #132336;
  --bruise: #34264F;
  --acid:   #C8FF2E;                    /* bright — the one shouting element */
  --acid-mid:   #A8D626;                /* hover / borders / active states   */
                                        /* area grows, or it vibrates        */

  /* Three steps, and all three are readable. Measured against --black,
     not guessed: bone at 32% composites to #505050, which is 2.51:1 —
     under WCAG AA by a wide margin, and --faint carries real content
     (door ages, sold-out rows, drop dates, the footer). 50% lands at
     4.7:1. --dim went up with it to keep the ladder apart; at 55% the
     two tokens would have been almost the same grey.
       bone 100% -> 17.0:1   --text
       bone  72% ->  9.1:1   --dim
       bone  50% ->  4.7:1   --faint   */
  --text:   var(--bone);
  --dim:    color-mix(in srgb, var(--bone) 72%, transparent);
  --faint:  color-mix(in srgb, var(--bone) 50%, transparent);
  /* rules are a tinted slate, never white-at-n% — that tint is most of
     why the reference reads 'printed' instead of 'default dark theme'  */
  --line:   #23283A;
  --line-2: #3A4157;

  --r-sm: 4px;                          /* the refs DO use radius —     */
  --r-md: 6px;                          /* 4/6/8 are their common steps */
  --r-lg: 8px;

  --serif: 'Bodoni Moda', 'Didot', Georgia, serif;
  --mono:  'Space Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --tick: .625rem;  /* 10px — registration marks */
  --xs: .75rem;     /* 12px — THE label size */
  --sm: .8125rem;   /* 13px — body mono */
  --md: .9375rem;   /* 15px — pack copy */
  --lg: 1.1875rem;  /* 19px — tile names */

  --gut: clamp(1rem, 3vw, 2.75rem);
  --maxw: 62rem;
  --bar: 2.9rem;

  --ease: cubic-bezier(.2, .8, .2, 1);
  --t-pnt: .15s;  /* paint: colour, background, border */
  --t-mov: .34s;  /* movement: transform */  /* the house curve */
  --t:    .22s;                         /* the house duration */
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  background: var(--black);
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--md);
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  /* the page is a column of dates and a column of prices */
  font-variant-numeric: tabular-nums lining-nums;
}
body.lock { overflow: hidden; }

img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button, input { font: inherit; color: inherit; background: none; border: 0; }
button { cursor: pointer; }
:where(a,button,input,[tabindex]):focus-visible { outline: 2px solid var(--acid); outline-offset: 2px; }

/* The weight matters here even though nothing is painted. <h1 class="mark">
   has no weight rule of its own, so it inherits the UA stylesheet's bold,
   and the browser dutifully downloaded a whole second Space Mono file to
   lay out one clipped pixel of text nobody can see. Hidden text must never
   pull a font file. This line is what lets the request below drop 700. */
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; font-weight:400; }
.skip { position:absolute; left:var(--gut); top:-4rem; z-index:100;
  background:var(--acid); color:var(--black); padding:.45rem 1rem; font-size:var(--xs); }
.skip:focus { top:.5rem; }

/* ── global grain ───────────────────────────────────────── */
.grain {
  position: fixed; inset: -2%; z-index: 1;
  pointer-events: none; opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.6' numOctaves='5' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='.12'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='.9'/%3E%3CfeFuncG type='linear' slope='.95'/%3E%3CfeFuncB type='linear' slope='1.12'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: gr 8s steps(5) infinite;
  will-change: transform;
}
@keyframes gr {
  0%,100% { transform: translate(0,0) } 20% { transform: translate(-1%,.5%) }
  40% { transform: translate(.5%,-1%) } 60% { transform: translate(-.5%,-.5%) }
  80% { transform: translate(1%,.5%) }
}
@media (prefers-reduced-motion: reduce) { .grain { animation: none; } }

/* ── atoms ──────────────────────────────────────────────── */
.brack {
  font-family: var(--mono); font-size: var(--xs); font-weight: 400;
  letter-spacing: -.03em; text-transform: uppercase;
  color: var(--blue);
}
.brack::before { content: '['; margin-right: .3em; }
.brack::after  { content: ']'; margin-left: .3em; }

.muted { color: var(--faint); font-size: var(--sm); padding-block: 1.25rem; text-align: center; }

/* small bordered CTA — doron's signature button */
.pill {
  border-radius: var(--r-sm);
  display: inline-flex; align-items: center; gap: .6em;
  padding: .5rem 1rem;
  /* 44px. On .pill rather than .pill--go: "Sold out" and the un-announced
     states render as a plain .pill in the very same slot, so raising only
     the filled variant would sit a 37px box next to a 44px one. */
  min-height: 2.75rem;
  border: 1px solid var(--line-2);
  background: color-mix(in srgb, var(--bone) 4%, transparent);
  font-size: var(--xs); letter-spacing: -.04em; text-transform: uppercase;
  transition: background var(--t-pnt) var(--ease), border-color var(--t-pnt) var(--ease);
}
.pill i { width: .4em; height: .4em; background: var(--acid); flex: none; }
.pill span { transition: transform var(--t-mov) var(--ease); }
.pill:hover { background: color-mix(in srgb, var(--bone) 9%, transparent); border-color: var(--line-2); }
.pill--go { background: var(--acid); border-color: var(--acid); color: var(--black); }
.pill--go i { background: var(--black); }
.pill--go:hover { background: var(--acid-mid); border-color: var(--acid-mid); }
.pill:hover span { transform: translateX(3px); }

/* ── bar ────────────────────────────────────────────────── */
.bar {
  position: fixed; inset: 0 0 auto; z-index: 50;
  height: var(--bar);
  /* 1fr | auto | 1fr, the same trick the landing page's .doors uses, and
     for the same reason: with `var(--bar) 1fr auto` the links were centred
     between the mark and the CTA, which are different widths, so the set
     sat about 20px right of the bar's real centre. Equal flanks put it on
     the true centre line at every width, and it stays there when the CTA
     changes from "Tickets" to "Dates" or disappears on a phone. */
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: stretch;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--black) 72%, transparent);
  backdrop-filter: blur(14px);
}
/* The flank columns are elastic now, so the mark has to hold its own
   square or its right border drifts out with the column. */
.bar__mark {
  justify-self: start;
  width: var(--bar);
  display: grid; place-items: center;
  border-right: 1px solid var(--line);
}
/* Both copies share one grid cell, so the hover is a crossfade and not a
   reflow. Explicit px because the global `img { height: auto }` would
   otherwise let the intrinsic 3x size through. */
.bar__g {
  grid-area: 1 / 1;
  width: 19px; height: 22px;
  transition: opacity var(--t-pnt) var(--ease);
}
.bar__g--hot { opacity: 0; }
.bar__mark:hover .bar__g { opacity: 0; }
.bar__mark:hover .bar__g--hot { opacity: 1; }

.bar__seg { display: flex; justify-content: center; }
.bar__seg a {
  display: inline-flex; align-items: center; padding-inline: 1.15rem;
  border-right: 1px solid var(--line);
  font-size: var(--xs); letter-spacing: -.04em; text-transform: uppercase;
  color: var(--dim);
  transition: color var(--t-pnt) var(--ease), background var(--t-pnt) var(--ease);
}
.bar__seg a:hover { color: var(--text); background: color-mix(in srgb, var(--bone) 5%, transparent); }
.bar__seg a[aria-current="page"] { color: var(--text); }
.bar__seg a[aria-current="page"]::before { content: '•'; color: var(--acid); margin-right: .55em; font-size: .8em; }
/* Dividers BETWEEN the links only. Hard left the trailing border carried
   the rail on into the bar; centred it is a line hanging in open space. */
.bar__seg a:last-child { border-right: 0; }

.bar__end { justify-self: end; display: flex; align-items: stretch; }
.bar__cta {
  display: inline-flex; align-items: center; gap: .55em; padding-inline: 1.15rem;
  border-left: 1px solid var(--line);
  font-size: var(--xs); letter-spacing: -.04em; text-transform: uppercase;
  color: var(--acid);
}
.bar__cta i { width: .4em; height: .4em; background: var(--acid); }
.bar__cta:hover { background: color-mix(in srgb, var(--acid) 10%, transparent); }

.bar__burger { display: none; width: var(--bar); border-left: 1px solid var(--line);
  flex-direction: column; justify-content: center; align-items: center; gap: .28rem; }
.bar__burger span { display: block; width: 1rem; height: 1px; background: var(--text);
  transition: transform var(--t-mov) var(--ease); }
.bar__burger[aria-expanded="true"] span:first-child { transform: translateY(.32rem) rotate(45deg); }
.bar__burger[aria-expanded="true"] span:last-child  { transform: translateY(-.33rem) rotate(-45deg); }

.menu { position: fixed; inset: var(--bar) 0 auto; z-index: 49;
  background: var(--black); border-bottom: 1px solid var(--line); }
.menu a { display: flex; gap: 1rem; padding: .95rem var(--gut);
  border-top: 1px solid var(--line);
  font-size: var(--sm); letter-spacing: -.04em; text-transform: uppercase; }
.menu i { font-style: normal; color: var(--blue); }

/* ── hero ───────────────────────────────────────────────── */
/* ── hero ────────────────────────────────────────────────
   One object on a flat black field. No gradient, no glow,
   no artwork — the ghost is the only thing moving.        */
.hero {
  min-height: 100svh;
  display: grid; align-content: center; justify-items: center;
  /* Symmetric: the bar is held back here, so nothing has to be left
     room for and the ghost sits on the middle of the screen. */
  padding: 2rem var(--gut);
  text-align: center;
  background: var(--black);
}

/* The wordmark. Sized off the viewport width so it never crowds the ghost
   on a phone, and capped so it stays the smaller of the two marks. */
.mark {
  width: clamp(11.5rem, 27vw, 17.5rem);
  margin: 0 0 clamp(1.25rem, 3vw, 2rem);
}
.mark img { width: 100%; height: auto; display: block; }

.ghost {
  /* Both numbers came down when the track strip went in below the doors:
     the page is one screen by contract, so new height is not added, it is
     taken from somewhere. 42vh keeps the ghost the largest thing on a
     laptop and lets it shrink first on a short window. */
  margin: 0 0 clamp(1.25rem, 3vw, 2.25rem);
  width: min(42vh, 68vw, 27rem);
  aspect-ratio: 1;
}
.ghost__v, .ghost__still {
  width: 100%; height: 100%; display: block;
  object-fit: contain;
}
/* The still only stands in when the video can't or shouldn't play. */
.ghost__still { display: none; }

/* ── the three doors ────────────────────────────────────── */
/* Fitts's law, basically: Tour is the one most people came for, so it
   gets the middle and the largest target. Merch and Samples are quiet
   outlines either side of it. */
.doors {
  display: grid;
  gap: .75rem;
  justify-items: center;
  width: min(100%, 22rem);
}

.door {
  display: grid; place-content: center;
  width: 100%;
  min-height: 3rem;                      /* 48px: a comfortable tap target */
  padding: .85rem 1.6rem;
  font-family: var(--mono); font-size: var(--xs); font-weight: 400;
  letter-spacing: -.04em; text-transform: uppercase;
  color: var(--dim);
  border: 1px solid var(--line-2);
  background: transparent;
  transition: color var(--t-pnt) var(--ease), border-color var(--t-pnt) var(--ease),
              background-color var(--t-pnt) var(--ease);
}
.door:hover { color: var(--text); border-color: var(--bone); }

/* Tour: filled, taller, wider. Bone rather than Acid, so the one
   shouting colour stays saved for accents — it arrives on hover. */
.door--go {
  min-height: 3.75rem;                   /* 60px */
  padding: 1.1rem 2.5rem;
  font-size: var(--sm);
  color: var(--black);
  background: var(--bone);
  border-color: var(--bone);
}
.door--go:hover { color: var(--black); background: var(--acid); border-color: var(--acid); }

@media (min-width: 34rem) {
  /* 1fr | auto | 1fr, not three auto columns: "Samples" is a wider word
     than "Merch", so auto columns would centre the row as a block and
     leave Tour a few pixels off true centre. Equal flanks fix it, and
     Merch and Samples come out the same width into the bargain. */
  .doors {
    width: auto;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: clamp(.6rem, 2vw, 1.1rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ghost__v     { display: none; }
  .ghost__still { display: block; }
}

/* ── sections ───────────────────────────────────────────── */
/* The bar is fixed, so the first section on a page has to start below it. */
main:not(.hero) { padding-top: var(--bar); }

.sec {
  max-width: var(--maxw); margin-inline: auto;
  padding: clamp(3.5rem, 8vw, 6.5rem) var(--gut);
  scroll-margin-top: var(--bar);
}
.sec--tight { padding-block: clamp(2rem, 4vw, 3rem); }

.sec__head {
  display: grid; justify-items: center; gap: .9rem;
  text-align: center;
  padding-bottom: 1.25rem; margin-bottom: 2rem;
  border-bottom: 1px solid var(--line-2);
}
.sec__h {
  font-family: var(--serif);
  font-variation-settings: 'opsz' 48, 'wght' 500;
  font-optical-sizing: none;
  font-size: clamp(1.6rem, 1.1rem + 1.9vw, 2.75rem);
  font-weight: 500; line-height: 1; letter-spacing: -.045em;
  margin-top: .4rem;
  display: flex; align-items: center; justify-content: center; gap: .55rem;
}
.sec__h--caps { text-transform: uppercase; letter-spacing: .015em; }
.sec__note { font-size: var(--xs); color: var(--faint); letter-spacing: -.04em; text-transform: uppercase; }

/* Shown only once the live Laylo feed has actually answered. */
.src {
  margin-top: .5rem; font-family: var(--mono); font-size: var(--xs);
  color: var(--faint); letter-spacing: -.04em; text-transform: uppercase;
  display: flex; align-items: center; justify-content: center; gap: .45rem;
}
.src[hidden] { display: none; }   /* author display:flex would beat the UA rule */
.src::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%;
  background: var(--acid); box-shadow: 0 0 0 3px color-mix(in srgb, var(--acid) 18%, transparent);
}

/* ── tour manifest ─ centred ribbon ───────────────────────────────
   Shrink-to-fit. Nothing stretches: each column is a FIXED measure
   derived from the longest real string in the data, so every row
   computes exactly --row-w and `margin-inline: auto` centres it.
   Ten identical widths stacked = the same result as centring a
   wrapper block, with no wrapper element.

   The longest-venue problem is solved by fixing the place column at
   that longest string's width rather than letting each row shrink to
   its own content: per-row shrink-wrapping under centring produces a
   zigzag of ragged edges on both sides — the mush the brief forbids.
   A fixed measure buys shared vertical edges, and the existing
   text-overflow: ellipsis is the overflow valve for any future
   venue name longer than the 25 characters quoted below.

   Inside the ribbon, local alignment is preserved: date, city and
   status each keep a left edge. Centred means the SET sits on the
   page's centre line, not that every word floats.                 */
:root {
  /* Space Mono advances at exactly .6em, so these are measured, not guessed. */
  --c-date:  3.15rem;  /* "DEC 05" — 6ch @ --sm (13px) = 46.8px, in 50.4px   */
  --c-place: 16.9rem;  /* "Skyway Theatre - The Loft" needs 168px; the rest is
                          deliberate slack, so the ribbon reaches the 34rem
                          site measure that merch and samples also use       */
  --c-age:   1.75rem;  /* "21+" — 3ch @ --xs (12px) = 21.6px, in 28px        */
  --c-stat:  4.75rem;  /* "TICKETS ->" @ --xs with -.04em + .5rem gap
                          = 68.5px, in 76px — the 7.5px of slack is the
                          travel room for the hover arrow nudge             */
  --row-gap: 2.1rem;   /* widened with --c-place to land --row-w on 34rem */
  --row-pad: .6rem;
  --row-w: calc(var(--c-date) + var(--c-place) + var(--c-age) + var(--c-stat)
                + (var(--row-gap) * 3) + (var(--row-pad) * 2));  /* 34rem / 544px */
}

/* The divider now spans the ribbon, not the 62rem column, so its two
   hairlines terminate on exactly the same verticals as every row's
   border-bottom. That shared edge is what makes the narrow block read
   as deliberate rather than as a left-aligned list nudged inward. */
.mo {
  width: min(var(--row-w), 100%);
  margin: 1.4rem auto .3rem;
  display: flex; align-items: center; gap: .9rem;
  font-size: var(--xs); letter-spacing: -.03em; text-transform: uppercase;
  color: var(--blue);
}
.mo:first-child { margin-top: 0; }
.mo::before, .mo::after { content: ''; flex: 1; height: 1px; background: var(--line); }

.row {
  position: relative; isolation: isolate;
  width: min(var(--row-w), 100%);   /* min() is the sub-320px safety net */
  margin-inline: auto;
  display: grid;
  grid-template-columns:
    var(--c-date)
    minmax(0, var(--c-place))       /* minmax keeps the ellipsis reachable */
    var(--c-age)
    var(--c-stat);
  align-items: center;
  gap: var(--row-gap);
  padding: .5rem var(--row-pad);
  border-bottom: 1px solid var(--line);
}

/* Unchanged. inset: 0 now wraps the 396px ribbon, so the navy -> bruise
   sweep crosses a shorter distance and reads a touch stronger. Good. */
.row::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--navy) 92%, transparent),
    color-mix(in srgb, var(--bruise) 60%, transparent));
  opacity: 0; transition: opacity var(--t) var(--ease);
}
@media (hover: hover) and (pointer: fine) { a.row:hover::before { opacity: 1; } }

.row--laylo { position: relative; }

/* The tick stays anchored to the ROW box (it is .row__d's pseudo but
   resolves against .row, the nearest positioned ancestor — so .row__d
   must stay unpositioned, below). Re-homed only in proportion: a short
   centred notch instead of a 60%-height bar, because left: 0 is no
   longer the page edge but the inboard left edge of the ribbon, and
   every row now shares that edge — the notches stack into a clean
   column, like perforation marks down a ticket stub. */
.row--laylo .row__d::before {
  content: ''; position: absolute; left: 0;
  top: 50%; transform: translateY(-50%);
  width: 1px; height: 1.15em;
  background: color-mix(in srgb, var(--acid) 42%, var(--black));
}

/* All ten dates are exactly 6 characters, so this column self-aligns;
   text-align is stated only to pin the intent. Do NOT add
   position: relative here — it would steal the Laylo tick. */
.row__d {
  font-size: var(--sm); color: var(--dim); letter-spacing: 0;
  text-align: start;
}
a.row:hover .row__d { color: var(--acid); }

/* Left-aligned, not centred: cities share a hard left edge, which is
   the scan edge. Centring the text inside this column would make the
   city edges ragged and cost more than the trailing air it saves. */
.row__p { display: flex; flex-direction: column; gap: .05rem; min-width: 0; }
.row__c {
  font-family: var(--serif);
  font-variation-settings: 'opsz' 11, 'wght' 600;
  font-optical-sizing: none;
  font-size: 1rem; line-height: 1.2; letter-spacing: -.02em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.row__v { font-size: .7rem; line-height: 1.3; color: var(--dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.row__a { font-size: var(--xs); color: var(--faint); text-align: center; }

/* justify-self: end -> start. TICKETS / SOLD OUT / SOON are 7, 8 and 4
   characters; ending them flush right would leave their left edges
   ragged. Starting them gives the third vertical rule, and leaves the
   4px arrow nudge room to travel inside the column instead of pushing
   against the ribbon's right edge. */
.row__s {
  justify-self: start; display: inline-flex; align-items: center; gap: .5rem;
  font-size: var(--xs); letter-spacing: -.04em; text-transform: uppercase;
  white-space: nowrap;
}
a.row .row__s { color: var(--acid); }
.row__s em { font-style: normal; transition: transform var(--t-mov) var(--ease); }
a.row:hover .row__s em { transform: translateX(4px); }

.row--soldout { color: var(--faint); }
.row--soldout .row__s { color: var(--faint); }
/* The strike belongs to a dead end, not to the status word. A sold-out
   date WITH its own drop renders as a live "Waitlist" anchor, and a
   struck-through working link reads as broken. So: strike the <div>
   rows only, and let the anchor keep the blue it has earned. */
div.row--soldout .row__s { text-decoration: line-through; }
a.row--soldout .row__s   { color: var(--blue); }
.row--soon .row__s { color: var(--blue); }

/* ── narrow ───────────────────────────────────────────────────────
   Same ribbon, re-measured. Age drops (as before), so --row-w is
   recomputed over three columns and two gaps: 19.85rem / 318px,
   which clears 375px minus 2 x --gut (16px at that width) = 343px
   with 25px to spare, and still fits a 320px screen because
   min(--row-w, 100%) lets the minmax(0, ...) place column give way. */
@media (max-width: 46rem) {
  :root {
    --c-date:  3.15rem;
    --c-place: 9.8rem;   /* 156.8px — the 168px venue ellipsizes here */
    --c-stat:  4.5rem;
    --row-gap: .7rem;
    --row-pad: .5rem;
    --row-w: calc(var(--c-date) + var(--c-place) + var(--c-stat)
                  + (var(--row-gap) * 2) + (var(--row-pad) * 2));
  }
  .row {
    grid-template-columns: var(--c-date) minmax(0, var(--c-place)) var(--c-stat);
    row-gap: .3rem;
  }
  .row__a { display: none; }
  .mo { gap: .7rem; }
}

/* The head shares the ribbon's measure, so TOUR sits over the list and the
   rule under it terminates on the same two verticals as every row border. */
#tour .sec__head { width: min(var(--row-w), 100%); margin-inline: auto; }

/* And so does the RSVP block directly below it. The dates are 545px wide
   and the window under them was 909px, so the page read as two different
   layouts stacked — the one thing a single-column page cannot afford.
   Same expression #merch and #samples already use. */
#rsvp { max-width: calc(var(--row-w) + 2 * var(--gut)); }

/* The same measure carries merch and samples, so all three inner pages are
   one column: the heading, the rule under it and the content below all start
   and stop on the same two verticals, whichever page you are on. */
#merch, #samples { max-width: calc(var(--row-w) + 2 * var(--gut)); }

/* ── window card (doron's chrome bar) ───────────────────── */
.win { border: 1px solid var(--line-2); border-radius: var(--r-md); overflow: hidden; background: color-mix(in srgb, var(--navy) 30%, var(--black)); }
.win__bar {
  display: flex; align-items: center; gap: .75rem;
  padding: .45rem .7rem;
  border-bottom: 1px solid var(--line-2);
  font-size: var(--xs); letter-spacing: -.04em;
  color: var(--faint);
}
.win__dots { display: flex; gap: .3rem; }
.win__dots i { width: .45rem; height: .45rem; border: 1px solid var(--line-2); display: block; }
.win__dots i:first-child { background: var(--line-2); }
.win__t { flex: 1; }
.win__body { padding: 1.1rem; }

/* laylo iframe sits flush; its own theme handles the inside */
.laylo-embed { width: 100%; border: 0; display: block;
  animation: none !important; transition: opacity 350ms ease; }
.laylo-fallback { display: grid; gap: .7rem; justify-items: start; }
.laylo-fallback__p { font-size: var(--sm); color: var(--dim); max-width: 42ch; }
.laylo-fallback code {
  font-size: var(--xs); color: var(--blue);
  border: 1px solid var(--line); padding: .35rem .5rem; justify-self: start;
}

/* ═══════════════════════════════════════════════════════════
   MERCH — replaces the existing `.grid` / `.tile` block.
   New classes introduced here, stated plainly:
     .mo--run     group heading (modifier on the tour page's .mo)
     .tile--cross non-product pad cell
     .tile__f     format label
     .tile__ship  pre-order ship date
     .tile__go    external action line
     .plate__g    format glyph
     .plate--cross / .plate__x   the pad cell's plate
     .grid__note  "photography to follow"
     .band / .band__t / .band__p / .band__fine   the notify band
   ═══════════════════════════════════════════════════════════ */

/* ── merch run ────────────────────────────────────────────
   Fixed integer columns. NEVER auto-fill: with an odd count
   auto-fill leaves a half-width orphan at the left of the last
   row and the page ends on a hole — and the count is exactly
   what nobody knows yet. Every shop worth copying declares
   literal integers; none lets the browser pick. --cols is set
   per breakpoint, not per viewport guess.
   544.8px measure - 16px gap = two 264.4px tracks.           */
.grid {
  --cols: 1;
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: 1.9rem 1rem;
}

/* 46.01rem is the site's existing breakpoint: it is where the bar
   swaps to the burger AND where --row-w widens to 34rem. Merch
   gains its second column at exactly the width the measure does. */
@media (min-width: 46.01rem) {
  .grid { --cols: 2; }

  /* 1-2 items: one column of full-measure rows. Changing the CARD
     is what saves a short catalogue; changing only the column count
     leaves one tile stranded beside 264px of nothing.             */
  .grid[data-layout="rail"] { --cols: 1; gap: 1.1rem; }
  .grid[data-layout="rail"] .tile { flex-direction: row; gap: 1.1rem; align-items: flex-start; }
  .grid[data-layout="rail"] .tile__m { width: 10rem; flex: none; }   /* 160px + 17.6 + 366.4 */
  .grid[data-layout="rail"] .tile__b {
    flex: 1 1 auto; min-width: 0;
    margin-top: 0; padding-top: .15rem; border-top: 0;
  }
  /* The plate is 160px here, not 264px, and .plate__i is sized off the
     VIEWPORT — so on a wide screen the numeral hits its 4.2rem cap inside a
     small box and collides with the glyph. Re-scale the plate's furniture to
     its actual width, and drop the tour name: it repeats on every card and
     only wraps to two lines at this size. */
  .grid[data-layout="rail"] .plate { padding: .7rem; }
  .grid[data-layout="rail"] .plate::before { inset: 8px; }
  .grid[data-layout="rail"] .plate__i { font-size: 1.9rem; }
  .grid[data-layout="rail"] .plate__g { inset: 28%; }
  .grid[data-layout="rail"] .plate__s { display: none; }
}

/* 20+ items: shorten the plate so more cards reach one screen.
   (5/4, not 4/5 — 4/5 is width:height and is TALLER.)          */
.grid[data-dense="on"] .tile__m { aspect-ratio: 5 / 4; }

/* Group heading — the tour page's month divider, reused. A merch
   group label and a tour month label are now one rule, so both
   terminate on the same two verticals as every row border.      */
.mo--run { grid-column: 1 / -1; width: auto; margin: .6rem 0 -.5rem; }
.mo--run:first-child { margin-top: 0; }
.mo--run b { font-weight: 400; color: var(--faint); }   /* the count, at 20+ */

.tile { display: flex; flex-direction: column; min-width: 0; }

/* ── the media box ──────────────────────────────────────── */
/* --plate-g is set per card from JS (deterministic by catalogue
   index), not by :nth-child — group headings occupy child slots
   and would scramble a CSS-only cycle.                        */
.tile__m {
  position: relative; aspect-ratio: 1; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r-md);
  background:
    linear-gradient(165deg,
      color-mix(in srgb, var(--plate-g, var(--bruise)) 52%, transparent),
      transparent 72%),
    #0A0C11;
}
.tile__m img { width: 100%; height: 100%; object-fit: cover;
  transition: transform var(--t-mov) var(--ease); }
@media (hover: hover) and (pointer: fine) { a.tile:hover .tile__m img { transform: scale(1.04); } }

/* The drawn plate. Artwork, not an error state — the Bandcamp merch
   convention is a grid of flat printed designs, no photography. */
.plate {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 1rem; gap: .35rem;
}
/* the mount: a hairline inset from the edge, not a second border */
.plate::before {
  content: ''; position: absolute; inset: 12px; border-radius: 2px;
  border: 1px solid color-mix(in srgb, var(--blue) 22%, transparent);
  pointer-events: none;
}
/* one outline per format, so twenty plates don't read as wallpaper */
.plate__g {
  position: absolute; inset: 22%; color: var(--blue); opacity: .3;
  transition: opacity var(--t-pnt) var(--ease);
}
.plate__g svg { width: 100%; height: 100%; display: block; }
a.tile:hover .plate__g { opacity: .45; }

.plate__i {
  font-family: var(--serif);
  font-variation-settings: 'opsz' 96, 'wght' 400;
  font-optical-sizing: none;
  font-size: clamp(2.6rem, 1.4rem + 4vw, 4.2rem);
  line-height: .82; letter-spacing: -.05em;
  color: color-mix(in srgb, var(--bone) 16%, transparent);
  transition: color var(--t-pnt) var(--ease);
}
a.tile:hover .plate__i { color: color-mix(in srgb, var(--bone) 32%, transparent); }
.plate__s {
  font-size: var(--tick); letter-spacing: 0; text-transform: uppercase;
  color: var(--faint);
}

/* Pre-order is the ONE flag and the only acid in the run. No store
   retrieved has a pre-order badge, but their workaround — the words
   in the product title — would wreck a 19px Bodoni name line.     */
.tile__flag {
  position: absolute; top: .5rem; left: .5rem; padding: .12rem .45rem;
  background: var(--black); border: 1px solid var(--line-2);
  font-size: var(--xs); letter-spacing: -.04em; text-transform: uppercase;
}
.tile--preorder .tile__flag {
  color: var(--acid);
  border-color: color-mix(in srgb, var(--acid) 45%, transparent);
}

/* ── the text block ─────────────────────────────────────── */
.tile__b { padding-top: .65rem; border-top: 1px solid var(--line); margin-top: .65rem; }

/* Format label. Ternion Sound's 8-item page holds up because every
   tile prints Vinyl / Shirt / Hat. Tracking is -.03em to match
   .brack and .pack__gh — the house mono idiom is tight.         */
.tile__f {
  display: block; font-size: var(--tick);
  letter-spacing: -.03em; text-transform: uppercase;
  color: var(--blue); margin-bottom: .3rem;
}

/* Name left, price right, dotted leader between — the same ::after
   leader already used by .pack__g li. Fixes ragged card bottoms. */
.tile__r { display: flex; align-items: baseline; gap: .5rem; }
.tile__r::after {
  content: ''; order: 1; flex: 1 1 auto; min-width: .7rem; height: 0;
  border-bottom: 1px dotted color-mix(in srgb, var(--bone) 16%, transparent);
  transform: translateY(-.3em);
}
.tile__n { font-family: var(--serif); font-size: var(--lg); font-weight: 500;
  line-height: 1.15; min-width: 0; }
.tile__p { order: 2; flex: none; font-size: var(--sm); }
a.tile:hover .tile__p { color: var(--acid); }

.tile__note { display: block; font-size: var(--xs); color: var(--dim); margin-top: .25rem; }
.tile__var  { display: block; font-size: var(--xs); color: var(--faint);
  letter-spacing: -.04em; margin-top: .15rem; }

/* The ship date sits next to the decision, not below the button.
   Blue, not acid — the flag already spends the acid on this card. */
.tile__ship {
  display: block; margin-top: .25rem;
  font-size: var(--tick); letter-spacing: -.03em; text-transform: uppercase;
  color: var(--blue);
}

/* "Buy ->", never "Add to cart": checkout is off-domain and the arrow
   is the affordance that says so. One anchor per card.           */
.tile__go {
  display: flex; align-items: center; gap: .4rem; margin-top: .6rem;
  font-size: var(--xs); letter-spacing: -.04em; text-transform: uppercase;
  color: var(--dim);
  transition: color var(--t-pnt) var(--ease);
}
.tile__go em { font-style: normal; transition: transform var(--t-mov) var(--ease); }
a.tile:hover .tile__go { color: var(--acid); }
a.tile:hover .tile__go em { transform: translateX(4px); }

/* Sold out: the status replaces the price, which is where a store
   is expected to put it. Dim the PLATE, not the card —
   50% on the whole tile makes the name hard to read. The card keeps
   its position: nobody in this scene sorts sold-out stock to the end.
   The same rule dims a real photo later, for free.               */
.tile--soldout .tile__m { opacity: .45; }
.tile--soldout .tile__m img { filter: grayscale(1); }
.tile--soldout .tile__p { color: var(--faint); }
.tile--soldout .tile__var { color: var(--faint); text-decoration: line-through; }

/* ── non-product pad cell ───────────────────────────────── */
/* Identical border and plate geometry, no price. Doron pads its grids
   the same way ([01]-[06] index, bundle tile, VIEW ALL tiles). Only
   the LAST row of the whole grid is ever padded.                  */
.tile--cross .tile__m { background: #0A0C11; }
.plate--cross { justify-content: space-between; }
.plate__x {
  font-family: var(--serif); font-size: 2.4rem; line-height: 1;
  align-self: flex-start;
  color: color-mix(in srgb, var(--blue) 70%, transparent);
  transition: transform var(--t-mov) var(--ease);
}
a.tile--cross:hover .plate__x { transform: translateX(4px); }
.tile--cross .tile__go { color: var(--blue); }

/* ── one product ──────────────────────────────────────────
   A single item is a product page, not a one-cell grid: a
   lone tile in a two-column grid is a half-empty row, which
   is the one thing a store page cannot look like. The shape
   here is the one this scene's stores have converged on — square
   art left, the decision column right, long copy below.
   The grid above still owns two items and up.            */
.grid[data-layout="solo"] { display: block; }

.pd { display: grid; gap: 1.4rem; }

/* Same geometry and the same drawn plate as a tile, so a photo
   dropped into `img` later needs no second set of rules. */
.pd__m {
  position: relative; aspect-ratio: 1; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r-md);
  background:
    linear-gradient(165deg,
      color-mix(in srgb, var(--plate-g, var(--navy)) 52%, transparent),
      transparent 72%),
    #0A0C11;
}
.pd__m img { width: 100%; height: 100%; object-fit: cover; }

/* No catalogue numeral: "01" is a position in a run, and a store
   with one product has no run. The glyph and the tour name carry
   the plate on their own. */
.pd .plate__i { display: none; }
.pd[data-status="soldout"] .pd__m { opacity: .45; }

.pd__buy { display: grid; align-content: start; gap: .45rem; text-align: left; }

.pd__f {
  font-size: var(--tick); letter-spacing: -.03em; text-transform: uppercase;
  color: var(--blue);
}
.pd__n {
  font-family: var(--serif); font-weight: 500; line-height: 1.07;
  font-size: clamp(1.6rem, 1.2rem + 1.7vw, 2.15rem); letter-spacing: -.012em;
}
.pd__note { font-size: var(--sm); color: var(--dim); }
.pd__pr   { font-size: var(--md); margin-top: .2rem; }
.pd[data-status="soldout"] .pd__pr { color: var(--faint); }

/* Full width, because it is the only decision on the page. */
.pd__cta { justify-content: center; margin-top: .65rem; padding-block: .72rem; }
.pd__cta[aria-disabled] { color: var(--faint); }

.pd__ship {
  font-size: var(--tick); letter-spacing: -.03em; text-transform: uppercase;
  color: var(--blue);
}
.pd__sz { font-size: var(--xs); color: var(--faint); letter-spacing: -.04em; margin-top: .3rem; }
.pd__sz span {
  color: var(--dim); text-transform: uppercase;
  letter-spacing: -.03em; margin-right: .55em;
}

/* The reference's three-tick reassurance row. Content comes from
   MERCH_META.assure, which holds only claims the site already
   makes — the row is a restatement, never a new promise.      */
.pd__tr {
  list-style: none; margin: .55rem 0 0; padding: .8rem 0 0;
  border-top: 1px solid var(--line); display: grid; gap: .32rem;
}
.pd__tr li { display: flex; gap: .55rem; font-size: var(--xs); color: var(--dim); }
.pd__tr li::before { content: '\2013'; color: var(--blue); flex: none; }

.pd__b { font-size: var(--md); color: var(--dim); line-height: 1.62; margin-bottom: 1.1rem; }
.pd__b:last-child { margin-bottom: 0; }

@media (min-width: 46.01rem) {
  /* 13.5rem art / the rest ≈ 42:58 inside the 34rem measure, which
     is the split the reference uses (285px art, 420px details). */
  .pd { grid-template-columns: 13.5rem minmax(0, 1fr); gap: 1.6rem 1.9rem; }
  .pd__about { grid-column: 1 / -1; }

  /* The plate is 216px here, not 264px. Scale its padding and the
     mount hairline down with it. (.plate__i is gone entirely — see
     above — which is also what removes the viewport-sized numeral
     that overflowed this box in the rail layout.)              */
  .pd .plate         { padding: .85rem; }
  .pd .plate::before { inset: 10px; }
  .pd .plate__g      { inset: 25%; }
}

/* ── under the run ──────────────────────────────────────── */
/* Type-only cells read as unfinished; the same cells with a
   scheduled absence under them read as a decision.          */
.grid__note {
  margin-top: 1.2rem; text-align: center;
  font-size: var(--tick); letter-spacing: -.03em; text-transform: uppercase;
  color: var(--faint);
}
.grid__note[hidden] { display: none; }

/* ── notify band ────────────────────────────────────────────
   The grid is never the whole page. GREM sells four products and
   does not read as empty because a hero band sits above the grid
   and an email band below it. Three bands: the run can shrink to
   two cards and the page still has a top, middle and bottom.
   Shared by merch and samples.                                 */
.band {
  width: min(var(--row-w), 100%); margin: 2.6rem auto 0;
  padding-top: 1.5rem; border-top: 1px solid var(--line-2);
  display: grid; gap: .8rem; justify-items: center; text-align: center;
}
.band__t {
  font-family: var(--serif); font-size: clamp(1.15rem, 1rem + .6vw, 1.4rem);
  font-weight: 500; line-height: 1.15;
}
.band__p { font-size: var(--sm); color: var(--dim); max-width: 40ch; }
.band__fine {
  font-size: var(--tick); letter-spacing: -.03em; text-transform: uppercase;
  color: var(--faint);
}
.band .pill[hidden] { display: none; }


/* ═══════════════════════════════════════════════════════════
   SAMPLES — replaces the existing `.pack` block.
   New classes introduced here, stated plainly:
     .drop / .drop__d / .drop__m    the dated status line
     .demo / .demo__k / .demo__bar  the demo slot
     .pack__spec / .pack__spec dt,dd  the spec grid
     .pack__tot                     the manifest total row
     .faq                           the three questions
   .pack__from is REDEFINED: it was a <p> of chips, it is now a
   titled block with an <ol>. .band comes from the merch CSS.
   ═══════════════════════════════════════════════════════════ */

.pack { display: grid; gap: 2.25rem; }
.pack__lead { text-align: center; }

/* DELETED: @media (min-width: 58rem) { .pack { grid-template-columns: 1fr 1fr } }
   It keyed off VIEWPORT width while #samples is capped at the 34rem
   measure, so above 928px the pack split into two 256px columns
   inside 544px and the dotted-leader manifest rows collapsed.
   One column always — the same column as tour and merch.        */

/* ── the dated status line ────────────────────────────────
   The release moment is the hero. A sample pack has no product
   photography by definition, so the date and the three true
   numbers under it do the work a cover shot would.
   No countdown: Cymatics' pack pre-launch has none, and Organic
   Basics' timer was reading 00d 00h 00m 00s when retrieved —
   a zeroed timer is worse than a printed date.                */
.drop {
  display: grid; justify-items: center; gap: .45rem;
  padding-bottom: 1.4rem; margin-bottom: .4rem;
  border-bottom: 1px solid var(--line);
  text-align: center;
}
.drop__d {
  font-family: var(--serif);
  font-variation-settings: 'opsz' 48, 'wght' 500;
  font-optical-sizing: none;
  font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.3rem);
  font-weight: 500; line-height: 1; letter-spacing: -.045em;
}
.drop__m {
  font-size: var(--xs); letter-spacing: -.04em; text-transform: uppercase;
  color: var(--dim);
}
.drop--live .drop__d { color: var(--text); }

.pack__t { font-family: var(--serif); font-size: clamp(1.4rem,1.1rem+1vw,1.9rem);
  font-weight: 500; line-height: 1.1; }
.pack__b { color: var(--dim); font-size: var(--sm); margin-top: .7rem;
  max-width: 42ch; margin-inline: auto; }

/* The stat triplet, at display scale — the other half of the hero. */
.pack__stats { list-style: none; padding: 1.1rem 0; margin: 1.4rem 0;
  border-block: 1px solid var(--line); display: flex; justify-content: center;
  gap: clamp(1.25rem, 5vw, 2.5rem); flex-wrap: wrap; }
.pack__stats b { display: block; font-family: var(--serif);
  font-variation-settings: 'opsz' 48, 'wght' 500; font-optical-sizing: none;
  font-size: clamp(2.1rem, 1.6rem + 1.8vw, 3rem);
  font-weight: 500; color: var(--text); line-height: 1; letter-spacing: -.04em; }
.pack__stats span { font-size: var(--xs); letter-spacing: -.04em;
  text-transform: uppercase; color: var(--faint); }

.pack__buy { display: flex; align-items: center; justify-content: center;
  gap: 1rem; flex-wrap: wrap; }
.pack__fine { font-size: var(--xs); color: var(--faint); }
/* the pre-drop state when there is no drop link to point at yet */
.pack__buy .pill[aria-disabled="true"] { color: var(--dim); cursor: default; }

/* ── demo slot ────────────────────────────────────────────
   Native <audio preload="none"> by choice: Cymatics ABYSS ships 15
   and Unison 24 of exactly that, with no library and no load cost.
   While `demo` is null the box renders a dimmed bar labelled
   "to follow" rather than disappearing — it holds the layout and
   is honest that audio is coming.                              */
.demo {
  margin-top: 1.3rem; padding: .75rem .85rem;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  display: grid; gap: .6rem; text-align: left;
}
.demo__k {
  display: flex; justify-content: space-between; gap: 1rem;
  font-size: var(--tick); letter-spacing: -.03em; text-transform: uppercase;
  color: var(--faint);
}
.demo audio { width: 100%; display: block; accent-color: var(--acid); }
.demo--void { opacity: .55; }
.demo--void .demo__bar {
  height: 2px; background: color-mix(in srgb, var(--bone) 14%, transparent);
  position: relative; margin-block: .35rem;
}
.demo--void .demo__bar::before {
  content: ''; position: absolute; inset: 0 84% 0 0; background: var(--blue);
}

/* ── pulled from ──────────────────────────────────────────
   REDEFINED: was a <p> of chips in the lead. Now a titled block
   above the contents list. Provenance precedes inventory — every
   competing pack page substitutes an "inspired by" list for this,
   and these are four of his own released tracks. No years: the
   data has none and none are invented.                        */
.pack__from {
  margin-top: 1.7rem; padding-top: 1.2rem;
  border-top: 1px solid var(--line); text-align: left;
}
.pack__from ol { list-style: none; padding: 0; margin: .5rem 0 0; counter-reset: t; }
.pack__from li {
  display: flex; align-items: baseline; gap: .7rem;
  padding: .5rem 0; border-bottom: 1px solid var(--line);
  font-size: var(--sm);
}
.pack__from li:last-child { border-bottom: 0; padding-bottom: 0; }
.pack__from li::before {
  counter-increment: t; content: counter(t, decimal-leading-zero);
  flex: none; font-size: var(--tick); color: var(--blue); letter-spacing: 0;
}

/* ── manifest ─────────────────────────────────────────────
   Group label carries its subtotal and the card closes on a
   stated total, summed from the data rather than written down
   anywhere — so the card cannot disagree with its own rows.
   The whole block is skipped while SAMPLES.contents is empty. */
.pack__g { border-bottom: 1px solid var(--line); padding: .9rem 1.1rem; }
.pack__g:last-of-type { border-bottom: 0; }
.pack__gh { font-size: var(--xs); letter-spacing: -.03em; text-transform: uppercase;
  color: var(--blue); margin-bottom: .5rem; }
.pack__gh b { font-weight: 400; color: var(--faint); }
.pack__g ul { list-style: none; padding: 0; display: flex; flex-direction: column; gap: .25rem; }
.pack__g li { display: flex; align-items: baseline; gap: .55rem; font-size: var(--sm); }
.pack__g li span { color: var(--dim); }
.pack__g li::after { content: ''; flex: 1; order: 1; height: 0;
  border-bottom: 1px dotted color-mix(in srgb, var(--bone) 18%, transparent);
  transform: translateY(-.25em); }
.pack__g li b { order: 2; font-weight: 400; }

.pack__tot {
  display: flex; align-items: baseline; gap: .55rem;
  padding: .85rem 1.1rem; border-top: 1px solid var(--line-2);
}
.pack__tot span { font-size: var(--xs); letter-spacing: -.03em;
  text-transform: uppercase; color: var(--dim); }
.pack__tot::after { content: ''; order: 1; flex: 1; height: 0;
  border-bottom: 1px dotted color-mix(in srgb, var(--bone) 18%, transparent);
  transform: translateY(-.25em); }
.pack__tot b { order: 2; font-weight: 400; font-family: var(--serif);
  font-size: 1.15rem; letter-spacing: -.03em; }

/* ── spec grid ────────────────────────────────────────────
   Label/value rows, after Eidan's Gumroad attributes block and
   Teenage Engineering's spec lines. Carries only what the stat
   triplet does not, so nothing prints twice. The renderer drops
   null cells and suppresses the block below three — a two-cell
   spec grid looks worse than none.                            */
.pack__spec {
  margin: 0; display: grid; grid-template-columns: 1fr auto;
  column-gap: 1rem; border-top: 1px solid var(--line);
}
.pack__spec dt {
  grid-column: 1; padding: .55rem 0; border-bottom: 1px solid var(--line);
  font-size: var(--xs); letter-spacing: -.03em; text-transform: uppercase;
  color: var(--dim);
}
.pack__spec dd {
  grid-column: 2; margin: 0; padding: .55rem 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--sm); text-align: right;
}
.pack__spec dt:last-of-type, .pack__spec dd:last-of-type { border-bottom: 0; }

/* ── faq ──────────────────────────────────────────────────
   Royalty-free, delivery, DAW — the three questions LÄMMERFYR,
   Unison and Ghost Syndicate all converge on, in that order.  */
.faq { text-align: left; }
.faq details { border-bottom: 1px solid var(--line); }
.faq details:last-child { border-bottom: 0; }
.faq summary {
  cursor: pointer; list-style: none; padding: .8rem 0;
  display: flex; align-items: baseline; gap: .6rem; font-size: var(--sm);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: '+'; margin-left: auto; color: var(--blue); }
.faq details[open] summary::after { content: '\2013'; }
.faq p { padding: 0 0 .95rem; font-size: var(--sm); color: var(--dim); max-width: 56ch; }

/* ── footer ─────────────────────────────────────────────── */
.foot { max-width: var(--maxw); margin-inline: auto; padding: 2.5rem var(--gut) 3rem; border-top: 1px solid var(--line); }
.foot__row { display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 1rem; padding-bottom: 1.25rem; }
.foot__wm { font-family: var(--serif); font-size: clamp(1.6rem,5vw,3.25rem); font-weight: 400; font-style: italic;
  line-height: 1; color: color-mix(in srgb, var(--bone) 18%, transparent); }
/* Seven links in a wrapping row. As bare text they were 12px type in a
   19px line box, so the one-character X link was a 7x20px target: a thumb
   got TikTok or YouTube instead, and all seven open a new tab, so a
   mis-tap costs a trip back. 44px square minimum now, with the row gap
   pulled in from 1.1rem so the row still measures about the same and
   still wraps.

   The hover underline has to leave border-bottom. A 19px line box centred
   in 44px leaves ~12px of air beneath the glyphs, and a bottom border
   paints on the element edge — down there it reads as a box, not an
   underline. The ::after sits just under the text instead. */
.foot__social { display: flex; justify-content: center; gap: .4rem; flex-wrap: wrap; }
.foot__social a { position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 2.75rem; min-width: 2.75rem; padding-inline: .35rem;
  font-size: var(--xs); letter-spacing: -.04em; text-transform: uppercase; color: var(--dim);
  transition: color var(--t-pnt) var(--ease); }
.foot__social a::after { content: ''; position: absolute;
  left: .35rem; right: .35rem; bottom: calc(50% - .8em); height: 1px;
  background: var(--acid); opacity: 0;
  transition: opacity var(--t-pnt) var(--ease); }
.foot__social a:hover { color: var(--acid); }
.foot__social a:hover::after { opacity: 1; }
.foot__row--end { padding-top: 1.25rem; padding-bottom: 0; border-top: 1px solid var(--line);
  font-size: var(--xs); color: var(--faint); }
.foot__row--end a { color: var(--dim); border-bottom: 1px solid var(--line); }
.foot__row--end a:hover { color: var(--acid); }

/* The Laylo disclosure. Quietest thing in the footer, but it is a
   statement of fact about where the data goes, so it is not --faint. */
.foot__fine {
  margin-top: 1.1rem; max-width: 62ch;
  font-size: var(--xs); line-height: 1.6; color: var(--faint);
}
.foot__fine a { color: var(--dim); border-bottom: 1px solid var(--line-2); }
.foot__fine a:hover { color: var(--acid); border-bottom-color: var(--acid); }

/* ── responsive ─────────────────────────────────────────── */
@media (max-width: 46rem) {
  /* Three words fit a phone. TOUR, MERCH and SAMPLES come to 204px at this
     padding, which clears 320px with the mark's 46px cell taken off — so
     the links stay on the bar instead of hiding behind a burger that only
     ever held the same three. The centring is the bar's own 1fr/auto/1fr
     and needs nothing here; hiding the CTA does not disturb it, because
     the columns are defined by the template and not by their contents. */
  .bar__end { display: none; }
  .bar__seg a { padding-inline: .85rem; }

  /* The burger and #menu stay in the markup, just unreachable. A fourth
     page brings them straight back — delete this one line. */
  .bar__burger { display: none; }

  /* The rows have their own narrow-screen block up with the manifest rules,
     where the measures they depend on are defined. Nothing for them here. */
}
@media (min-width: 46.01rem) { .menu { display: none !important; } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important; }
  .grain { animation: none; }
}
@media print { .bar,.menu,.grain,.ghost { display: none; } body { background:#fff; color:#000; } }

/* ── the homepage track ──────────────────────────────────────
   A player built from the page's own parts. The smallest Spotify
   iframe is 152px tall and arrives with its own green and its own
   typeface; this strip is 44 and arrives with neither. It sits
   under the doors and stays quiet: the page already has one
   shouting element and this is not it.                        */
.trk {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: .8rem;
  width: min(100%, 22rem);          /* the doors' measure, so edges line up */
  margin-top: clamp(1rem, 2.4vw, 1.5rem);
  padding: .45rem .6rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  text-align: left;
}
/* An empty host renders as a stray 1px rule across the page. */
.trk:empty { display: none; }

.trk__play {
  display: grid; place-items: center;
  width: 2rem; height: 2rem;
  padding: 0;
  border: 1px solid var(--line-2);
  border-radius: 50%;
  background: none;
  color: var(--bone);
  cursor: pointer;
  transition: border-color var(--t-pnt) var(--ease),
              color var(--t-pnt) var(--ease);
}
.trk__play:hover { border-color: var(--acid); color: var(--acid); }

/* Drawn in CSS so there is no second SVG to keep in step with the
   button's state: a triangle made from a border, swapped for two
   bars by a gradient once the strip is playing. */
.trk__ico {
  width: 0; height: 0;
  margin-left: 2px;                  /* optical centring of a triangle */
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 8px solid currentColor;
}
.trk[data-playing] .trk__ico {
  width: 8px; height: 10px;
  margin-left: 0;
  border: 0;
  background: linear-gradient(to right,
    currentColor 0 3px, transparent 3px 5px, currentColor 5px 8px);
}

.trk__body { min-width: 0; display: grid; gap: .3rem; }

.trk__t {
  margin: 0;
  font-family: var(--mono); font-size: var(--tick);
  letter-spacing: -.02em; text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.trk__wave {
  height: 1.4rem;
  cursor: pointer;
  touch-action: none;        /* drag-to-seek is ours, not the page's scroll */
}
.trk__wave svg { display: block; width: 100%; height: 100%; }
.trk__wave:focus-visible {
  outline: 2px solid var(--acid); outline-offset: 3px; border-radius: 2px;
}

/* Not --line-2: that is 2.00:1 on black, and until something has
   played the acid fill is zero-width, so this bed is the entire
   visible extent of the slider. --faint is the existing 50%-bone
   rung (4.7:1) rather than a new one. --line-2 stays on the strip
   border and the play ring, where it is an edge, not the data. */
.trk__base { fill: var(--faint); }
.trk__fill { fill: var(--acid); }

.trk__time {
  margin: 0;
  font-family: var(--mono); font-size: var(--tick);
  color: var(--faint);
  white-space: nowrap;
}
.trk__sep { margin: 0 .3em; color: var(--line-2); }

@media (min-width: 34rem) {
  /* The doors go content-width here, so the strip can't match them
     exactly. A touch wider than the mobile measure reads deliberate
     where 22rem under a ~28rem row would read like a mistake. */
  .trk { width: min(100%, 28rem); }
}

@media (prefers-reduced-motion: reduce) {
  .trk__play { transition: none; }
}


/* ── the parked pages ═══════════════════════════════════════
   Merch and samples while there is nothing on them. Centred, and
   deliberately NOT full-height: bar + block + footer comes to about
   680px, so the whole page including the footer lands on one screen on
   a laptop. A 100svh block would have pushed the footer — which is
   where the Laylo disclosure lives — below the fold on every device. */
.soon {
  display: grid; justify-items: center; align-content: center;
  gap: 1.1rem; text-align: center;
  padding: clamp(4.5rem, 15vh, 8rem) var(--gut) clamp(3.5rem, 11vh, 6rem);
  /* `#merch, #samples { max-width: ... }` up with the merch rules still
     applies to these sections by id, and it used to be paired with the
     `margin-inline: auto` on .sec — which this block does not use. Without
     this line the whole page is a 576px column pinned to the left edge. */
  margin-inline: auto;
}
.soon__t {
  margin: 0;
  font-family: var(--serif); font-weight: 400;
  font-size: clamp(2.6rem, 9vw, 4.75rem); line-height: .95;
  letter-spacing: -.02em;
}
.soon__p    { margin: 0; font-size: var(--sm); color: var(--dim);   max-width: 36ch; }
.soon__fine { margin: 0; font-size: var(--xs); color: var(--faint); max-width: 40ch; }
/* The pill is the one thing on the page to do, so it gets air above it
   that the 1.1rem grid gap does not give. */
.soon .pill { margin-top: .6rem; }

/* ── the privacy page ────────────────────────────────────
   One page of prose on a site that otherwise has none, so it gets the
   smallest block that can carry it rather than a general "typography"
   layer nothing else would use. Measure is capped in ch, not rem: the
   constraint is line length in characters, and it should hold whatever
   the root size is.                                                 */
.doc { max-width: 64ch; margin-inline: auto; text-align: left; }
.doc > * + * { margin-top: 1rem; }
.doc h2 {
  font-family: var(--serif);
  font-variation-settings: 'opsz' 48, 'wght' 500;
  font-optical-sizing: none;
  font-size: 1.3rem; font-weight: 500; line-height: 1.15;
  letter-spacing: -.03em; color: var(--text);
  margin-top: 2.4rem;
}
.doc h2:first-child { margin-top: 0; }
.doc p, .doc li { font-size: var(--sm); line-height: 1.75; color: var(--dim); }
.doc ul { list-style: none; padding: 0; display: grid; gap: .5rem; }
/* The bracket is the site's list mark everywhere else it has one. */
.doc li { padding-left: 1.1rem; position: relative; }
.doc li::before { content: '—'; position: absolute; left: 0; color: var(--blue); }
.doc a { color: var(--text); border-bottom: 1px solid var(--line-2);
  transition: color var(--t-pnt) var(--ease), border-color var(--t-pnt) var(--ease); }
.doc a:hover { color: var(--acid); border-bottom-color: var(--acid); }
.doc__up { font-size: var(--xs); color: var(--faint); text-transform: uppercase;
  letter-spacing: -.04em; }
/* Said plainly because it is the one thing on the page a reader might
   otherwise assume the opposite of. */
.doc__key { color: var(--text); }

/* ── short viewports ──────────────────────────────────────
   index.html is one screen by contract (§0), and nothing above
   adapts to viewport HEIGHT, so it had quietly stopped keeping
   that promise. Measured overflow before this block: 68px at
   320x568 (iPhone SE), 23px at 360x640, 6px at 375x667 (iPhone
   6/7/8, SE2), 80px at 667x375 (any phone turned sideways) and
   8px at 1024x600. It held only on tall modern phones and
   full-size desktops.

   The cause is not that the page is too tall — it is that the
   ghost cannot pay. .ghost is width: min(42vh, 68vw, 27rem),
   and the note there says the 42vh term "lets it shrink first
   on a short window". It does not: on anything narrower than
   ~390px, 68vw IS the smaller term, so the height term never
   gets to act and the ghost holds its full size however short
   the screen is. Meanwhile the track strip added a fourth block
   under the doors, putting the fixed floor — padding, wordmark,
   three doors, strip — near 418px before the ghost gets a pixel.

   So below 760px tall the vertical rhythm tightens and the
   ghost is governed by height (30svh) instead of width. None of
   it fires above that, so tall screens are untouched: the ghost
   still measures 265.2px at 390x844 and 378px at 1440x900,
   exactly as before. svh rather than vh, so a retracting mobile
   URL bar cannot push the page back over.

   Re-measured after: zero overflow at 320x480, 320x568, 360x640,
   375x667, 280x653, 667x375 and 1024x600, and no horizontal
   scroll at any of them.                                      */
@media (max-height: 760px) {
  .hero  { padding: clamp(.75rem, 2.2svh, 2rem) var(--gut); }
  .mark  { width: clamp(9.5rem, 24vw, 17.5rem);
           margin-bottom: clamp(.5rem, 1.6svh, 2rem); }
  .ghost { width: min(30svh, 62vw, 27rem);
           margin-bottom: clamp(.5rem, 1.6svh, 2.25rem); }
  .doors { gap: .5rem; }
  /* 44px even at the tightest size: still a real tap target. */
  .door     { min-height: 2.75rem; padding: .6rem 1.2rem; }
  .door--go { min-height: 3.25rem; padding: .8rem 2rem; }
  .trk   { margin-top: clamp(.5rem, 1.4svh, 1.5rem); }
}

/* ── things (/things) ═════════════════════════════
   The links page: a stack of rows, one tap each. It borrows rather
   than invents — .door's geometry and hover, .mo's group rule, and
   .row__s's travelling arrow — so it reads as part of the site and
   not as a widget dropped on top of it.

   .things      the column. Same min(var(--row-w), 100%) measure as
                .mo and the tour ribbon, so it lines up with every
                other inner page instead of picking its own width.
   .things__g   one group of rows.
   .thing       a row. 48px floor, like .door.
   .thing--go   the one emphasised row — bone fill, exactly as
                .door--go does it, so Acid Signal stays an accent
                and only arrives on hover.
   .thing--off  a row with no destination. Dashed and faint: it has
                to stop looking like a control, because it is a <p>.
   .thing__t    label, with .thing__s stacked under it.
   .thing__a    the trailing chevron. Acid on anchors only, matching
                `a.row .row__s`, so dead rows never get the colour. */
.things { width: min(var(--row-w), 100%); margin-inline: auto; }
.things__g { display: grid; gap: .5rem; margin-bottom: 1.6rem; }
.things__g:last-child { margin-bottom: 0; }

.thing {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  min-height: 3rem;                      /* 48px: the .door floor */
  padding: .75rem 1.1rem;
  font-family: var(--mono); font-size: var(--xs); font-weight: 400;
  letter-spacing: -.04em; text-transform: uppercase;
  color: var(--dim);
  border: 1px solid var(--line-2);
  background: transparent;
  transition: color var(--t-pnt) var(--ease), border-color var(--t-pnt) var(--ease),
              background-color var(--t-pnt) var(--ease);
}
.thing__t { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
/* Sentence case and not uppercase: the sub-line is a real phrase, and
   .7rem of tracked-out caps is where this type stops being readable. */
.thing__s {
  font-size: .7rem; line-height: 1.3; letter-spacing: -.02em;
  text-transform: none; color: var(--faint);
}
.thing__a { flex: none; }
.thing__a em { font-style: normal; display: inline-block;
               transition: transform var(--t-mov) var(--ease); }
a.thing .thing__a { color: var(--acid); }

.thing--go {
  min-height: 3.5rem;                    /* 56px */
  color: var(--black); background: var(--bone); border-color: var(--bone);
}
.thing--go .thing__s { color: color-mix(in srgb, var(--black) 62%, transparent); }
a.thing--go .thing__a { color: var(--black); }

.thing--off { color: var(--faint); border-style: dashed; }

@media (hover: hover) and (pointer: fine) {
  a.thing:hover { color: var(--text); border-color: var(--bone); }
  a.thing--go:hover { color: var(--black); background: var(--acid); border-color: var(--acid); }
  a.thing:hover .thing__a em { transform: translateX(4px); }
}

/* ── the cursor ══════════════════════════════════════════════
   The ghost's own hands, all the way across the site: the closed hand
   at rest, and the pointing finger wherever something can be clicked.
   Two poses rather than one, because a single cursor everywhere would
   buy the look by throwing away the only signal that says "this is a
   link" — the hand still points, it just points with our hand.

   `cursor` inherits, so the whole page is covered by setting it once on
   :root and then overriding it on the controls.

   Both files come from 00 BRAND via tools/mkmark.py — Wave.png and
   Point.png, cropped to the hand and given a hairline of the page's
   black so they read on the Bone White Tour door as well as on the
   dark. The number pairs are the hotspots the generator prints; they
   are not guesses and they must change with the art.

   Desktop only. A touch screen has no cursor to replace, and
   `pointer: fine` keeps this off a tablet with no trackpad either.

   LAST IN THE FILE on purpose: `.faq summary`, `.trk__play` and
   `.trk__wave` set `cursor: pointer` themselves, and a class beats a
   bare element selector however early it appears. They keep their own
   declaration as the floor; this is the override. */
@media (hover: hover) and (pointer: fine) {
  :root { cursor: url("../img/cursor-rest.png") 14 2, auto; }

  a[href], button, summary, [role="slider"],
  .faq summary, .trk__play, .trk__wave {
    cursor: url("../img/cursor-point.png") 24 2, pointer;
  }

  /* Nothing that cannot be clicked gets a hand saying it can — and it
     falls back to the rest hand, not the system arrow, or the illusion
     breaks on exactly the elements people look at hardest. */
  [aria-disabled="true"] { cursor: url("../img/cursor-rest.png") 14 2, default; }
}
