/* ============================================================================
   UNIÓN , lane L. 05 Lineup, schedule, sound.

   Owned by lane L. No other lane edits this file, and this file edits nothing
   else. Every value here is a var() off tokens.css: no hex, no font name, no
   easing curve and no duration is written in this file.

   ONE documented exception, the same one tokens.css carries: a mask gradient
   uses #000 / #0000 as its OPAQUE and TRANSPARENT stops. Those are alpha
   stencils, not colour, and nothing here is ever painted with them.

   Everything composes from the seven devices. Where a class below exists, it is
   because the LAYOUT is lane L's (a bill, a day board, a timeline), never
   because a device needed restyling.
   ============================================================================ */

/* ---------------------------------------------------------------------------
   Shared to the lane. The billing block, the day board and the phone schedule
   all set names and times in the same two voices, so they are declared once.
   --------------------------------------------------------------------------- */
.l-name{font-weight:900;font-stretch:var(--wdth-condensed);text-transform:uppercase;
  line-height:.92}
/* a time is read at a glance and never wraps, so it is tabular and fixed-width */
.l-time{font:900 var(--size-small)/1 var(--font);font-stretch:var(--wdth-condensed);
  font-variant-numeric:tabular-nums;letter-spacing:.02em;white-space:nowrap}

/* the replay button every scroll-driven piece carries, so it can be judged in a
   still. Deliberately NOT a pill: the one pill is the CTA, and a documentation
   control must not compete with it. A square black bar, the system's own shape. */
.l-replay{appearance:none;cursor:pointer;display:inline-flex;align-items:center;
  gap:var(--s3);border:var(--rule) solid var(--black);background:transparent;
  color:var(--ink);padding:var(--s3) var(--s4);min-height:var(--tap-min);
  border-radius:var(--radius-slab);
  font:900 13px/1 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase;letter-spacing:.1em;
  transition:background var(--dur-state) var(--ease-out),
             color var(--dur-state) var(--ease-out)}
.l-replay:hover,.l-replay.is-hover{background:var(--black);color:var(--cream)}
/* on any black field, not just a .spec-stage--black. The sound board's slab is a
   black slab on a CORAL stage, so a stage-scoped selector missed it and the
   button rendered black-on-black: present, invisible, and only caught by looking. */
.on-dark .l-replay,.spec-stage--black .l-replay,
.l2-slab .l-replay,.l1-slab .l-replay{border-color:var(--cream);color:var(--cream)}
.on-dark .l-replay:hover,.spec-stage--black .l-replay:hover,
.l2-slab .l-replay:hover,.l1-slab .l-replay:hover{background:var(--cream);color:var(--black)}

/* the stage swatch every list uses to say WHICH stage, as a rule under the row
   rather than a filled block behind it. A cut-off block of colour is a swatch,
   which is the thing the brief forbids; a rule is the poster's own device. */
.l-stagerule{display:block;height:var(--rule);background:var(--stage);border:0;margin:0}


/* ===========================================================================
   L1 , BILLING POSTER BLOCK        from set-04 L1 (93)
   The black slab between two ribbon GUTTERS that bleed off the page edge.

   PORTED FROM THE SCREENSHOT, which is the thing that was actually approved.

   THE GUTTERS ARE GONE, and this is the correction that mattered. Louis's
   screenshot (18.03.20) shows the black slab on CLEAN CREAM with no ribbons
   either side: the gutter SVGs had not loaded when he captured it. What he
   starred is what he SAW. He has since said the pattern work is falling short,
   so reading the lab's source and restoring gutters he never looked at was me
   porting the CODE instead of the APPROVED ARTEFACT. The component is the clean
   slab. The guttered build is kept, clearly unapproved, as L-bill-gutters.

   EVERYTHING BELOW IS MEASURED OFF HIS SCREENSHOT, as a ratio to the SLAB, not
   to the viewport. Scale 3194/1440 = 2.218x. Slab 385..1618 x 188..895 image px,
   so 1233 x 707, ratio 1.744. Against that slab width:
     headline span 66.4%   headline cap 7.3%
     mid-tier cap 2.43%    small-tier cap 1.38%
     pink rule span 91.5%  button 23.0% wide, height 4.38% of slab width
   The slab is a container, so every one of those is set in cqw and the type
   scales to the SLAB at any viewport. Sizing off vw was why my first build came
   out a step small at 57% span instead of his 66%.
   =========================================================================== */
/* the slab needs a REAL column, not a shrink-to-fit one. `display:grid` with
   `justify-content:center` and no template gave it an auto column, so the slab
   collapsed to its narrowest content and every cqw size collapsed with it. The
   gate passed that render; only looking at the PNG caught it. */
/* THE CONTAINER IS THE WRAP, NOT THE SLAB. `container-type:inline-size` resolves
   cqw against the element's CONTENT box, so putting it on the slab made every
   cqw a fraction of the slab MINUS its own 5cqw side padding. The headline was
   sized off 1060px while being measured against the 1204px slab: 88% instead of
   66%. Measured, not guessed: computed font-size came back 80.77px, which is
   7.62% of 1060, not of 1204.
   The wrap carries the container, the slab fills it, so cqw is a true fraction
   of the slab's own width and every ratio below is the one measured off the
   approved shot. */
.l1-wrap{display:block;container-type:inline-size;
  width:100%;max-width:1233px;margin-inline:auto}
.l1-slab{width:100%}
/* ---------------------------------------------------------------------------
   THE GUTTER, used ONLY by #L-bill-proposal, which is not the component.
   Kept here so the unapproved variant can be shown and judged, and so nothing
   is lost if Louis rules that the bill does want pattern beside it.
   --------------------------------------------------------------------------- */
.l1-wrap--gut{grid-template-columns:minmax(0,123px) minmax(0,980px) minmax(0,123px)}
.l1-gut{position:relative;overflow:hidden;min-height:100%;
  -webkit-mask-image:linear-gradient(90deg,#000 0 82%,#0000);
  mask-image:linear-gradient(90deg,#000 0 82%,#0000)}
.l1-gut--r{-webkit-mask-image:linear-gradient(270deg,#000 0 82%,#0000);
  mask-image:linear-gradient(270deg,#000 0 82%,#0000)}
.l1-gut u-svg{position:absolute;display:block;width:var(--w);left:var(--x);top:var(--y);
  transform:translate(-50%,-50%) rotate(var(--r,0deg))}
@media (max-width:900px){.l1-wrap--gut{grid-template-columns:1fr}
  .l1-gut{display:none}}
.l1-gut u-svg svg{width:100%;height:auto;overflow:visible}

/* the slab's own proportion, 1.744:1 measured off the screenshot, held by its
   padding rather than an aspect-ratio so a longer bill can still grow. */
.l1-slab{background:var(--black);color:var(--cream);text-align:center;
  padding:6.3cqw 4.2cqw 5.6cqw;border-radius:var(--radius-slab)}
.l1-eyebrow{margin:0 0 4.6cqw}
/* the bill's three tiers, every size a ratio to the SLAB. Cream on black, the
   only reversed pair the system allows for information.
   MEASURED: his headline spans 66.4% of the slab and its cap is 7.3% of the slab
   width. At Archivo 900/62% a cap is about .72em, so 7.3/.72 = 10.1cqw. */
/* MEASURED, after getting this wrong twice by measuring the wrong box.
   .l1-huge is a block-level <p>, so its getBoundingClientRect is the full slab
   width whatever the type does: it reported 88 to 90% at every size I tried and
   sent me chasing the number downward. The thing to measure is the TEXT, via a
   Range over the name. Measured that way: 5.62cqw gave a 35.1% text span, so
   5.62 x (66.4 / 35.1) = 10.63cqw. My first derivation of 10.1 was nearly right
   and I "corrected" it twice on a bad measurement. */
.l1-huge{margin:0;font-size:10.63cqw;line-height:.9;white-space:nowrap}
/* the other two tiers carried the same estimate error, so they take the same
   correction as the headline, so the three tiers keep his ratio to each other,
   which is what the bill's hierarchy actually is. */
.l1-mid{margin:2.6cqw 0 0;font-size:3.59cqw;line-height:1}
.l1-small{margin:2.0cqw 0 0;font-size:2.02cqw;line-height:1.15}
/* below the breakpoint the slab is the whole width and cqw sizing would make the
   headline absurd, so the phone gets its own scale and is allowed to wrap. */
@media (max-width:900px){
  .l1-huge{font-size:clamp(34px,9vw,52px);white-space:normal}
  .l1-mid{font-size:clamp(16px,4.4vw,24px)}
  .l1-small{font-size:clamp(12px,3vw,16px)}
  .l1-slab{padding:var(--s6) var(--s5)}
  /* MEASURED at 390: the tier boxes butt exactly (870 to 870), but .l1-huge
     carries line-height .9, so its glyphs paint BELOW its own box and land on
     the tier under it. The boxes pass a box check and the render still reads as
     text on text, which is what the overlap gate caught.
     Real space between the tiers, not a tighter box. */
  .l1-huge{padding-bottom:.12em}
  .l1-mid{margin-top:var(--s5)}
  .l1-small{margin-top:var(--s5)}
}
.l1-mid,.l1-small{display:flex;flex-wrap:wrap;justify-content:center;
  align-items:center;gap:.3em .7em}
.l1-dot{width:.34em;height:.34em;background:var(--primary);border-radius:var(--radius-round);
  flex:none;display:block}
/* the pink rule that draws across on scroll. transform-origin is the centre,
   because it grows out from the middle of the bill the way a rule is ruled. */
/* MEASURED: the rule spans 91.5% of the slab and is 2px, and it is PRESENT AT
   REST in his shot. My first build left it at scaleX(0) until the scroll fired,
   so the rest state had no rule at all, which is a different composition. The
   draw animates TO this, it does not create it. */
/* 91.5% of the SLAB, not of the padded content box: width:91.5% measured back
   at 80.6% because the slab's 5cqw side padding had already been taken off. Set
   in cqw so it is a fraction of the slab itself, as measured. */
.l1-rule{height:var(--rule);background:var(--primary);border:0;
  width:91.5cqw;margin:3.4cqw auto 1.9cqw;transform-origin:50% 50%}
.l1-more{margin:0;color:var(--stage-corner)}
.l1-cta{display:flex;flex-wrap:wrap;gap:var(--s4) 3cqw;align-items:center;
  justify-content:center;margin-top:3.6cqw}
/* THE BILLING BUTTON. A SQUARE pink slab: hard corners, NO arrow, condensed 900
   caps, generous padding. Measured off his shot at 23.0% of the slab wide and a
   height of 4.38% of the slab width.
   This is NOT the system pill and must not be given the pill's shape. The
   brief's "one pill" rule governs NEW unstarred parts; a starred component keeps
   the button Louis approved, and he approved a square slab. Swapping it for the
   pill was me applying a general rule over a specific approval. */
.l1-btn{display:inline-flex;align-items:center;justify-content:center;
  appearance:none;border:0;cursor:pointer;text-decoration:none;
  width:23cqw;min-width:180px;min-height:var(--tap-min);height:4.38cqw;padding:0 2.4cqw;
  background:var(--primary);color:var(--black);border-radius:var(--radius-slab);
  font:900 2cqw/1 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase;letter-spacing:.04em;
  transition:transform var(--dur-squash) var(--ease-verb-squash)}
.l1-btn:hover,.l1-btn.is-hover{transform:scale(1.03,.95)}
.l1-btn:active,.l1-btn.is-active{transform:scale(.97,1.04)}
.l1-btn:focus-visible,.l1-btn.is-focus{outline:var(--focus-width) solid var(--focus-colour-on-dark);
  outline-offset:var(--focus-offset)}
@media (max-width:900px){.l1-btn{width:auto;font-size:15px;padding:var(--s4) var(--s6)}}
/* the names start hidden ONLY when the animation will run. js adds this class,
   so with js dead or GSAP unreachable the bill simply reads. */
.l1-slab.is-armed .l1-name{opacity:0}
.l1-name{display:inline-block}

@media (max-width:900px){
  /* the phone bill. The dots go, because at this size they crowd the wrap, and
     the tiers get more air between the names.
     NOTE: the type sizes live in the ONE phone block above. This block used to
     re-set .l1-huge as well, which silently overrode it and put the headline
     back to 12vw: at 390 that overlapped the tier below it, and the overlap
     check caught it. One property, one place. */
  .l1-dot{display:none}
  .l1-mid,.l1-small{gap:.25em 1.1em}
}


/* ===========================================================================
   L2 , THE SOUND BOARD             from set-04 L2 (85)
   The headliner slot as a departure board, flipping through the SOUNDS.
   Measured off the starred shot: the slot's word is PINK on black at display
   scale, the sub-line is cream, a pink rule sits under it, then a fact row in
   the FESTIVAL INFO poster's micro-label-over-value form, then the pill.
   =========================================================================== */
.l2-slab{background:var(--black);color:var(--cream);border-radius:var(--radius-slab);
  padding:clamp(var(--s5),4vw,var(--s8));max-width:1100px}
.l2-hd{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3) var(--s5);
  margin-bottom:clamp(var(--s5),3vw,var(--s7))}
/* the slot clips the outgoing word, but a 1em box with overflow:hidden CUTS THE
   ACUTE off Reggaetón: at these line-heights the accent sits above the line box.
   Lane F hit the same thing on every display voice. The slot is given room above
   and the clip is inset by that much, so the flip still clips and the Ó survives. */
.l2-slot{font-size:var(--size-display);height:1.18em;padding-top:.18em;
  overflow:hidden;position:relative}
/* the flip: one word sits in the slot at a time. The slot is exactly 1em tall so
   the outgoing word is clipped by the slot, not hidden by a mask. */
.l2-word{position:absolute;left:0;top:.18em;width:100%;
  color:var(--primary);white-space:nowrap}
.l2-word[data-on="0"]{position:relative;top:0}
.l2-sub{margin:var(--s3) 0 0;font-size:clamp(17px,2vw,26px)}
.l2-rule{height:var(--rule);background:var(--primary);border:0;
  margin:clamp(var(--s5),3vw,var(--s6)) 0 clamp(var(--s4),2.4vw,var(--s5));max-width:760px}
/* the fact row, straight off the FESTIVAL INFO poster: value large, micro label
   under it. Never the other way round. */
.l2-facts{display:flex;flex-wrap:wrap;gap:var(--s5) clamp(var(--s6),4vw,var(--s8));
  margin:0 0 clamp(var(--s5),3vw,var(--s7));padding:0;list-style:none}
.l2-facts li{margin:0}
.l2-facts b{display:block;font:900 clamp(26px,3.2vw,44px)/1 var(--font);
  font-stretch:var(--wdth-condensed);font-variant-numeric:tabular-nums}
.l2-facts span{display:block;margin-top:var(--s2);opacity:.72}
.l2-cta{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s4) var(--s5)}
/* the sound board's button. Same failure as L1: I had swapped it for the system
   pill. His starred shot shows a SQUARE pink slab with a black arrow inside it,
   so that is what it is. Square corners, arrow kept, no pill radius. */
.l2-btn{display:inline-flex;align-items:center;gap:var(--s5);
  appearance:none;border:0;cursor:pointer;text-decoration:none;
  padding:var(--s4) var(--s5);min-height:var(--tap-min);
  background:var(--primary);color:var(--black);border-radius:var(--radius-slab);
  font:900 clamp(17px,1.8vw,22px)/1 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase;letter-spacing:.03em;
  transition:transform var(--dur-squash) var(--ease-verb-squash)}
.l2-btn svg{width:30px;height:18px;flex:none}
.l2-btn svg path{fill:currentColor;stroke:none}
.l2-btn:hover,.l2-btn.is-hover{transform:scale(1.03,.95)}
.l2-btn:active,.l2-btn.is-active{transform:scale(.97,1.04)}
.l2-btn:focus-visible,.l2-btn.is-focus{outline:var(--focus-width) solid var(--focus-colour-on-dark);
  outline-offset:var(--focus-offset)}


/* ===========================================================================
   L3 , ANNOUNCEMENT SWEEP          from set-04 L3 (87)
   A ribbon travels along each line and leaves the name PAINTED behind it.
   The name is revealed by a clip that follows the stroke, so the stroke is
   doing the painting rather than sitting decoratively beside a fade.
   =========================================================================== */
/* MEASURED off his 18.06.20: the rows span 54% of the stage's width (his 1719 of
   3176), NOT most of it; the stage tag is right-aligned at the END of the row,
   flush with the rule above it, in a column of its own; and NO ribbon is visible
   at rest. The ribbon only exists while it sweeps. */
.l3-frame{max-width:54%;min-width:min(100%,560px)}
/* his tag sits at the row's end in its own column, so the row is a two-column
   grid rather than a flex chain that lets the tag trail the name. */
.l3-line{position:relative;overflow:hidden;
  display:grid;grid-template-columns:1fr auto;align-items:baseline;
  gap:var(--s3) var(--s4);padding:clamp(var(--s3),1.6vw,var(--s4)) 0;
  border-bottom:var(--rule) solid var(--black)}
.l3-line:first-of-type{border-top:var(--rule) solid var(--black)}
.l3-nm{margin:0;font-size:clamp(22px,3.4vw,50px);
  /* the paint clip. At rest (armed) it is shut; the sweep opens it. */
  clip-path:inset(0 0 0 0)}
.l3-line .d-label{justify-self:end}
.l3-frame.is-armed .l3-nm{clip-path:inset(0 100% 0 0)}
.l3-frame.is-armed .d-label{opacity:0}
/* THE BOUNDARY RULE, by geometry not luck: the row CLIPS (overflow:hidden) and
   the stroke is parked a full row-width off the left edge, so at rest there is
   nothing to see. The previous version gave the stroke a fixed 15% width at
   left:0, which parked four ribbons in plain sight on his empty stage. */
.l3-swipe{position:absolute;inset:0;pointer-events:none;z-index:var(--z-raise);
  /* GSAP's xPercent writes this same transform channel, so a CSS translateX here
     is silently overwritten and the stroke ends parked over the name. The parked
     state is declared as a CUSTOM PROPERTY GSAP also drives, so the two agree at
     rest, mid-sweep and at the end. */
  transform:translateX(var(--swx,-135%))}
.l3-swipe u-svg{position:absolute;left:50%;top:50%;height:68%;width:auto;
  transform:translate(-50%,-50%);display:block}
.l3-swipe svg{height:100%;width:auto;overflow:visible}
.l3-bar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s4) var(--s5);
  margin-top:clamp(var(--s5),3vw,var(--s6))}
/* his PLAY THE ANNOUNCEMENT is a FILLED PINK SQUARE SLAB, not an outlined bar.
   The shared .l-replay is the documentation control; this one is the component's
   own button and his shot shows it filled. */
.l3-bar .l-replay{background:var(--primary);color:var(--black);border-color:var(--primary)}
.l3-bar .l-replay:hover,.l3-bar .l-replay.is-hover{background:var(--black);color:var(--cream);
  border-color:var(--black)}


/* ===========================================================================
   E1 , THE DAY BOARD               from set-05 E1 (91)
   The SET TIMES poster, live. Three columns, each capped in its stage colour,
   the playing row INVERTED to black with the soundwave running as an equaliser.

   Measured off the starred shot: each column has a heavy black border, the cap
   carries the stage name left and the running time right, rows are separated by
   a hairline, past rows are dimmed, the playing row is black with a stage-colour
   equaliser at its right edge, and a black foot bar runs the full width under
   all three columns.
   =========================================================================== */
.e1-hd{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s3) var(--s5);
  margin-bottom:clamp(var(--s5),3vw,var(--s6))}
/* MEASURED off his 18.07.17: SET TIMES is the tall CONDENSED display voice, not
   the wide one, and its cap height is 5.9% of the board's own width (his 176 of
   2977). The .t-wide voice rendered it a step small AND a step wide. Sized from
   the board rather than the viewport, because this page carries a sidebar the
   lab did not. */
.e1-ttl{margin:0;font-size:clamp(46px,9.4cqw,118px)}
.spec-stage:has(.e1-hd){container-type:inline-size}
.e1-cols{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(var(--s4),2vw,var(--s5))}
.e1-col{border:var(--rule-heavy) solid var(--black);border-radius:var(--radius-slab);
  overflow:hidden;min-width:0}
.e1-cap{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  padding:var(--s3) var(--s4);background:var(--stage);color:var(--black);
  border-bottom:var(--rule-heavy) solid var(--black)}
.e1-cap h4{margin:0;font-size:clamp(16px,1.6vw,22px)}
/* MEASURED off his 18.07.17: the cap's range is a small TRACKED MICRO label in
   caps, right aligned on the cap's own line, not the condensed tabular time
   voice the rows use. It is a caption on the column, not a set time. */
.e1-cap .l-time{margin-left:auto;text-align:right;
  font:700 var(--size-micro)/1 var(--font);font-stretch:100%;
  letter-spacing:var(--track-micro);text-transform:uppercase}
.e1-rows{margin:0;padding:0;list-style:none}
.e1-row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;
  gap:var(--s4);padding:var(--s3) var(--s4);min-height:var(--tap-min);
  border-top:1px solid var(--hairline)}
.e1-row:first-child{border-top:0}
.e1-row b{margin:0;font:700 var(--size-small)/1.2 var(--font);
  letter-spacing:.06em;text-transform:uppercase;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* a set that has already finished. Dimmed, not struck: it still happened. */
.e1-row.is-past{opacity:.42}
/* the set that is on NOW: the row inverts and the wave runs in the stage colour. */
.e1-row.is-live{background:var(--black);color:var(--cream)}
.e1-row.is-live .d-wave{--dc:var(--stage)}
.e1-foot{display:flex;flex-wrap:wrap;justify-content:space-between;gap:var(--s3) var(--s5);
  margin-top:clamp(var(--s4),2vw,var(--s5));padding:var(--s4) var(--s5);
  background:var(--black);color:var(--cream);border-radius:var(--radius-slab)}

@media (max-width:820px){
  /* three columns at 390 would be 90px each, which cannot hold a name. The board
     stacks, and each column keeps its cap so the stage is never lost. */
  .e1-cols{grid-template-columns:1fr}
}


/* ===========================================================================
   E2 , TIMELINE RIVER              from set-05 E2 (88)
   Each set is a fat round-ended RIBBON BAR (.d-ribbon--bar) on its stage's
   lane, and the NOW line travels across them.

   Measured off the starred shot: the frame is black, the lane label is a solid
   block in the stage colour at the left, the bars are round-ended and the NOW
   line is a thin cream vertical with a small cream flag at the top.

   MEASURED off his 18.07.08, on a board 2975px wide: each bar is 72px tall and
   the lane pitch is 116px, so the whole three-lane block is 304px. That is
   24px of bar and 108px of board per 1000px of board WIDTH. The lab sized these
   off the viewport and had no sidebar, so vw came out roughly twice too fat
   here; they are driven off the board's own width instead (cqw on .e2-inner),
   which is the same proportion at any container. These are SLIM ribbons, not
   pills: a pill is as tall as its type wants, a ribbon is as tall as the board
   says.
   =========================================================================== */
.e2-frame{background:var(--black);color:var(--cream);border-radius:var(--radius-slab);
  overflow:hidden;max-width:100%}
.e2-scroll{overflow-x:auto;overscroll-behavior-x:contain}
.e2-inner{position:relative;min-width:900px;padding-bottom:var(--s2);
  container-type:inline-size;
  --e2-bar:2.42cqw;    /* his 72 of 2975 */
  --e2-gap:1.48cqw}    /* his 116 pitch minus the 72 bar */
.e2-ruler{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;
  border-bottom:var(--rule) solid var(--cream);margin-left:var(--e2-lbl,120px)}
.e2-ruler span{padding:var(--s2) var(--s3);border-left:1px solid var(--hairline-on-dark);
  font:700 var(--size-micro)/1.2 var(--font);letter-spacing:.12em}
.e2-lane{display:grid;grid-template-columns:var(--e2-lbl,120px) 1fr;align-items:stretch}
.e2-lbl{display:flex;align-items:center;padding:var(--s3) var(--s4);
  background:var(--stage);color:var(--black);
  font:900 clamp(11px,1.1vw,14px)/1.05 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase}
/* the lane's own strip: one bar's height plus the pitch gap, so three lanes
   stack to his 108-per-1000. No extra margin: the gap IS the pitch. */
.e2-track{position:relative;height:calc(var(--e2-bar) + var(--e2-gap));margin:0}
/* each set: a fat round-ended bar, positioned by its start and width along the
   day. --a and --b are fractions of the track, written by the markup. */
.e2-set{position:absolute;top:50%;height:var(--e2-bar);transform:translateY(-50%);
  left:calc(var(--a) * 100%);width:calc((var(--b) - var(--a)) * 100%);
  display:flex;align-items:center;padding:0 var(--s4);
  appearance:none;border:0;cursor:pointer;text-align:left;
  background:var(--stage);color:var(--black);border-radius:var(--radius-round);
  /* his bar type is ~32% of the bar's height, so it rides the bar rather than
     setting it. Floored at 10px so a slim bar never becomes unreadable. */
  font:900 clamp(10px,calc(var(--e2-bar) * .44),14px)/1 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase;letter-spacing:.02em;
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis;
  transition:transform var(--dur-bounce) var(--ease-verb-bounce)}
/* it SWELLS when you reach for it, which is what the starred one does. It does
   not lift and it does not glow. */
.e2-set:hover,.e2-set.is-hover{transform:translateY(-50%) scale(1.015,1.14);z-index:var(--z-raise)}
.e2-set:focus-visible,.e2-set.is-focus{outline:var(--focus-width) solid var(--focus-colour-on-dark);
  outline-offset:var(--focus-offset);z-index:var(--z-raise)}
.e2-set.is-selected{box-shadow:inset 0 0 0 var(--rule-heavy) var(--black)}
/* a set that clashes with a selected one, read by the eye down a vertical */
.e2-set.is-error{box-shadow:inset 0 0 0 var(--rule-heavy) var(--black);opacity:.55}
.e2-now{position:absolute;top:0;bottom:var(--s5);width:var(--rule);
  background:var(--cream);left:var(--now,50%);z-index:var(--z-raise);pointer-events:none}
.e2-now span{position:absolute;top:0;left:50%;transform:translateX(-50%);
  background:var(--cream);color:var(--black);padding:2px var(--s2);
  font:900 10px/1.4 var(--font);font-stretch:var(--wdth-condensed);letter-spacing:.1em}
.e2-hint{margin:0;padding:var(--s3) var(--s5) var(--s4);opacity:.7}

@media (max-width:700px){
  .e2-inner{--e2-lbl:86px}
  /* at 390 the shortest set was 4px of a 82px label, i.e. a bar with a sliver of
     a word in it. The track is given more room so every bar can hold its name;
     the frame scrolls sideways on its own, so the page still never pans.
     The bar type is pinned at 11px here rather than left on the cqw ramp: on a
     1180px board the ramp gave 14px and the shortest set (13% of the track)
     could not hold its name. Pinning the type fits the name without widening
     the board, which would only push more bars past the scroll window. */
  .e2-inner{min-width:1180px}
  .e2-set{font-size:11px}
}


/* ===========================================================================
   E7 , PHONE SCHEDULE, STICKY TABS from set-05 E7 (81)
   The stage tabs are label-in-slabs that take the stage's colour when chosen,
   and the list re-keys in from the side rather than fading.

   Measured off the starred shot: the whole thing has one heavy black border, the
   tab strip is three equal cells divided by black rules, the chosen tab is
   filled in its stage colour and the others are the cream ground, past rows are
   dimmed, and the live row is black.
   =========================================================================== */
.e7-phone{border:var(--rule-heavy) solid var(--black);border-radius:var(--radius-slab);
  max-width:420px;overflow:hidden;background:var(--cream);color:var(--black)}
.e7-tabs{display:grid;grid-template-columns:repeat(3,1fr);
  position:sticky;top:0;z-index:var(--z-raise);
  border-bottom:var(--rule-heavy) solid var(--black);background:var(--cream)}
.e7-tab{appearance:none;cursor:pointer;border:0;
  border-left:var(--rule-heavy) solid var(--black);
  background:transparent;color:var(--black);
  padding:var(--s3) var(--s2);min-height:var(--tap-min);
  font:900 clamp(10px,2.6vw,13px)/1.1 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase;letter-spacing:.02em;
  transition:background var(--dur-state) var(--ease-out)}
.e7-tab:first-child{border-left:0}
.e7-tab[aria-selected="true"],.e7-tab.is-selected{background:var(--stage)}
.e7-tab:focus-visible,.e7-tab.is-focus{outline:var(--focus-width) solid var(--focus-colour);
  outline-offset:calc(var(--focus-offset) * -1)}
.e7-list{margin:0;padding:0;list-style:none}
.e7-list li{display:grid;grid-template-columns:auto 1fr auto;align-items:center;
  gap:var(--s4);padding:var(--s4);min-height:var(--tap-min);
  border-top:1px solid var(--hairline)}
.e7-list li:first-child{border-top:0}
.e7-list b{margin:0;font:700 var(--size-small)/1.2 var(--font);
  letter-spacing:.06em;text-transform:uppercase;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.e7-list li.is-past{opacity:.42}
.e7-list li.is-live{background:var(--black);color:var(--cream)}
.e7-list li.is-live .d-wave{--dc:var(--stage)}
/* the re-key: the list arrives from the side. Not a fade. */
.e7-list.is-keying{animation:e7-key var(--dur-drop) var(--ease-verb-drop)}
@keyframes e7-key{from{transform:translateX(var(--s6));opacity:0}
  to{transform:translateX(0);opacity:1}}
@media (prefers-reduced-motion:reduce){.e7-list.is-keying{animation:none}}


/* ===========================================================================
   S1 , THE PLAYLIST SLAB           from set-10 S1 (88)
   A black poster slab carrying a bar meter that only moves WHILE PLAYING, with
   the pink progress ribbon running edge to edge.

   Measured off the starred shot: the slab is split, player left and a cream
   track list right; the play control is a square pink block; the meter is a long
   multi-coloured bar row sitting on a common baseline; the progress ribbon is
   square-ended, edge to edge, with a cream playhead; times sit under its ends.
   =========================================================================== */
.s1-slab{display:grid;grid-template-columns:1fr minmax(0,300px);
  background:var(--black);color:var(--cream);border-radius:var(--radius-slab);
  overflow:hidden;max-width:1100px}
.s1-player{padding:clamp(var(--s5),3vw,var(--s6));min-width:0}
.s1-hd{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3) var(--s4);
  margin-bottom:clamp(var(--s5),3vw,var(--s7))}
/* the play control: a square block in the primary, never a round icon button.
   Square is the system's shape and this is not the one pill. */
.s1-play{appearance:none;cursor:pointer;border:0;border-radius:var(--radius-slab);
  width:var(--s8);height:var(--s8);min-width:var(--tap-min);min-height:var(--tap-min);
  background:var(--primary);color:var(--black);flex:none;
  display:grid;place-items:center;
  transition:transform var(--dur-squash) var(--ease-verb-squash)}
.s1-play:hover,.s1-play.is-hover{transform:scale(1.05,.93)}
.s1-play:active,.s1-play.is-active{transform:scale(.94,1.06)}
.s1-play:focus-visible,.s1-play.is-focus{outline:var(--focus-width) solid var(--focus-colour-on-dark);
  outline-offset:var(--focus-offset)}
.s1-play svg{width:22px;height:22px;fill:currentColor}
.s1-play .s1-play__pause{display:none}
.s1-play[aria-pressed="true"] .s1-play__pause{display:block}
.s1-play[aria-pressed="true"] .s1-play__go{display:none}
.s1-track-t{margin:0;font-size:clamp(26px,3.4vw,46px)}
.s1-artist{margin:var(--s2) 0 0;color:var(--primary);
  font:700 var(--size-micro)/1.4 var(--font);letter-spacing:var(--track-micro);
  text-transform:uppercase}
/* the long meter. A .d-wave--meter is three to five bars; this is the same
   device grammar stretched to a full waveform row, so it stays .d-wave and only
   the count changes. Bars are multi-coloured, as on the starred shot. */
/* the starred meter fills a good band of the slab; --s8 (72px) rendered short
   against the track title, so it is given the starred proportion. */
.s1-meter{margin-top:clamp(var(--s5),3vw,var(--s6));height:var(--s9);gap:3px;width:100%}
.s1-meter i{flex:1 1 0;width:auto}
.s1-meter i:nth-child(3n){background:var(--coral)}
.s1-meter i:nth-child(4n){background:var(--stage-corner)}
.s1-meter i:nth-child(5n){background:var(--primary)}
/* .d-wave--meter only profiles five bars, so a twenty-five bar row came out flat.
   The starred one is a WAVEFORM: varied heights on a common baseline. Seven
   levels cycled across the row give that without a fixed pattern reading. */
.s1-meter i:nth-child(7n+1){transform:scaleY(.52)}
.s1-meter i:nth-child(7n+2){transform:scaleY(.78)}
.s1-meter i:nth-child(7n+3){transform:scaleY(.95)}
.s1-meter i:nth-child(7n+4){transform:scaleY(.62)}
.s1-meter i:nth-child(7n+5){transform:scaleY(.86)}
.s1-meter i:nth-child(7n+6){transform:scaleY(.44)}
.s1-meter i:nth-child(7n){transform:scaleY(.70)}
/* stopped: the row holds its profile at a lower level. NO opacity change: the
   bars are pink, coral and yellow, and fading them over the black slab turned
   the whole row a muddy olive. It reads as stopped because it is not moving. */
.s1-meter.is-paused i{animation:none}
/* .d-wave--eq staggers five delays, so twenty-five bars would pulse in five
   locked groups. Seven delays across the row break that up into a waveform that
   never visibly repeats. */
.s1-meter.d-wave--eq i:nth-child(7n+1){animation-delay:0s}
.s1-meter.d-wave--eq i:nth-child(7n+2){animation-delay:.09s}
.s1-meter.d-wave--eq i:nth-child(7n+3){animation-delay:.21s}
.s1-meter.d-wave--eq i:nth-child(7n+4){animation-delay:.33s}
.s1-meter.d-wave--eq i:nth-child(7n+5){animation-delay:.14s}
.s1-meter.d-wave--eq i:nth-child(7n+6){animation-delay:.27s}
.s1-meter.d-wave--eq i:nth-child(7n){animation-delay:.38s}
/* the progress ribbon, edge to edge. Square ends, because it runs to the slab's
   own edges and a round cap there would read as a pill. */
.s1-prog{position:relative;height:var(--s3);margin-top:var(--s4);
  background:var(--hairline-on-dark);border-radius:var(--radius-slab)}
.s1-prog i{position:absolute;left:0;top:0;bottom:0;width:calc(var(--p,.35) * 100%);
  background:var(--primary);border-radius:var(--radius-slab)}
.s1-prog b{position:absolute;top:-3px;bottom:-3px;width:var(--rule-heavy);
  left:calc(var(--p,.35) * 100%);background:var(--cream)}
.s1-times{display:flex;justify-content:space-between;margin-top:var(--s2)}
/* the track list: cream, so it reads as a printed list stuck to the slab. It must
   not run past the slab's foot, or the black field stops being the frame. */
.s1-list{margin:0;padding:0;list-style:none;background:var(--cream);color:var(--black);
  border-left:var(--rule-heavy) solid var(--black);
  align-self:stretch;display:flex;flex-direction:column}
.s1-list li:last-child{flex:1 1 auto}
/* the bullet meter in a track row. .d-wave--sm is 22px tall with 5px bars, which
   at three bars renders as flecks; the starred rows carry a readable little
   meter, so the row's own wave is given its proper size back. */
.s1-list .d-wave{height:18px;gap:3px;flex:none}
.s1-list .d-wave i{width:4px}
/* MEASURED off his 17.53.41: an idle row carries a THREE-DOT glyph, three equal
   small squares at full weight, not three faded slivers. The shared .is-idle
   profile (scaleY .15, opacity .45) rendered as flecks at this size, so the row
   glyph gets its own: equal stubs, full opacity. The playing row keeps the
   staggered bars. */
/* MEASURED off his 17.53.41 at 8x: an idle row's glyph is three BLACK SQUARES
   SIDE BY SIDE, level with the row. Ours were pink slivers wrapping into a
   vertical stack, because the row's wave inherited --dc (the primary) and the
   flex was free to wrap in a 18px-tall box. */
/* both row states are BLACK: the idle three dots and the playing row's three
   staggered bars. Same device, two states, one colour. */
/* the row glyph is a <span>, so `.s1-list span{display:block}` below was winning
   over `.d-wave{display:flex}` and stacking the three marks into one tall bar.
   That is why the glyph read as a sliver rather than his three dots. */
.s1-list .d-wave{display:flex;flex-wrap:nowrap;align-items:center;--dc:var(--black)}
.s1-list .d-wave--meter.is-idle i{transform:none;opacity:1;width:5px;height:5px}
.s1-list__hd{padding:var(--s4);border-bottom:var(--rule) solid var(--black)}
.s1-list button{width:100%;appearance:none;cursor:pointer;border:0;text-align:left;
  display:grid;grid-template-columns:auto 1fr;align-items:center;gap:var(--s4);
  padding:var(--s3) var(--s4);min-height:var(--tap-min);
  border-top:1px solid var(--hairline);background:transparent;color:var(--black);
  transition:background var(--dur-state) var(--ease-out)}
.s1-list button:hover,.s1-list button.is-hover{background:var(--stage-corner)}
.s1-list button:focus-visible,.s1-list button.is-focus{outline:var(--focus-width) solid var(--focus-colour);
  outline-offset:calc(var(--focus-offset) * -1)}
.s1-list button[aria-current="true"],.s1-list button.is-selected{background:var(--primary)}
.s1-list b{display:block;font:900 var(--size-small)/1.2 var(--font);
  font-stretch:var(--wdth-condensed);text-transform:uppercase}
.s1-list span{display:block;margin-top:2px;opacity:.7;
  font:700 10px/1.3 var(--font);letter-spacing:.12em;text-transform:uppercase}

@media (max-width:820px){.s1-slab{grid-template-columns:1fr}
  .s1-list{border-left:0;border-top:var(--rule-heavy) solid var(--black)}}


/* ===========================================================================
   S6 , ANNOUNCEMENTS AS LABEL-IN-SLAB   from set-10 S6 (90)
   Pure composition: a stack of .d-slab. Nothing is restyled, so this block is
   only the stack's own spacing and the eyebrow above it.
   =========================================================================== */
/* MEASURED off his 17.55.00: the slab's cap height is 3.1% of the slab's own
   width. At 1100px the type read a step small against the field, so the stack is
   narrowed until the ratio matches rather than the type being enlarged. */
.s6-stack{display:grid;max-width:960px}
/* his titles break at 19-20 characters, not 16: "THE FIRST NAMES ARE / COMING",
   two lines, not three. The shared .d-slab__t measure is set for a narrower
   slab, so this stack widens its own. */
.s6-stack .d-slab__t{max-width:20ch}
/* the slabs BUTT onto each other, as on the starred shot, which is one black
   field divided by its labels rather than three cards with gaps. His shot shows
   NO divider rule between them: the label is the only division. */


/* ===========================================================================
   Q1 , RIVER-FILLED DISPLAY TYPE   from set-17 Q1 (90)
   The headline is a WINDOW cut into a live river: the pattern is the ink of the
   letterforms and drifts inside them.

   This is the one place a river is laid as SVG <path>s rather than through
   UNION.river, because UNION.river builds DOM <u-svg> elements and DOM cannot be
   clipped to text. Same rule, different medium; js/lineup.js says so.
   =========================================================================== */
.q1-wrap{display:grid;gap:clamp(var(--s4),2vw,var(--s5))}
.q1-svg{width:100%;height:auto;display:block;overflow:visible}
.q1-svg text{font-family:var(--font);font-weight:900;font-stretch:var(--wdth-condensed);
  text-transform:uppercase}
/* the drift. Slow, because this is a ground, not an event. */
@keyframes q1-drift{to{transform:translate3d(-13%,4%,0)}}
.q1-flow{animation:q1-drift 34s var(--ease-linear) infinite alternate}
.q1-sub{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s5);align-items:baseline}
@media (prefers-reduced-motion:reduce){.q1-flow{animation:none}}


/* ===========================================================================
   P2 , NIGHT RIVER HERO            from set-09 P2 (88)
   Cream, pink, coral and yellow strokes run bottom-left to top-right across a
   black field and drift at three depths, the wordmark on the floor of the river.

   PORTED, NOT REDESIGNED. Every stroke's --w, --x, --y, --r and --d below is the
   starred file's own value, and the radial mask that concentrates the mass into
   the top right is ported with them: that mask is why the starred shot has a
   dense corner and a clear floor for the wordmark. I had dropped both the mask
   and the drift on the first build; both are back.
   =========================================================================== */
.p2-wrap{position:relative;overflow:clip;background:var(--black);color:var(--cream);
  min-height:min(88vh,760px);display:grid;align-content:end;
  gap:var(--s4);padding:clamp(var(--s5),4vw,var(--s8))}
/* the layer's own mask. NOT an edge fade: a radial that thins the mass toward the
   bottom left, so the strokes crowd the top right and the wordmark sits on a
   clear floor. The stops are alpha stencils, the documented #000 exception. */
.p2-riv{position:absolute;inset:-8%;pointer-events:none;z-index:var(--z-river);
  -webkit-mask-image:radial-gradient(118% 92% at 88% 12%,#000 42%,#0006 66%,#0000 84%);
  mask-image:radial-gradient(118% 92% at 88% 12%,#000 42%,#0006 66%,#0000 84%)}
.p2-riv u-svg{position:absolute;display:block;width:var(--w);left:var(--x);top:var(--y);
  transform:translate(-50%,-50%) rotate(var(--r,0deg));will-change:transform}
.p2-riv svg{width:100%;height:auto;overflow:visible}
.p2-wrap > :not(.p2-riv){position:relative;z-index:var(--z-body)}
.p2-mark{width:clamp(250px,46vw,620px)}
.p2-mark svg{width:100%;height:auto}
.p2-line{margin:0;font-size:clamp(20px,2.6vw,40px)}
.p2-foot{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s6);margin:0;padding:0;
  list-style:none;border-top:var(--rule) solid var(--cream);padding-top:var(--s4)}
/* the day version of the same hero, to pair with it. Cream ground, black display,
   and NO pattern at all: the brief's rule is that a component which works without
   pattern goes without it, and this one does. */
/* the day version carries no river, so it does not need the night one's height:
   88vh of empty cream is not restraint, it is a hole. It sizes to its content. */
.p2-wrap--day{background:var(--ground);color:var(--ink);min-height:0}
.p2-wrap--day .p2-foot{border-top-color:var(--black)}


/* ===========================================================================
   P9 , DISPLAY TYPE THAT SQUASHES  from set-09 P9 (83)
   Each word is squashed flat on entry and springs to its drawn width as you
   scroll, borrowing the bubble lettering's own compression.

   The scroller is its own frame, so the piece can be judged without moving the
   whole page, and ScrollTrigger is scoped to it.
   =========================================================================== */
/* REBUILT to his 18.25.18. What his shot has that the first build did not: the
   three words are CENTRED and stacked as one block, a ribbon field bleeds in
   from every edge (black top-left, coral top-centre, pink top-right, black
   right, coral bottom-left), and mid-entry the squashed words read as a DARKER
   YELLOW rather than black, because they enter at low opacity. The first build
   left-aligned one word and cut it off at the frame's left edge, on an empty
   ground. */
.p9-frame{border:var(--rule) solid var(--black);border-radius:var(--radius-slab);
  overflow:hidden;container-type:inline-size}
.p9-scroll{height:min(72vh,560px);overflow-y:auto;overscroll-behavior-y:contain;
  background:var(--stage-corner);color:var(--black)}
.p9-inner{padding:0}
/* the lead label sits INSIDE the first screenful, over the stage's top-left
   corner, exactly as his does: it is a caption on the set-piece, not a screen
   before it. The first build gave it 40vh of margin, which pushed the words
   below the fold and left the opening shot an empty yellow box. */
.p9-lead{position:absolute;top:0;left:0;z-index:var(--z-body);margin:0;
  padding:var(--s6) clamp(var(--s5),4vw,var(--s7))}
/* the set-piece itself: the frame's own height, so the words are centred in the
   FIRST screenful, then a tail of scroll below it to drive the squash. */
.p9-stage{position:relative;min-height:100%;display:grid;place-items:center;
  overflow:clip;padding:var(--s7) clamp(var(--s5),4vw,var(--s7))}
.p9-tail{height:120vh}
.p9-riv{position:absolute;inset:0;pointer-events:none;z-index:var(--z-river)}
.p9-rb{position:absolute;left:var(--x);top:var(--y);width:var(--w);
  transform:translate(-50%,-50%) rotate(var(--r));display:block}
.p9-rb svg{width:100%;height:auto;overflow:visible}
/* the words sit OVER the field, centred. This stage carries data-ribbon-ok: the
   shapes are a ground the words stand on, which is the starred composition. */
.p9-words{position:relative;z-index:var(--z-body);text-align:center;width:100%}
.p9-word{display:block;margin:0 0 clamp(var(--s5),4vw,var(--s7));
  font-size:clamp(34px,11cqw,150px);transform-origin:50% 100%}
.p9-word:last-child{margin-bottom:0}
.p9-frame.is-armed .p9-word{transform:scaleY(.18) scaleX(1.3);opacity:.28}
.p9-bar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s4) var(--s5);
  padding:var(--s4) clamp(var(--s5),4vw,var(--s7));
  border-top:var(--rule) solid var(--black)}


/* ===========================================================================
   ALSO , the unstarred parts a real lineup section needs.
   Kept deliberately simpler than the starred components, built from the same
   devices, so they sit beside them without a seam and never out-decorate them.
   =========================================================================== */

/* L-ARTIST , artist card and detail panel.
   No stock photography exists and none will before announcement, so the image
   block is a DRAWN placeholder: one ribbon stroke on the stage colour. It is a
   real object, not a grey rectangle waiting for a photo. */
.la-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));
  gap:clamp(var(--s4),2vw,var(--s5))}
.la-card{appearance:none;cursor:pointer;border:var(--rule) solid var(--black);
  border-radius:var(--radius-slab);background:var(--ground);color:var(--ink);
  padding:0;text-align:left;display:block;overflow:hidden;
  transition:transform var(--dur-state) var(--ease-out)}
.la-card:hover,.la-card.is-hover{transform:translateY(calc(var(--s1) * -1))}
.la-card:focus-visible,.la-card.is-focus{outline:var(--focus-width) solid var(--focus-colour);
  outline-offset:var(--focus-offset)}
.la-card.is-disabled{opacity:.45;pointer-events:none}
/* the drawn placeholder. ONE stroke, big, bleeding off the block's edges. */
.la-shot{position:relative;overflow:hidden;aspect-ratio:1;background:var(--stage);
  display:grid;place-items:center}
/* MEASURED: at 78% these ribbon SVGs paint past their viewBox and fill the block
   edge to edge as one blob, which is a colour field with a shape in it, not a
   drawn mark. Brought down so the stroke reads AS a stroke with the stage colour
   around it, and offset off-centre so four cards in a row do not read as a
   repeating tile. */
.la-shot u-svg{position:absolute;display:block;width:46%;left:56%;top:46%;
  transform:translate(-50%,-50%) rotate(var(--r,-12deg))}
.la-shot svg{width:100%;height:auto;overflow:visible}
.la-body{padding:var(--s4);display:grid;gap:var(--s2)}
.la-nm{margin:0;font-size:clamp(17px,1.8vw,24px)}
.la-meta{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2) var(--s3)}
/* genre tags are .d-label, so a tag is the same object everywhere in the system */
.la-tags{display:flex;flex-wrap:wrap;gap:var(--s2);margin:0;padding:0;list-style:none}

/* the detail panel: the same card opened out, so nothing new is invented */
.la-panel{display:grid;grid-template-columns:minmax(0,320px) 1fr;
  gap:clamp(var(--s5),3vw,var(--s6));border:var(--rule) solid var(--black);
  border-radius:var(--radius-slab);padding:clamp(var(--s4),2.4vw,var(--s5));
  max-width:900px}
.la-panel .la-shot{border-radius:var(--radius-slab)}
.la-panel__body{display:grid;gap:var(--s4);align-content:start}
@media (max-width:700px){.la-panel{grid-template-columns:1fr}}

/* L-STAGE , stage header. A cap in the stage colour over a fact row. */
.ls-head{border:var(--rule-heavy) solid var(--black);border-radius:var(--radius-slab);
  overflow:hidden;max-width:900px}
.ls-head__cap{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:var(--s3) var(--s5);padding:var(--s4) var(--s5);background:var(--stage);color:var(--black)}
.ls-head__cap h4{margin:0}
.ls-head__facts{display:flex;flex-wrap:wrap;gap:var(--s4) var(--s6);margin:0;padding:var(--s4) var(--s5);
  list-style:none}
.ls-head__facts b{display:block;font:900 clamp(19px,2vw,26px)/1 var(--font);
  font-stretch:var(--wdth-condensed);font-variant-numeric:tabular-nums}
.ls-head__facts span{display:block;margin-top:var(--s1);opacity:.72}

/* L-MYDAY , save-to-my-day and the clash warning.
   The save control is a .d-label that fills when saved, so saving is the same
   FLOOD gesture the slab uses. The clash notice is black on yellow, the care
   pair, because a clash is the one thing here someone must not miss. */
.lm-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3) var(--s4);
  padding:var(--s4) 0;border-bottom:1px solid var(--hairline);min-width:0}
/* the saved row's name must be allowed to shrink, or a long placeholder pushes the
   row past 390 and the whole spec reports wider than the viewport. */
.lm-row > b{min-width:0;overflow:hidden;text-overflow:ellipsis}
.lm-save{appearance:none;cursor:pointer;border:var(--rule) solid var(--black);
  background:transparent;color:var(--ink);
  display:inline-flex;align-items:center;gap:var(--s2);
  padding:var(--s2) var(--s3);min-height:var(--tap-min);border-radius:var(--radius-slab);
  font:900 13px/1 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase;letter-spacing:.04em;
  transition:background var(--dur-flood) var(--ease-verb-flood)}
.lm-save[aria-pressed="true"],.lm-save.is-selected{background:var(--stage);border-color:var(--black)}
.lm-save:focus-visible,.lm-save.is-focus{outline:var(--focus-width) solid var(--focus-colour);
  outline-offset:var(--focus-offset)}
.lm-save.is-disabled{opacity:.45;pointer-events:none}
.lm-clash{display:flex;align-items:center;gap:var(--s3);
  padding:var(--s3) var(--s4);background:var(--care);color:var(--black);
  border:var(--rule) solid var(--black);border-radius:var(--radius-slab);
  margin-top:var(--s4);max-width:66ch}
.lm-clash b{font-weight:900;font-stretch:var(--wdth-condensed);text-transform:uppercase}

/* L-TBA , the "lineup to be announced" empty state.
   No pattern: an empty state that decorates itself is pretending it has content.
   The soundwave at rest is the whole idea. */
.lt-empty{display:grid;justify-items:center;gap:var(--s4);text-align:center;
  padding:clamp(var(--s6),5vw,var(--s8)) var(--s5);
  border:var(--rule) dashed var(--black);border-radius:var(--radius-slab);
  max-width:620px}

/* L-NEWS , news card and article typography.
   The card is a .d-slab at --sm. The article below is the long read, and it has
   no pattern anywhere in it: pattern behind a paragraph is the banned thing. */
.ln-article{max-width:68ch}
.ln-article h3{margin:0 0 var(--s4)}
.ln-article p{margin:0 0 var(--s4);font:400 var(--size-body)/var(--lh-body) var(--font)}
.ln-article blockquote{margin:var(--s6) 0;padding-left:var(--s5);
  border-left:var(--rule-heavy) solid var(--primary);
  font:900 clamp(19px,2.2vw,28px)/1.15 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase}
.ln-article ul{margin:0 0 var(--s4);padding-left:1.2em}
.ln-article li{margin-bottom:var(--s2);font-size:var(--size-body);line-height:var(--lh-body)}
.ln-figure{margin:var(--s6) 0}
.ln-figure .la-shot{aspect-ratio:16/9;border-radius:var(--radius-slab)}
.ln-figure figcaption{margin-top:var(--s3);opacity:.72}
