/* Fergus Oswald — v3.
 *
 * Built from mockup/v3/source.dc.html, 19 Sep 2026. The design was made in
 * Claude with every style inline; this pulls them into classes so the same
 * rule is written once. Where a number here looks arbitrary it came from the
 * mockup — the clamp() ranges especially.
 *
 * ## The thing that shapes everything
 *
 * The page does not scroll. The shell is exactly the viewport, and only the
 * left pane scrolls inside it. That is the design: header, panel, index, with
 * a photograph filling the right. It means every panel has to survive a short
 * window, which is why the type scales with the viewport height as well as
 * its width.
 */

:root {
  --paper:  #F2E3C7;   /* the ground. Warm cream */
  --ink:    #33200F;   /* the text. Near-black brown */
  --flame:  #D9541F;   /* the accent: hover, the one coloured word */
  --deep:   #9C3A10;   /* a link that has been hovered */
  --pencil: #4A3A2C;   /* the handwriting and the crayon underlines */
  --dim:    rgba(51, 32, 15, 0.76);
  --faint:  rgba(51, 32, 15, 0.22);
  --wash:   rgba(51, 32, 15, 0.06);

  --display: 'Bricolage Grotesque', Helvetica, Arial, sans-serif;
  --hand:    'Architects Daughter', 'Comic Sans MS', cursive;
  --body:    Karla, Helvetica, Arial, sans-serif;
}

html, body {
  margin: 0; padding: 0; height: 100%;
  background: var(--paper);
  overflow: hidden;
}
* { box-sizing: border-box; }
body { font-family: var(--body); color: var(--ink); }

a { color: var(--ink); text-decoration: none; }
::selection { background: var(--flame); color: #F2E3C7; }
/* ⚠️ THE TOKEN, not the brown. Typed literally, this survived the flip and
   left the contact fields as three empty boxes on the dark — no idea what went
   in any of them. Same trap as .hard-fail, in the opposite direction: that one
   paired two tokens that had become one colour, this one dodged the tokens
   altogether. textarea too; only inputs were covered. */
input::placeholder, textarea::placeholder { color: var(--dim); opacity: 0.8; }
.defs { position: absolute; }

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

@keyframes fadein  { from { opacity: 0; } to { opacity: 1; } }
@keyframes slidein { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes draw     { from { stroke-dashoffset: -62; } to { stroke-dashoffset: 0; } }
/* The box round a button. pathLength="100" normalises the outline, so this is
   the same arithmetic whatever width the button turns out to be. */
@keyframes draw-box { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }
/* The words on the signup button go pale as the colour arrives underneath. */
@keyframes pale      { from { color: var(--ink); } to { color: var(--paper); } }
@keyframes write   { from { clip-path: inset(0 100% -20% 0); } to { clip-path: inset(0 -6% -20% 0); } }

/* The mockup animates for thirteen seconds. Honoured, but never at the cost
   of the content appearing — every animated thing is visible at rest. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001s !important;
    animation-delay: 0s !important;
    transition-duration: 0.001s !important;
  }
  /* ⚠️ Not enough on its own for the doors. Both of their animations START
     from an invisible state, so merely making them instant leaves a page of
     blank boxes for a moment — and on a slow machine, longer. These land them
     finished. */
  .door-word, .written-word, .note, .ask-kicker, .ask-words span, .ask-small,
  .drawn-field input, .ask-go .go-word { clip-path: none !important; }
  .door-box path, .drawn-box path, .drawn-box line,
  .drawn-rule path { stroke-dashoffset: 0 !important; }
  /* The button's words are pale BECAUSE it is coloured in. If the colouring
     lands instantly the words have to land pale with it, or they are ink on
     ink and unreadable. */
  .ask-go .go-word { color: var(--paper) !important; }
}

/* ---- the frame ----------------------------------------------------------- */

.shell {
  position: fixed; inset: 0;
  display: grid;
  /* ⚠️ `minmax(0, 1fr)` AND NOT `1fr`, for the same reason the rows already
     say it — and leaving the column out was a real fault, not a tidiness one.
 
     A grid with no declared column gets ONE implicit `auto` column, and a grid
     item's default `min-width: auto` refuses to shrink below its own
     min-content. So the column grew to whatever the widest thing inside
     demanded and the whole frame went with it: **measured at 452px inside a
     390px phone on 21 Sep 2026**, with `.top`, `.body` and every panel all
     452 wide and the right-hand 62px simply off the screen.
 
     That one missing word is why the standfirst read "Singer / Songwriter /
     Instrumentalis", why the index sliced "Drums etc" and dropped
     Follow/Contact onto a second line behind a stray slash, and why Nick
     reported the Releases and Drums etc pages as "too wide" on a phone. Four
     symptoms, one cause — and the suite passed through all of them, because
     nothing in it measures a width.
 
     `minmax(0, 1fr)` lets the column be narrower than its content, and the
     things inside that scroll (the reel, the lists) go back to scrolling. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto;
  padding: clamp(14px, 2.2vh, 26px) clamp(16px, 2.6vw, 34px);
}

.top {
  /* Positioned for the same reason .pane is: so the full-bleed photograph on
     the home page passes UNDER the wordmark rather than over it. A fixed
     .plate carries z-index 0, and a positioned element with z-index 0 paints
     above ordinary in-flow content — so without this the picture would sit on
     top of the name. */
  position: relative; z-index: 1;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 20px;
  padding: clamp(14px, 3.2vh, 40px) 0 clamp(10px, 1.6vh, 18px);
  border-bottom: 1px solid var(--ink);
}
.wordmark {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(20px, 3.1vw, 42px); line-height: 0.82;
  letter-spacing: -0.03em; text-transform: uppercase;
}
.standfirst {
  margin: 0 0 3px; font-size: 10px; font-weight: 500;
  letter-spacing: 0.24em; text-transform: uppercase;
  color: var(--dim); text-align: right;
}

/* The photograph sits BEHIND the words, not beside them.
   Nick, 19 Sep 2026: "put the video / image behind the buttons aligned to
   right and with a mask that blends the photo/video into the background
   colour so most of behind the buttons is the colour not the photo."

   So this stopped being a two-column grid. The words span the full width and
   the picture is a layer underneath, pushed to the right and faded out
   leftwards — see .plate. */
.body {
  position: relative;
  min-height: 0;
  padding: clamp(14px, 2.4vh, 30px) 0;
  display: flex;
}

.pane {
  position: relative; z-index: 1;      /* above the wash */
  flex: 1 1 auto;
  min-width: 0; min-height: 0;
  /* ⚠️ NOT `justify-content: center`. A centred flex column that is taller
     than its box overflows at BOTH ends, and the top overflow cannot be
     scrolled to — scrollTop is already 0 there. On a short window the six
     buttons sat above the pane's top edge and were unreachable by any means.
     
     `margin-block: auto` on the child centres it exactly the same way when it
     fits, and collapses to a normal top-aligned scroll when it does not. The
     brief is no scrolling; the fallback is for the window that makes that
     impossible, and a reachable page beats a clipped one. */
  display: flex; flex-direction: column; justify-content: flex-start;
  overflow-y: auto; scrollbar-width: none;
}
.pane > * { margin-block: auto; }
/* ⚠️ HOME IS CENTRED, THE SUB PAGES ARE NOT. Nick, 21 Sep 2026: "keep the
   title of each section at the top at same level". Vertical centring puts a
   short panel's heading halfway down and a long one's at the top, so the
   written heading jumps as you move between them. Only home — six doors and
   a signup, designed as one centred block — keeps the auto margins. */
.pane-top { justify-content: flex-start; }
.pane-top > * { margin-block: 0; }
.pane::-webkit-scrollbar { width: 0; height: 0; }
.panel { animation: slidein 0.45s ease both; }

/* ---- the photograph ------------------------------------------------------ */

/* The wash.
 *
 * Pinned to the right of the content area and faded out towards the left, so
 * the buttons sit on cream and the picture is only really present at the right
 * edge. Three things do that work together and all three are needed:
 *
 *   - the MASK fades the layer itself out leftwards, to nothing by 88%
 *   - `multiply` blends what is left into the cream rather than sitting on it
 *   - grayscale plus a low opacity stops a colour photograph competing with
 *     the one accent colour on the page
 *
 * A mask rather than a gradient overlay on top: an overlay would also dim the
 * text sitting above it, and the text is the point.
 */
.plate {
  position: absolute;
  top: 0; bottom: 0; right: 0;
  width: min(62%, 780px);
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to left, #000 0%, #000 34%, rgba(0,0,0,0.45) 62%, transparent 88%);
  mask-image: linear-gradient(to left, #000 0%, #000 34%, rgba(0,0,0,0.45) 62%, transparent 88%);
}
.plate img, .plate video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: right center;
  filter: grayscale(1) contrast(1.04);
  mix-blend-mode: multiply;
  opacity: 0.5;
}

/* ⚠️ THE FADE IS ON THE PARENT, NOT THE PICTURE, and that is a bug fix
   rather than tidying.
   
   `fadein` runs `opacity: 0 → 1`, and `animation-fill-mode: both` leaves the
   final keyframe in force for ever. An animation beats a normal declaration in
   the cascade, so `opacity: 0.5` on the picture above NEVER APPLIED — every
   photograph rendered at full strength.
   
   It was invisible because no photograph has ever been in the picture store, so
   the plate has only ever shown its placeholder text. The first picture put
   behind the page, on 21 Sep 2026, came out almost black and the page with it.
   
   Fading the PARENT leaves the picture's own opacity alone. */
.plate { animation: fadein 0.6s ease both; }
/* ---- the home page, with a photograph behind it --------------------------- */
/*
 * Nick, 21 Sep 2026: *"the text/buttons etc should be the beige colour instead
 * of the black/brown"*, and before that *"the photo/video to be under the
 * buttons and text… photo/video to completely fill the screen on phone."*
 *
 * So home stops being ink-on-paper and becomes paper-on-photograph. It is four
 * custom properties rather than a second set of rules, because every colour on
 * the page already comes from these — including the hand-drawn boxes, whose
 * stroke was changed to `currentColor` in `app.js` on the same day so that a
 * flip reaches them too. It used to be the hex value typed twice.
 */
/* ⚠️ SCOPED TO .pane, NOT TO THE PAGE — and it has to be.
   
   The photograph now stops at the two rules, so the header and the index sit on
   plain beige. Flipping the whole page would put beige writing on beige there:
   the wordmark and the index would vanish. Only the band over the photograph
   goes pale. Caught by rendering it; the first version flipped `body`. */
/* ⚠️ NO LONGER SCOPED TO .pane. The note above was right while the photograph
   stopped at the two rules: flipping the whole page would have put beige on
   beige in the header and the index. Now the photograph runs behind those too,
   so the opposite is true — leaving them dark would put brown writing on a
   dark picture. The flip goes on the body and reaches everything. */
body.photo-home {
  --ink:    #F2E3C7;                      /* the writing is the paper colour now */
  --pencil: #F2E3C7;
  --dim:    rgba(242, 227, 199, 0.88);
  --faint:  rgba(242, 227, 199, 0.42);
  --wash:   rgba(242, 227, 199, 0.08);   /* the film wells, now that they sit on a photograph */
  color: var(--ink);
}

/* The picture can be strong now, and has to be: pale writing needs something
   dark under it. This is the opposite problem to the cream page, where the
   photograph had to stay faint to keep out of the ink's way. */
body.photo-home { --plate-strength: 0.92; background: #2A1E14; }

/* ⚠️ The signup button colours ITSELF in with --ink, which is now the paper
   colour — so its word would be paper on paper and vanish. It takes the dark
   brown back, explicitly. This is the one place the flip cannot be automatic. */
/* ⚠️ THE BUTTON DOES NOT FOLLOW THE PAGE'S FLIP, and that is the point.
   
   Everything else in the band goes pale because it sits on a photograph. The
   button is not writing on a photograph — it is a coloured-in block, and it
   brings its own ground with it. So it keeps the ORIGINAL logic: dark pencil
   strokes laid down, and the word going pale as the colour arrives underneath.
   
   Flipping it with the rest gave cream strokes and a dark word, which rendered
   as a pale block with the word lost among the gaps between strokes — the one
   thing on the page a visitor is asked to press, and the hardest to read.
   Caught by cropping the render and looking at it, not by any check. */
body.photo-home .pane .ask-go { color: #33200F; }

/* Solid, per Nick's *"buttons to be solid"*. Over a photograph a hairline reads
   as a smudge, so the drawn boxes thicken and stop being translucent. */
body.photo-home .pane .door-box path,
body.photo-home .pane .drawn-box path,
body.photo-home .pane .drawn-box line { stroke-width: 2.6; }
body.photo-home .pane .hint { opacity: 1; }

/* ⚠️ REVERSED 21 Sep 2026. This block used to hide the drawn box on the home
   page and put a flat cream rectangle behind the word instead.
   
   Nick, looking at the live site: *"the sign-up button... needs to be coloured
   in roughly, not smoothly."* That flat rectangle IS the smoothness — the
   pencil colouring was still being generated underneath and then hidden.
   
   The original reasoning was that a sketch "reads as a sketch on paper and as a
   mess on a photograph". That was a guess about how it would look, made without
   rendering it, and it was wrong: the colouring-in is the character of this
   site, and taking it off the one button everybody is asked to press took the
   character off exactly the wrong thing.
   
   What the flip actually needed was only the SECOND half of the old note — the
   email box, whose text is hard-coded brown and invisible here. That stays. */
body.photo-home .pane .ask-go { background: transparent; }

/* The strokes are the paper colour here, not the ink, because on the home page
   --ink IS the paper colour. They are laid a little heavier so they read
   against a photograph rather than against cream. */
/* Laid a little heavier than on cream: the block has a photograph behind its
   gaps rather than paper, so a thin pass reads as dirt instead of colour. */
body.photo-home .pane .ask-go .drawn-box .in { stroke-opacity: 1; }
body.photo-home .pane .drawn-field input { color: var(--paper); }
body.photo-home .pane .drawn-field input::placeholder { color: rgba(242, 227, 199, 0.62); }

/* The small print under the form, and the standfirst, sit directly on the
   photograph with nothing behind them. A shadow rather than a panel: a box
   behind every stray line would turn the page into a set of cards. */
body.photo-home .pane { text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55); }

/* Nick, 21 Sep 2026: *"let's put some padding between the buttons and the left
   side."*
   
   On every other panel the words start at the left rule, which is right when
   they are on plain cream — the rule and the text share an edge and the page
   reads as one column. Over a photograph it reads as the buttons being pushed
   up against the frame.
   
   It goes on the PANE rather than on `.doors`, so the signup and its small
   print move with the buttons. Indenting the buttons alone would leave the
   writing beneath them hanging off to the left, which is worse than the thing
   it fixes. Scoped to the photograph page: the text panels are unchanged. */
body.photo-home .pane { padding-left: clamp(16px, 2.6vw, 44px); }

/* ---- the photograph, full bleed behind the home page ---------------------- */
/*
 * Nick, 20 Sep 2026: *"put the image completely behind the home page."*
 *
 * On every other panel the photograph is a column down the right, handed back
 * to the words by the mask above. On HOME it fills the window instead and sits
 * behind everything — the wordmark, the doors and the index alike.
 *
 * **`fixed`, not `absolute`.** `.plate` lives inside `.body`, which sits inside
 * `.shell`'s padding, so an absolute full-bleed would stop at the padding edge
 * and leave a cream margin down three sides — which reads as a mistake rather
 * than a design. Nothing in its ancestry uses `transform`, `filter` or
 * `will-change`, so `fixed` reaches the real window edges. Checked, because a
 * single transform anywhere above it would silently turn this back into an
 * absolute box and the border would come back.
 */
.plate.full {
  /* ⚠️ ABSOLUTE, INSIDE .body — not fixed to the window, and that is Nick's
     instruction of 21 Sep 2026: *"the header and the footer should be the
     original beige — the photo/video should not extend to those, it should be
     within the lines."*
     
     The lines are the rule under the wordmark and the rule over the index.
     `.body` is exactly the band between them, so `inset: 0` against it lands on
     both without a number being typed. An earlier version of this was
     `position: fixed; inset: 0`, which filled the whole window — and took the
     header and footer with it. */
  /* ⚠️ FIXED TO THE WINDOW, which reverses the 'within the lines' rule of
     21 Sep. Nick, same day, later: *"extend the background image to go behind
     the header and footer."* So it is the whole window again, and the two
     rules now sit ON the photograph rather than fencing it in.
     
     Nothing in .plate's ancestry uses transform, filter or will-change, so
     `fixed` reaches the real window edges — checked, because any one of those
     above it would silently turn this back into an absolute box and put the
     beige margin back down three sides. */
  position: fixed;
  inset: 0;
  width: auto;
  /* No mask. The mask exists to give the left half back to the words; behind
     all of them there is no left half to give back. */
  -webkit-mask-image: none;
  mask-image: none;
}

/* ⚠️ THE SCRIMS, carried over from the v4 exploration, and they are what makes
   a full-bleed photograph survive under a LIST rather than under six buttons.
   
   Home could rely on `text-shadow` alone: big pale words, lots of gaps, and the
   picture reading through them was the point. Releases, Live and Playlists are
   rows of 11px labels and hairline rules, and those disappear over any busy
   part of a photograph.
   
   Two gradients rather than one flat tint: a diagonal weighted to the left,
   where the writing lives, and a vertical one that anchors the two rules top
   and bottom. A flat tint strong enough for the tracklist would have killed the
   photograph everywhere else. */
.plate.full::before,
.plate.full::after {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 1;
}
.plate.full::before {
  background: linear-gradient(100deg,
    rgba(42, 30, 20, 0.92) 0%,
    rgba(42, 30, 20, 0.82) 38%,
    rgba(42, 30, 20, 0.46) 72%,
    rgba(42, 30, 20, 0.68) 100%);
}
/* Weighted to the very top and the very bottom now that the wordmark and the
   index sit on the picture rather than above it — 10px letter-spaced caps need
   more help than a headline does. */
.plate.full::after {
  background: linear-gradient(to bottom,
    rgba(42, 30, 20, 0.86) 0%,
    rgba(42, 30, 20, 0.42) 16%,
    transparent 34%,
    transparent 62%,
    rgba(42, 30, 20, 0.52) 82%,
    rgba(42, 30, 20, 0.9) 100%);
}

.plate.full img, .plate.full video {
  object-position: center;

  /* ⚠️ `multiply` is turned OFF here, and this is the whole reason the
     full-bleed version needed its own rule rather than a wider `.plate`.

     `mix-blend-mode` blends an element with its backdrop WITHIN ITS OWN
     STACKING CONTEXT — and the `position: fixed` above creates one. So the
     moment this went full bleed, `multiply` stopped seeing the cream page
     underneath and started blending against nothing. A photograph that had
     been a soft wash down the right-hand column came through at almost full
     strength and turned the entire page black, with the wordmark and the
     doors barely legible on top of it.

     It was RENDERED AND LOOKED AT, which is the only reason this is written
     down rather than shipped. The arithmetic said 0.26 would be comfortable.
     It was not remotely comfortable. */
  mix-blend-mode: normal;

  /* ⚠️ NOT grayscale here, and that is the colouring fix rather than a
     preference.

     Nick, 20 Sep 2026, on the first full-bleed render: *"just the colouring."*
     The column treatment desaturates the picture to grey, which is right when
     it is a sliver at the right-hand edge and wrong when it is the whole page:
     grey over cream goes muddy, and the page stops being cream at all. The
     warm ground is the design — it is the first thing named in `:root`.

     So the picture is toned INTO the paper instead of drained of colour.
     `sepia` pulls it towards the same warm brown as --paper and --ink;
     `brightness` lifts it off black, because the photograph is a near-black
     red-lit live shot and a dark picture at any opacity reads as dirt rather
     than as a photograph. */
  filter: sepia(0.42) saturate(0.7) brightness(1.24) contrast(0.92);

  /* How present the photograph is. ONE NUMBER on purpose — the same reasoning
     as TEMPO for the animation speeds: "make the photo stronger" has to stay a
     single edit or it stops being a thing anyone adjusts.

     ⚠️ Low, deliberately. The cream and the ink are the design; the photograph
     is behind them. Every value above about 0.25 starts taking the page's
     colour away rather than adding to it — which is exactly what Nick objected
     to. It is a look he picks, not a value that is correct. */
  /* Nick, 21 Sep 2026: *"make the opacity mask on the background vid/pics a bit
     less dense maybe 20% so pics are brighter"*. 0.14 -> 0.20.
 
     ⚠️ This is the ONE number, and it stays one number. The note above it has
     said since 20 Sep that anything above about 0.25 starts taking the page's
     colour away rather than adding to it — 0.20 is inside that, and it is a
     look Nick picks rather than a value that is correct. */
  opacity: var(--plate-strength, 0.20);
}

/* ⚠️ GONE. The caption labelled the photograph when it was a column down the
   right-hand side with its own space to sit in. Full bleed removed that space:
   `bottom: 0` became the window's bottom, so it landed in the index's row and
   smudged into "Follow/Contact", and being an auto-z sibling of a z-index: 1
   scrim it was painted under an almost-opaque wash on the way — an illegible
   ghost on top of the navigation.
   
   Deleted rather than re-anchored. It was labelling a picture nobody is asked
   to look at: the plate is aria-hidden with an empty alt precisely because it
   is a wash behind the words. */
.plate .caption { display: none; }

/* A panel whose photograph has not been supplied yet.
   NOT an empty rectangle: the design names what each one should be, so the
   placeholder says it. An unfilled slot that states its own brief reads as
   work in progress; a blank one reads as broken. Same call as the drawn
   release covers. */
/* No photograph yet. NOT a bordered box — this layer is behind the buttons
   now, and a rectangle with a border round it behind the words would look
   like a mistake rather than a slot. Just the brief, quietly, bottom right. */
.plate-empty {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: flex-end;
  padding: clamp(14px, 3%, 30px);
}
.plate-empty .hint {
  margin: 0; max-width: 20ch; text-align: right;
  font-family: var(--hand);
  font-size: clamp(14px, 1.4vw, 19px); line-height: 1.3;
  color: var(--pencil); opacity: 0.45;
}

/* ---- sub pages: written headings and pencil annotations -----------------
 *
 * Nick, 21 Sep 2026, choosing from the three explorations: "1a headings and
 * 1c annotations". So every panel below home opens the way home does — a word
 * written in the hand with a crayon line drawn under it — and the typeset
 * list underneath is then MARKED UP rather than redrawn: a ring round the row
 * that matters, a note in the margin in the accent colour, and the row rules
 * themselves drawn rather than ruled.
 *
 * The list stays typeset on purpose. A gig list or a discography is read by
 * scanning, and handwriting at six rows is slower to scan than type. The hand
 * does the heading, the edges and the pointing; the type does the facts.
 *
 * ⚠️ EVERY DRAWN LINE HERE IS GENERATED AT ITS MEASURED SIZE, by fitRules(),
 * for the same reason fitDoors() exists: a path authored at one width and
 * stretched to another with preserveAspectRatio="none" comes apart under the
 * crayon filter — the displacement is applied AFTER the scale, so a line
 * stretched 3x breaks into staggered dashes. These elements ship empty and
 * are filled once the browser knows how wide they are.
 */

.written { position: relative; display: inline-block; max-width: 100%; }
.written-word {
  display: inline-block;
  font-family: var(--hand);
  font-size: clamp(24px, min(4.6vw, 6.4vh), 48px);
  line-height: 1.05; color: var(--ink);
  /* ONE LINE. The heading is written, and the crayon line is drawn under the
     whole of it — a heading that wraps puts the underline through the middle
     of its own second line, and the write clip cuts that line off. */
  white-space: nowrap;
}
.written-ul {
  position: absolute; left: 0; bottom: -9px;
  width: calc(100% + 10px); height: 12px;
  color: var(--flame); overflow: visible; pointer-events: none;
}
.head-row {
  display: flex; align-items: flex-start; gap: clamp(12px, 2vw, 30px);
  flex-wrap: wrap;
  margin: 0 0 clamp(18px, 3.2vh, 34px);
}
.note {
  display: inline-block;
  font-family: var(--hand); line-height: 1.15; color: var(--flame);
  font-size: clamp(14px, min(1.45vw, 2.1vh), 19px);
}
/* ⚠️ Wide enough for the note to stay on ONE line: the write clip reveals a
   single line, and a note that wraps loses its second one. */
.head-row .note { padding-top: clamp(4px, 1vh, 12px); white-space: nowrap; }
.note-under { margin-top: clamp(6px, 1.2vh, 14px); }

/* The rules under the rows are drawn now, so the ruled ones go. ⚠️ Scoped to
   .pane to beat the ruled versions further down the sheet. */
.pane .rows { border-top: 0; }
.pane .rows li, .pane .row { position: relative; border-bottom: 0; }
.marked { position: relative; display: inline-block; }
.pane .row .where > .marked { display: inline-block; width: max-content; max-width: 100%; }
.pane .row > .note-inline { flex: 0 0 auto; align-self: center; }
.note-inline { margin-left: clamp(10px, 1.6vw, 22px); white-space: nowrap; }
.row-rule {
  position: absolute; left: 0; bottom: -4px;
  width: 100%; height: 9px;
  color: var(--ink); opacity: 0.45; overflow: visible; pointer-events: none;
}
.ring {
  position: absolute; left: -10px; top: -7px;
  width: calc(100% + 20px); height: calc(100% + 14px);
  color: var(--flame); overflow: visible; pointer-events: none;
}
.drawn-rule { display: block; }

/* ---- artwork -------------------------------------------------------------
   Nick, 21 Sep 2026: "want artwork for all releases included". A release with
   no artwork yet gets its initial in the hand rather than an empty square —
   same call as the panel placeholders. */
.rows.arted li { align-items: center; }
.art {
  flex: 0 0 auto; width: clamp(34px, 3.6vw, 48px); aspect-ratio: 1;
  overflow: hidden; display: grid; place-items: center;
  background: rgba(242, 227, 199, 0.12);
}
.art img, .cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.art-none { font-family: var(--hand); font-size: 15px; color: var(--ink); opacity: 0.65; }
.withart { display: flex; gap: clamp(16px, 2.6vw, 34px); align-items: flex-start; }
.withart-body { flex: 1 1 auto; min-width: 0; }
.cover {
  position: relative; flex: 0 0 auto;
  width: clamp(104px, min(16vw, 26vh), 200px); aspect-ratio: 1;
  padding: 5px;
}
.cover-box {
  position: absolute; inset: 0; width: 100%; height: 100%;
  color: var(--ink); overflow: visible; pointer-events: none;
}
.door-cta { margin-top: clamp(10px, 2vh, 22px); margin-left: -2px; }

/* ---- the live carousel, along the foot of the releases panel -------------
   Nick, 21 Sep 2026: "make youtube carousel along bottom of releases that
   will show live performances of originals".

   Thumbnails, not more players. An embedded iframe costs a YouTube
   connection before anyone has asked to watch anything, and on a panel that
   already has a list above it four of them would not fit; the picture is
   swapped for the player on click. The strip scrolls sideways because the
   panel cannot grow downwards - the site does not scroll. */
.live-strip { margin-top: clamp(10px, 2vh, 22px); flex: 0 0 auto; }

/* ⚠️ THE STRIP IS PINNED TO THE FOOT AND THE LIST GIVES WAY.
   Six releases plus a row of thumbnails is taller than the pane, and the
   site does not scroll — so this panel becomes a column whose middle is
   allowed to shrink. The list scrolls inside itself if it has to; the
   heading and the strip always stay put. */
.panel-fit { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.panel-fit > .rows { flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-width: none; }
.panel-fit > .rows::-webkit-scrollbar { width: 0; }
.panel-fit > .head-row { flex: 0 0 auto; }

/* The foot of a panel: whatever it ends with sits on the bottom rule, with
   the signup on the left and the one door on the right of the same line.
   Nick, 21 Sep 2026. */
.panel-foot {
  margin-top: auto; flex: 0 0 auto;
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: clamp(12px, 2vw, 30px); flex-wrap: wrap;
  padding-top: clamp(8px, 1.6vh, 18px);
}
.panel-foot .ask { margin-top: 0; }
.panel-foot .door-cta { margin-top: 0; }
.live-strip-word {
  display: inline-block; margin: 0 0 clamp(6px, 1vh, 12px);
  font-family: var(--hand); color: var(--ink);
  font-size: clamp(15px, min(1.6vw, 2.4vh), 21px);
}
.reel {
  display: flex; gap: clamp(8px, 1.2vw, 16px);
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory; scrollbar-width: none;
  padding-bottom: 2px;
}
.reel::-webkit-scrollbar { height: 0; }
.reel-card {
  flex: 0 0 auto; scroll-snap-align: start;
  width: clamp(140px, 17vw, 220px);
  background: none; border: 0; padding: 0; margin: 0;
  color: var(--ink); cursor: pointer; text-align: left; font-family: var(--body);
}
.reel-shot {
  position: relative; display: block;
  aspect-ratio: 16 / 9; max-height: clamp(70px, 13vh, 130px);
  width: 100%; overflow: hidden;
  background: rgba(242, 227, 199, 0.12);
}
.reel-shot img, .reel-shot iframe {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; border: 0; display: block;
}
.reel-play {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  font-family: var(--hand); font-size: clamp(13px, 1.3vw, 17px);
  color: var(--ink); background: rgba(42, 30, 20, 0.55); padding: 3px 12px;
  pointer-events: none;
}
.reel-card:hover .reel-play, .reel-card:focus-visible .reel-play { color: var(--flame); }
.reel-name {
  margin: 6px 0 0; font-size: 12px; line-height: 1.3; color: var(--ink);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

@media (max-width: 720px) {
  .withart { flex-direction: column; }
  .cover { width: clamp(104px, 38vw, 170px); }
}

/* ---- the index ----------------------------------------------------------- */

.index {
  position: relative; z-index: 1;      /* above the photograph — see .top */
  display: flex; flex-wrap: wrap; align-items: baseline; gap: clamp(10px, 1.6vw, 26px);
  padding: clamp(10px, 1.6vh, 18px) 0 clamp(6px, 1vh, 12px);
  border-top: 1px solid var(--ink);
}
/* The whole index in the hand now, not just the page you are on — and the
   items are separated by a slash rather than by a rule under the current one.
   Written out, a row of names with slashes between them is what the index
   always was; the small caps and the border were type's way of saying it. */
.index a {
  display: flex; align-items: baseline; gap: 7px;
  font-family: var(--hand);
  font-size: clamp(15px, 1.5vw, 19px);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  padding-bottom: 3px;
  color: var(--dim);
}
.index a + a::before {
  content: '/';
  margin-right: clamp(10px, 1.6vw, 26px);   /* matches the flex gap, so the slash sits midway */
  color: var(--faint);
}
.index a:hover { color: var(--ink); }
.index a[aria-current="page"] { color: var(--ink); }
/* Nick, 21 Sep 2026: the numbers go. They were scaffolding that made a row of
   small caps read as an ordered index, and the labels do that on their own. */
.index .n { display: none; }

/* Where you are is carried by colour alone now — full strength against the
   others at --dim. With every item in the hand there is nothing left for an
   underline to distinguish, and a drawn rule under one of eight handwritten
   names read as a correction rather than as a marker. */
.index a[aria-current="page"] { border-bottom: 0; }

/* ---- type ---------------------------------------------------------------- */

.kicker {
  margin: 0 0 10px; font-size: 10px; font-weight: 600;
  letter-spacing: 0.24em; text-transform: uppercase; color: var(--dim);
}
.panel h2 {
  margin: 0 0 clamp(14px, 2.4vh, 26px);
  font-family: var(--display); font-weight: 700;
  font-size: clamp(25px, 3.8vw, 52px); line-height: 0.9;
  letter-spacing: -0.02em;
}
.panel h2 .hot { color: var(--flame); }
.lead {
  margin: 0 0 18px; max-width: 42ch;
  font-size: clamp(14px, 1.1vw, 17px); line-height: 1.6; color: var(--dim);
}
.hand { font-family: var(--hand); font-size: clamp(18px, 1.8vw, 24px); line-height: 1.2; margin: 0 0 6px; }

/* ---- buttons ------------------------------------------------------------- */

.btn, .btn-line {
  display: inline-block; padding: 12px 20px;
  font-size: 10px; font-weight: 600;
  letter-spacing: 0.2em; text-transform: uppercase; white-space: nowrap;
  cursor: pointer; font-family: var(--body);
}
/* ⚠️ The label on a SOLID button is named literally, for the same reason as
   .hard-fail: --ink and --paper are one colour now, so a solid button filled
   with --ink and lettered with --paper was cream on cream. The fill still
   comes from the token — it flips with the page — but the writing on top of it
   has to be the dark, always. */
.btn { background: var(--ink); color: #2A1E14; border: 1px solid var(--ink); }
.btn:hover { background: var(--flame); border-color: var(--flame); color: #F2E3C7; }
.btn-line { border: 1px solid var(--ink); background: transparent; color: var(--ink); }
.btn-line:hover { background: var(--ink); color: #2A1E14; }

/* ---- the home page's hand-annotated buttons ------------------------------ */

/* The doors: handwritten words with a box drawn round each.
   Nick, 19 Sep 2026 — "write first button left to right then draw the box
   around like it's hand drawn."

   `align-items: flex-start` so each is only as wide as its own words. Stretched
   to match, they would read as a menu bar rather than as six things someone
   wrote down. */
.doors {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: clamp(7px, 1.5vh, 16px); max-width: 560px;
}

/* ⚠️ THE HEIGHT HAS TO KNOW ABOUT THE WINDOW. Every previous attempt at making
   home fit a short window trimmed gaps and margins, and it kept not fitting,
   because the six door rows — the bulk of the panel — were a fixed 39px each
   whatever the window did: their type is vw, their padding's vh term never
   reached its floor. Six rows that cannot shrink are 234px of a 365px pane.
   
   `min(vw, vh)` on both: wide-but-short windows now take their size from the
   height, which is the dimension that is actually short. */
.door {
  position: relative;
  display: inline-block;
  padding: clamp(5px, min(1.1vh, 1.4vw), 13px) clamp(14px, 1.9vw, 24px);
  color: var(--ink);
}
.door-word {
  display: inline-block;
  font-family: var(--hand);
  font-size: clamp(17px, min(2.2vw, 3.3vh), 30px);
  line-height: 1.1;
  color: var(--ink);
  /* The reveal is a clip, not a fade, so the word appears letter by letter
     from the left — the same `write` the removed annotations used. */
}
/* The box is drawn round the outside of the words, so it must not add to the
   box the words sit in — hence absolute, and no pointer events of its own. */
.door-box {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  overflow: visible;
  pointer-events: none;
}
/* ⚠️ THE DOTS. A round cap on a dash of zero length still paints — so every
   box put a blob at its path's start point and sat there showing it until its
   own turn to draw came round, up to six seconds in. It read as a bullet
   beside each button, which is exactly what it was not.
   
   Butt caps on the boxes. The crayon filter is what makes these look drawn,
   not the cap shape, and the underlines under the signup keep their round
   ends where the length is never zero for long. */
.door-box path, .drawn-box path { stroke-linecap: butt; }

.door:hover .door-word { color: var(--flame); }
.door:hover .door-box path { stroke: var(--flame); }

/* ---- Drums, covers etc --------------------------------------------------- */

/* One column on a narrow pane, two when there is room. The pane is already
   narrow by design, so this is about the pane rather than the window. */
/* ⚠️ Four 16:9 players stacked two-up is 174px more than the pane has, and no
   width rule fixes that — the grid asked how WIDE the pane was and never how
   tall. Capping each player's height is what makes the panel fit: the columns
   then follow from the height rather than the other way round. */
.films {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
  gap: clamp(8px, 1.2vw, 16px);
}
.film-wrap { min-width: 0; }
.film { max-height: clamp(96px, 19vh, 190px); }
.film {
  position: relative; aspect-ratio: 16 / 9;
  border: 1px solid var(--faint);
  background: var(--wash);
}
.film iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
.film-name {
  margin: 6px 0 0;
  font-size: 12px; line-height: 1.35; color: var(--ink);
}
.film-name span { display: block; font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim); }

/* ---- the ask, under the doors -------------------------------------------- */

/* ⚠️ Tight on purpose. Six doors plus the signup is the whole home page, and
   the brief is that it fits without scrolling — on a 540px window the old
   rhythm put the email field and the button below the fold, which is the one
   conversion on the site. The vh terms do the work: roomy where there is room,
   and it closes up rather than overflowing where there is not. */
.ask { margin-top: clamp(6px, 2.2vh, 30px); max-width: 480px; }
.ask-kicker {
  margin: 0 0 clamp(3px, 0.7vh, 6px);
  font-size: 10px; font-weight: 600;
  letter-spacing: 0.24em; text-transform: uppercase; color: var(--dim);
}
.ask-words {
  margin: 0 0 clamp(5px, 1.4vh, 14px);
  font-family: var(--hand);
  font-size: clamp(13px, min(1.7vw, 2.6vh), 22px); line-height: 1.18; color: var(--ink);
}
.ask-words span { display: block; }

.ask-form { display: flex; flex-wrap: wrap; align-items: center; gap: clamp(5px, 1vh, 10px); }

/* The email field and its button, kept together on one line.
   Nick, 20 Sep 2026: "shorten the email entry box and put the button at the
   end of it so box and button are in line."

   A row of their own rather than two items in the form's flex — otherwise the
   button can wrap onto the next line on its own at an awkward width, which is
   exactly what "in line" rules out. `align-items: stretch` so the drawn box
   and the drawn button are the same height whatever the type does. */
.ask-row {
  display: flex; align-items: stretch; gap: 8px;
  flex: 0 1 auto; max-width: 100%;
}

/* The signup button. No background and no border of its own — the drawing is
   both, and the colour arrives as pencil strokes underneath the words. */
.ask-go {
  position: relative;
  flex: 0 0 auto;
  padding: clamp(11px, 1.4vh, 15px) clamp(18px, 2vw, 26px);
  background: transparent; border: 0; cursor: pointer;
  font-family: var(--hand); font-size: clamp(15px, min(1.5vw, 2.4vh), 19px);
  color: var(--ink);
}
.ask-go .go-word { position: relative; z-index: 1; display: inline-block; }
.ask-go .drawn-box { z-index: 0; }
.ask-go:hover .drawn-box path { stroke: var(--flame); }
.ask-go:hover .drawn-box line { stroke: var(--flame); }

/* The field and the box drawn round it. The input carries NO border of its
   own — the drawing is the border, and two would be visible as two. */
.drawn-field { position: relative; display: block; flex: 0 1 210px; min-width: 150px; }
.drawn-field input {
  width: 100%;
  padding: clamp(6px, 1.4vh, 15px) 16px;
  background: transparent; border: 0; outline: none;
  font-family: var(--hand); font-size: clamp(15px, min(1.5vw, 2.4vh), 19px);
  color: var(--ink);
}
.drawn-field input::placeholder { color: var(--faint); }
.drawn-box { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.drawn-field input:focus ~ .drawn-box path { stroke: var(--flame); }

.ask-small {
  flex-basis: 100%;
  font-family: var(--hand);
  font-size: clamp(12px, min(1.2vw, 2.1vh), 16px); line-height: 1.3; color: var(--dim);
}

/* Same reasoning as the doors: the box starts invisible, so anyone who reaches
   the field before it is drawn still gets an outline. */
.drawn-field input:focus,
.ask-go:focus-visible .go-word { animation: none !important; clip-path: none !important; }
.drawn-field input:focus ~ .drawn-box path,
.ask-go:focus-visible .drawn-box path {
  animation: none !important; stroke-dashoffset: 0 !important;
}
/* Focused before it is coloured: leave it uncoloured and keep the words dark,
   rather than showing pale words on cream. */
.ask-go:focus-visible .go-word { color: var(--ink) !important; }

/* ⚠️ A door whose animation has not started yet must still be REACHABLE.
   `write` is a clip-path and `draw-box` starts at a full dashoffset, so for the
   first few seconds a door is invisible — but it is a link, and a keyboard or
   a screen reader can reach it before it appears. This keeps the words present
   for anyone not looking at the screen, and the focus ring works throughout. */
.door:focus-visible .door-word { animation: none !important; clip-path: none !important; }
.door:focus-visible .door-box path { animation: none !important; stroke-dashoffset: 0 !important; }
/* The animation itself is INLINE on the path and on each line, exactly as the
   design has it — see the note above DOORS in app.js. The stylesheet must not
   also declare it, or the two can drift apart. */
.note > span {
  font-family: var(--hand);
  line-height: 1.32; color: var(--pencil);
}
.note .say-big   { font-size: clamp(14px, 1.2vw, 17px); line-height: 1.3; }
.note .say-small { font-size: clamp(13px, 1.1vw, 15px); line-height: 1.35; }
.note span span { display: block; }

/* ---- lists --------------------------------------------------------------- */

.rows { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--ink); }
.rows li, .row {
  display: flex; align-items: baseline; gap: 14px;
  padding: 10px 0; border-bottom: 1px solid var(--faint);
}
.rows .n, .row .n {
  width: 22px; flex: 0 0 auto;
  font-size: 10px; font-weight: 600; letter-spacing: 0.18em; color: var(--dim);
}
.rows .title, .row .title {
  flex: 1; min-width: 0;
  font-family: var(--display); font-weight: 500;
  font-size: clamp(16px, 1.4vw, 21px); line-height: 1.2;
}
.rows .title a:hover, .row .title a:hover { color: var(--deep); }
.rows .meta, .row .meta { font-size: 11px; letter-spacing: 0.1em; color: var(--dim); }
.row .when {
  width: 84px; flex: 0 0 auto;
  font-size: 10px; font-weight: 600; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--dim);
}
.row .where { flex: 1; min-width: 0; }
.row .where b { display: block; font-family: var(--display); font-weight: 500; font-size: clamp(15px, 1.3vw, 19px); line-height: 1.15; }
.row .where span { display: block; margin-top: 2px; font-size: 12px; color: var(--dim); }
.row .tickets {
  flex: 0 0 auto; font-size: 10px; font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase;
  border-bottom: 1px solid var(--faint);
}
.row .tickets:hover { color: var(--deep); border-color: var(--ink); }

/* ---- forms --------------------------------------------------------------- */

.form { display: flex; flex-wrap: wrap; gap: 8px; max-width: 460px; }
.form input, .form textarea {
  flex: 1; min-width: 190px; padding: 12px 14px;
  background: transparent; border: 1px solid var(--ink); color: var(--ink);
  font-family: var(--body); font-size: 13px; outline: none;
}
.form input:focus, .form textarea:focus { background: var(--wash); }
.form textarea { min-height: 96px; resize: vertical; flex-basis: 100%; }
.form .wide { flex-basis: 100%; }
.form-msg { margin: 10px 0 0; font-family: var(--hand); font-size: 17px; line-height: 1.4; }
.form-msg.bad { color: var(--deep); }

/* Off-screen, not display:none — a bot fills what it cannot see, and
   display:none is the first thing they learn to skip. */
.honey {
  position: absolute; left: -9999px; width: 1px; height: 1px;
  overflow: hidden;
}

/* ---- odds ---------------------------------------------------------------- */

/* ⚠️ LITERAL COLOURS, NOT THE TOKENS, and that is the cost of moving the flip
   from .pane to the body. --ink and --paper are now the SAME VALUE document
   wide, so `background: var(--ink); color: var(--paper)` painted cream on
   cream and this panel — the only "can't load" message there is — came out
   blank. Anything outside .pane that pairs the two tokens has to name its
   colours; there is no longer a contrast between them to rely on. */
.hard-fail {
  position: fixed; inset: auto 0 0 0;
  padding: 20px clamp(16px, 2.6vw, 34px);
  background: #33200F; color: #F2E3C7;
}
.hard-fail a { color: #F2E3C7; text-decoration: underline; }
.stale {
  position: fixed; left: 0; right: 0; top: 0; margin: 0;
  padding: 7px 10px; text-align: center;
  background: var(--flame); color: var(--paper);
  font-size: 10px; font-weight: 600; letter-spacing: 0.18em; text-transform: uppercase;
}

/* ---- the phone ----------------------------------------------------------- */

/* Below this width the two columns cannot both be read, so the photograph
   goes above the words rather than beside them, and the page is allowed to
   scroll. A fixed full-height layout on a phone fights the browser chrome. */
@media (max-width: 760px) {
  /* ⚠️ THE PHONE USES THE SAME FRAME AS EVERYTHING ELSE, from 21 Sep 2026.
     Nick: *"these all need to have the header and footer fixed on phone and
     content in the centre part scrolls vertically"*.

     This block used to say `html, body { overflow: auto; height: auto }` and
     `.shell { position: static; min-height: 100dvh }` — the whole page
     scrolled, header and index included.

     ⚠️ **That was not a phone compromise. It was built for a layout that no
     longer exists.** Nick, 19 Sep: *"I want the photo underneath on phone"* —
     the picture left the layer behind the words and became a 4:3 panel stacked
     BELOW them, which needs the page to scroll or the picture has nowhere to
     go. On 21 Sep he reversed that for every panel, and `render()` now sets
     `plate full` unconditionally, which is `position: absolute`. So nothing
     has been stacked below the words since — **the page went on scrolling to
     make room for a picture that had stopped being in the flow.**

     The rules further down that put `.plate` in the flow at 4:3 are dead for
     the same reason. They are left rather than deleted because the decision
     they serve was reversed once and could be again; if it is, this comment is
     where to start. */
  html, body { overflow: hidden; height: 100%; }
  /* On a phone the picture goes UNDERNEATH the words, not behind them.
     Nick, 19 Sep 2026: "I want the photo underneath on phone."

     So it comes back INTO the flow and the column stacks: words, then
     picture. Behind-the-words works on a wide screen because there is space
     to the right that the text never reaches; on a phone there is no such
     space, so a wash behind the buttons is just a photograph with writing
     on it. */
  .body { display: flex; flex-direction: column; gap: 18px; min-height: 0; }
  /* The middle is the only thing that moves. `min-height: 0` on both, or a
     flex item refuses to shrink below its content and the scroll never
     starts — it just pushes the index off the bottom of the screen. */
  .pane { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
  .plate {
    position: relative;
    top: auto; bottom: auto; right: auto;
    width: 100%;
    aspect-ratio: 4 / 3;
    /* Still softened at the top, so it joins the cream above it rather than
       starting with a hard edge. */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, rgba(0,0,0,0.6) 14%, #000 34%);
    mask-image: linear-gradient(to bottom, transparent 0%, rgba(0,0,0,0.6) 14%, #000 34%);
  }
  .plate img { opacity: 0.8; }   /* underneath, not under type — it can be stronger */

  /* ⚠️ HOME IS THE EXCEPTION, AND IT REVERSES A DECISION.
     
     Nick, 19 Sep 2026: *"I want the photo underneath on phone"* — which is the
     block above: the picture leaves the layer behind the words and becomes a
     4:3 panel stacked below them.
     
     Nick, 21 Sep 2026: *"photo/video to completely fill the screen on phone (if
     I draw the window to phone width the bottom quarter of the screen is the
     beige background colour)."* That is this. The earlier instruction still
     holds for every OTHER panel, and the reason it does is that those panels
     are text — a wash behind a tracklist on a narrow screen is a photograph
     with writing on it. Home is six buttons, and now pale ones. */
  .plate.full {
    position: absolute; inset: 0;
    width: auto; aspect-ratio: auto;
    -webkit-mask-image: none; mask-image: none;
  }
  /* The band has to be tall enough to be worth filling. Without this the words
     set the height and the picture is a strip behind them — which is the
     'bottom quarter is beige' Nick reported, in a different disguise. */
  body.photo-home .body { min-height: 68dvh; }
  .plate-empty { position: absolute; }
  .pane { justify-content: flex-start; }

  /* ⚠️ SUPERSEDED the same day by `.pane { flex: 1 1 auto }` above, which does
     this for every panel rather than only the ones with a foot. Kept because
     the reasoning is the reasoning for both, and because it names the symptom
     somebody will search for.

     THE PANE HAS TO GROW FOR A PANEL WITH A FOOT.
  
     Nick, 21 Sep 2026, on the Live page: *"move 'get notified about new dates'
     to the bottom, just above the line"*.
     
     `.panel-foot` is pushed down by `margin-top: auto`, which only does
     anything if the panel has spare height to push into. `.panel-fit` takes
     its height from `.pane` — and the rule a few lines above sets
     `.pane { flex: 0 0 auto }` on a phone, so the pane is exactly as tall as
     its words and there is no spare height at all. The signup therefore sat
     directly under the last gig row with the rest of the screen empty below
     it, while on a desktop it sat correctly on the bottom rule. **Same markup,
     same CSS, opposite result — which is why this was found by looking at a
     phone-width render and could not have been found by reading either file.**
     
     `.pane { flex: 0 0 auto }` was written when the picture was IN the flow
     below the words, as a 4:3 panel, and had to not be squashed. Every plate
     is `position: absolute` now (`.plate.full`, and `.plate-empty` above), so
     nothing is below the pane to protect any more — but it is scoped to panes
     that actually contain a fit panel rather than lifted wholesale, because
     the rule above is load-bearing for a layout that could come back.
     
     A browser without `:has()` ignores this and gets the old behaviour, which
     is wrong but not broken. */
  .standfirst { font-size: 9px; letter-spacing: 0.18em; }
}

