/* ============================================================================
   UNIÓN , the seven devices. Lane F owns this file; all lanes compose from it.

     .d-totem    totem bar            (from set-08 A1, set-02 N3, set-03 A8)
     .d-stub     perforated stub      (from set-01 A4 + A10, set-10 S10)
     .d-slab     label in slab        (from set-10 S6, set-03 A7)  with .d-label
     .d-wave     soundwave            (from set-06 K2, set-05 E1, set-08 A7)
     .d-ribbon   ribbon               (from set-17 Q2, set-03 A5)
     .d-sticker  sticker bubble       (from set-02 N10)            minor
     .d-mascot   the Ó                                              minor

   Every class here is POSITION-INDEPENDENT: no page-level selector, no fixed
   viewport assumption, no margin on the outside. A device can be dropped into a
   site page or any other surface unchanged. Size with a modifier or by the parent.

   Modifier grammar, the same on every device:
     --sm --md --lg          size. md is the default and needs no class.
     --main --grove --corner stage colour. Sets the device's own --dc from --stage.
     --pink --coral --yellow --cream --black   an explicit ground where a stage
                             would be wrong (a device that is not about a stage).
     .is-hover .is-focus .is-active .is-selected .is-disabled .is-loading
     .is-error .is-success   forced states, producing the SAME look as the real
                             pseudo-class, so a state can be shown still.
   ============================================================================ */

/* the device colour. Every device reads --dc for its accent, so one modifier
   grammar recolours all seven. */
/* .d-label is listed here too: it is documented as usable on its own, so it must
   carry its own default. Without this a bare .d-label has no --dc and renders
   with a transparent ground, which is how it shipped on the first render. */
.d-totem,.d-stub,.d-slab,.d-wave,.d-ribbon,.d-sticker,.d-mascot,.d-label{--dc:var(--primary)}
.d-totem--main,.d-stub--main,.d-slab--main,.d-wave--main,.d-ribbon--main,.d-sticker--main{--dc:var(--stage-main)}
.d-totem--grove,.d-stub--grove,.d-slab--grove,.d-wave--grove,.d-ribbon--grove,.d-sticker--grove{--dc:var(--stage-grove)}
.d-totem--corner,.d-stub--corner,.d-slab--corner,.d-wave--corner,.d-ribbon--corner,.d-sticker--corner{--dc:var(--stage-corner)}
.d-totem--pink,.d-stub--pink,.d-slab--pink,.d-wave--pink,.d-ribbon--pink{--dc:var(--pink)}
.d-totem--coral,.d-stub--coral,.d-slab--coral,.d-wave--coral,.d-ribbon--coral{--dc:var(--coral)}
.d-totem--yellow,.d-stub--yellow,.d-slab--yellow,.d-wave--yellow,.d-ribbon--yellow{--dc:var(--yellow)}
.d-totem--cream,.d-stub--cream,.d-slab--cream,.d-wave--cream{--dc:var(--cream)}
.d-totem--black,.d-stub--black,.d-slab--black,.d-wave--black{--dc:var(--black)}

/* ============================================================================
   1. .d-totem , THE TOTEM BAR
   The wayfinding totem from the mockups: a stack of full-width colour bars, each
   one a destination, each with its arrow. Square corners, black rule between.
   A bar is a <button> or an <a>; the stack is the .d-totem.
   Motion verb: DROP (the bars fall in from above) or BOUNCE on a single bar.
   ============================================================================ */
/* MEASURED off A1 at 17.50.14: the stack has NO outer border and NO rule between
   bars. The colour bars butt straight onto each other and onto the page; the
   "cream" rows are simply the ground showing through, which is what makes the
   stack read as painted signage rather than as a bordered table. */
.d-totem{display:flex;flex-direction:column;border-radius:var(--radius-slab)}

.d-totem__bar{display:grid;grid-template-columns:auto 1fr auto auto;align-items:center;
  gap:var(--s4);width:100%;text-align:left;text-decoration:none;cursor:pointer;
  appearance:none;border:0;
  padding:var(--s5) clamp(var(--s4),2.4vw,var(--s5));
  background:var(--dc);color:var(--black);
  font:900 clamp(17px,2vw,26px)/1 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase;letter-spacing:.005em;min-height:var(--tap-min);
  transition:transform var(--dur-state) var(--ease-out),
             background var(--dur-state) var(--ease-out)}
/* the stack alternates its grounds the way the signage stack does, unless a bar
   names its own. The cream step is TRANSPARENT: the page shows through, so the
   stack never becomes a set of stacked cards. */
/* the cycle counts the STACK's children, which may be bars directly (a menu) or
   .d-totem__row wrappers (an expanding totem), so both forms alternate. */
.d-totem--stack > :nth-child(4n+1) .d-totem__bar,
.d-totem--stack > .d-totem__bar:nth-child(4n+1){background:var(--stage-main)}
.d-totem--stack > :nth-child(4n+2) .d-totem__bar,
.d-totem--stack > .d-totem__bar:nth-child(4n+2){background:transparent}
.d-totem--stack > :nth-child(4n+3) .d-totem__bar,
.d-totem--stack > .d-totem__bar:nth-child(4n+3){background:var(--stage-corner)}
.d-totem--stack > :nth-child(4n) .d-totem__bar,
.d-totem--stack > .d-totem__bar:nth-child(4n){background:var(--stage-grove)}
/* inside a dark viewport (the phone menu) the breather step has to be cream,
   because there the ground is black and black-on-black is not a bar. */
.d-totem--onDark > :nth-child(4n+2) .d-totem__bar,
.d-totem--onDark > .d-totem__bar:nth-child(4n+2){background:var(--cream)}

.d-totem__bar:hover,.d-totem__bar.is-hover{transform:translateX(var(--s2))}
.d-totem__bar:focus-visible{outline-offset:calc(var(--focus-offset) * -1)}
.d-totem__bar.is-focus{outline:var(--focus-width) solid var(--focus-colour);
  outline-offset:calc(var(--focus-offset) * -1)}
.d-totem__bar:active,.d-totem__bar.is-active{transform:translateX(var(--s2)) scale(1,.96)}
.d-totem__bar.is-disabled,.d-totem__bar[disabled]{opacity:.45;cursor:not-allowed;transform:none}
.d-totem__bar.is-selected{box-shadow:inset 0 0 0 var(--rule-heavy) var(--black)}

.d-totem__no{font:700 var(--size-micro)/1 var(--font);letter-spacing:.16em;opacity:.55}
.d-totem__wave{width:32px;flex:none}
/* a SOLID filled arrow, as in the starred shot, not a stroked chevron */
.d-totem__arrow{width:34px;height:20px;flex:none;
  transition:transform var(--dur-state) var(--ease-bounce)}
.d-totem__arrow path{fill:currentColor;stroke:none}
/* THE arrow is Louis's drawn line arrow (lab .a1-arw): stroked shaft, open chevron, round caps.
   The solid block above is legacy markup only; tools/fix_arrow.py converts it. */
.d-totem__arrow[viewBox="0 0 30 18"] path{fill:none;stroke:currentColor;stroke-width:4.5;
  stroke-linecap:round;stroke-linejoin:round}
.d-totem__bar[aria-expanded="true"] .d-totem__arrow{transform:rotate(90deg)}

/* the answer face under a bar: black, and CLOSED by visibility, not only height.
   A 0fr row still leaves the paragraph a measurable text box, which reads to a
   screen reader and to the overlap check as live text sitting on the row below. */
.d-totem__face{display:grid;grid-template-rows:0fr;visibility:hidden;
  background:var(--black);color:var(--cream);
  transition:grid-template-rows var(--dur-drop) var(--ease-out),
             visibility 0s linear var(--dur-drop)}
.d-totem__row.is-open .d-totem__face{grid-template-rows:1fr;visibility:visible;
  transition-delay:0s,0s}
.d-totem__face > div{overflow:hidden}
.d-totem__face p{margin:0;padding:var(--s5) var(--s5) var(--s5) 62px;max-width:60ch;
  font-size:var(--size-body)}

/* sizes */
.d-totem--sm .d-totem__bar{padding:var(--s3) var(--s4);font-size:clamp(14px,1.5vw,18px)}
.d-totem--sm .d-totem__wave{width:22px} .d-totem--sm .d-totem__arrow{width:22px}
.d-totem--lg .d-totem__bar{padding:var(--s6) var(--s5);font-size:clamp(21px,2.8vw,36px)}
.d-totem--lg .d-totem__wave{width:42px} .d-totem--lg .d-totem__arrow{width:40px}

/* the ALERT variant: the totem bar with a live light at one end and a slow
   marquee in the middle. Read from across a field. (set-03 A8) */
.d-totem__bar--alert{display:flex;align-items:stretch;padding:0;overflow:hidden;
  border-top:var(--rule-heavy) solid var(--black);
  border-bottom:var(--rule-heavy) solid var(--black);cursor:default}
.d-totem__bar--alert:hover,.d-totem__bar--alert.is-hover{transform:none}
.d-totem__pulse{flex:0 0 auto;display:flex;align-items:center;gap:5px;
  padding:0 var(--s4);background:var(--black)}
.d-totem__pulse i{display:block;width:6px;height:26px;background:var(--dc);
  border-radius:var(--radius-round);transform-origin:50% 50%;transform:scaleY(.3)}
.d-totem__bar--alert.is-live .d-totem__pulse i{animation:d-pulse .7s ease-in-out infinite alternate}
.d-totem__bar--alert.is-live .d-totem__pulse i:nth-child(2){animation-delay:.16s}
.d-totem__bar--alert.is-live .d-totem__pulse i:nth-child(3){animation-delay:.32s}
@keyframes d-pulse{to{transform:scaleY(1)}}
.d-totem__track{flex:1 1 auto;overflow:hidden;display:flex;align-items:center;
  padding:var(--s3) 0;min-width:0}
/* the run is TWO identical halves, so translateX(-50%) loops seamlessly. It must
   be allowed its natural width, or the halves compress and the loop jumps. */
.d-totem__run{display:flex;gap:var(--s7);white-space:nowrap;padding-left:var(--s5);
  will-change:transform;flex:0 0 auto;min-width:max-content}
.d-totem__bar--alert.is-live .d-totem__run{animation:d-marquee var(--dur-marquee) var(--ease-linear) infinite}
@keyframes d-marquee{to{transform:translateX(-50%)}}
.d-totem__run span{font:900 18px/1 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase;letter-spacing:.03em}
.d-totem__x{appearance:none;cursor:pointer;flex:0 0 auto;
  border:0;border-left:var(--rule-heavy) solid var(--black);
  background:var(--black);color:var(--dc);padding:0 var(--s4);min-width:var(--tap-min);
  font:900 18px/1 var(--font);font-stretch:var(--wdth-condensed)}

@media (prefers-reduced-motion:reduce){
  .d-totem__bar--alert.is-live .d-totem__pulse i{transform:scaleY(1);animation:none}
  .d-totem__bar--alert.is-live .d-totem__run{animation:none}
}

/* ============================================================================
   2. .d-stub , THE PERFORATED STUB
   The ticket from the mockups: a body, a dashed perforation, a coloured stub with
   a barcode. The ONE device with a radius (10px), because a printed ticket has one.
   Motion verb: TEAR. .is-torn is the post-tear state and is shown still.
   ============================================================================ */
/* MEASURED off the starred shots (set-01 A4 at 18.00.50, A10 at 17.47.27,
   set-10 S10 at 17.54.50): the stub is SQUARE. There is no radius on it anywhere,
   the body carries a heavy black border rather than a shadow, the perforation is a
   chunky black dashed block that reads as punched, and the tab is short, roughly
   as wide as it is tall, not a deep panel. */
.d-stub{display:flex;align-items:stretch;overflow:hidden;position:relative;
  background:var(--cream);color:var(--black);border-radius:var(--radius-slab);
  border:var(--rule-heavy) solid var(--black);
  width:100%;max-width:560px;text-align:left}
button.d-stub,a.d-stub{appearance:none;padding:0;cursor:pointer;
  font:inherit;color:inherit}

.d-stub__body{flex:1 1 auto;min-width:0;padding:var(--s5) var(--s5);min-height:110px;
  display:flex;flex-direction:column;justify-content:center;gap:var(--s2);
  transition:background var(--dur-flood) var(--ease-out)}
.d-stub__body b{display:block;font:900 clamp(26px,3vw,44px)/.9 var(--font);
  font-stretch:var(--wdth-condensed);text-transform:uppercase}
.d-stub__body small{display:block;font:700 var(--size-micro)/1.4 var(--font);
  letter-spacing:var(--track-micro);text-transform:uppercase}

/* the perforation: a chunky punched dash, 6px of black with 6px gaps, sitting on
   the tab's own colour so the hole reads through. Never border-style:dashed. */
.d-stub__perf{width:6px;flex:0 0 6px;align-self:stretch;background:var(--dc);
  background-image:repeating-linear-gradient(var(--black) 0 7px,transparent 7px 14px)}

.d-stub__tab{flex:0 0 130px;background:var(--dc);color:var(--black);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:var(--s3);padding:var(--s4) var(--s3);overflow:hidden;
  transition:transform var(--dur-tear) var(--ease-verb-tear),opacity var(--dur-flood) .1s,
             flex-basis var(--dur-tear) .1s var(--ease-verb-tear),padding var(--dur-tear) .1s}
.d-stub__tab span{font:900 13px/1.5 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase;letter-spacing:.14em;text-align:center}

/* the barcode: a TIGHT dense block of varied bars, as on the printed ticket.
   Narrow gaps, mixed widths, all full height off a common baseline. */
.d-stub__code{display:flex;gap:2px;align-items:stretch;height:34px}
.d-stub__code i{display:block;width:3px;background:var(--black);height:100%}
.d-stub__code i:nth-child(2n){width:5px}
.d-stub__code i:nth-child(3n){width:2px;height:86%;align-self:flex-end}
.d-stub__code i:nth-child(5n){width:6px}

.d-stub:hover .d-stub__tab,.d-stub.is-hover .d-stub__tab,
.d-stub:focus-visible .d-stub__tab{transform:translateX(var(--s2)) rotate(3deg)}
.d-stub.is-focus{outline:var(--focus-width) solid var(--focus-colour);
  outline-offset:var(--focus-offset)}
/* the LIVE torn state: the stub leaves and the ticket closes up behind it */
.d-stub.is-torn .d-stub__tab{transform:translateX(40px) rotate(10deg);opacity:0;
  flex-basis:0;padding:0}
.d-stub.is-torn .d-stub__body{background:var(--care)}
/* the DOCUMENTED torn state, for a still: the stub is shown mid-tear, hanging off
   its perforation, because a state cell that shows nothing documents nothing. */
.d-stub.is-torn--shown .d-stub__body{background:var(--care)}
.d-stub.is-torn--shown{overflow:visible}
.d-stub.is-torn--shown .d-stub__tab{transform:translate(18%,10%) rotate(9deg);
  outline:var(--rule-heavy) solid var(--black);z-index:var(--z-raise)}
.d-stub.is-disabled,.d-stub[disabled]{opacity:.5;cursor:not-allowed}
.d-stub.is-disabled .d-stub__tab{transform:none}
.d-stub.is-error{box-shadow:inset 0 0 0 var(--rule-heavy) var(--black),
  inset 0 0 0 9px var(--coral)}

/* the COUNTER stub: a small standing stub, stamped into a row by the stepper.
   (set-01 A10) Its verb is BOUNCE, and its perforation runs across the shoulder. */
/* the chip, measured off A10 at 17.47.27: a small upright stub, square, its own
   black border, the barcode at the TOP, the perforation across the shoulder just
   under it, and the class label at the foot. */
.d-stub--chip{width:56px;max-width:none;flex-direction:column;align-items:center;
  justify-content:space-between;height:92px;padding:9px 0 var(--s2);
  background:var(--dc);border-radius:var(--radius-slab);position:relative;
  border:var(--rule-heavy) solid var(--black)}
.d-stub--chip span{font:900 var(--size-micro)/1 var(--font);
  font-stretch:var(--wdth-condensed);text-transform:uppercase;letter-spacing:.04em}
.d-stub--chip::before{content:"";position:absolute;left:0;right:0;top:28px;height:3px;
  background:repeating-linear-gradient(90deg,var(--black) 0 5px,transparent 5px 10px)}
.d-stub--chip .d-stub__code{height:15px;gap:2px}
.d-stub--chip .d-stub__code i{width:2px;height:100%;align-self:stretch}
.d-stub--chip .d-stub__code i:nth-child(2n){width:4px;height:70%;align-self:flex-end}
.d-stub--chip .d-stub__code i:nth-child(3n){width:3px;height:100%;align-self:stretch}

/* sizes */
.d-stub--sm .d-stub__body{min-height:88px;padding:var(--s4)}
.d-stub--sm .d-stub__tab{flex-basis:76px}
.d-stub--lg{max-width:920px}
.d-stub--lg .d-stub__body{min-height:170px;padding:var(--s6)}
.d-stub--lg .d-stub__tab{flex-basis:190px}

@media (max-width:700px){
  .d-stub{flex-direction:column}
  .d-stub__perf{width:auto;height:4px;flex:0 0 4px;
    background:repeating-linear-gradient(90deg,var(--black) 0 9px,transparent 9px 18px)}
  .d-stub__tab{flex-basis:auto;flex-direction:row;gap:var(--s4);padding:var(--s4)}
  .d-stub.is-torn .d-stub__tab{transform:translate(var(--s3),34%) rotate(4deg);flex-basis:auto}
  .d-stub--chip{flex-direction:column;width:52px}
}

/* ============================================================================
   3. .d-slab + .d-label , THE LABEL IN A SLAB
   The poster's pink tag bar set inside a black field. The single most-used device
   in the mockups. The label is the ONLY coloured thing at rest; on hover the
   colour FLOODS the slab out from the label and the type flips to black.
   Motion verb: FLOOD.
   ============================================================================ */
.d-label{display:inline-flex;align-items:center;gap:var(--s2);
  padding:var(--s2) var(--s3);background:var(--dc);color:var(--black);
  border-radius:var(--radius-slab);
  font:900 13px/1 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase;letter-spacing:.04em;white-space:nowrap}
.d-label--sm{padding:5px var(--s2);font-size:10px;letter-spacing:.08em}
.d-label--lg{padding:var(--s3) var(--s4);font-size:clamp(15px,1.8vw,20px)}
.d-label--struck{text-decoration:line-through;text-decoration-thickness:2px}
/* colour variants. These did NOT exist at first build, so every lane's coral and yellow tags
   silently rendered pink (found on lineup S6, 20 Sep). The label takes a stage colour by class
   or from a [data-stage] ancestor. --night is Louis's black tag with yellow type (THE CORNER
   tag in the announcement sweep). */
.d-label--main,.d-label--pink{--dc:var(--pink)}
.d-label--grove,.d-label--coral{--dc:var(--coral)}
.d-label--corner,.d-label--yellow{--dc:var(--yellow)}
.d-label--night{--dc:var(--black);color:var(--yellow)}
[data-stage="main"] .d-label--stage{--dc:var(--stage-main)}
[data-stage="grove"] .d-label--stage{--dc:var(--stage-grove)}
[data-stage="corner"] .d-label--stage{--dc:var(--stage-corner)}

/* MEASURED off S6 at 17.55.00: the slab has NO border. It is a black field, the
   label sits FLUSH in its top-left corner (no padding above or left of it), the
   meta line sits on the label's own baseline, and the arrow is a SOLID filled
   arrow out at the right, level with the title rather than the whole slab. */
.d-slab{position:relative;overflow:hidden;display:block;text-decoration:none;
  background:var(--black);color:var(--cream);border-radius:var(--radius-slab);
  --focus-colour:var(--focus-colour-on-dark)}
.d-slab__flood{position:absolute;inset:0;background:var(--dc);z-index:var(--z-river);
  clip-path:inset(0 100% 0 0);
  transition:clip-path var(--dur-flood) var(--ease-verb-flood)}
.d-slab__hd{display:flex;align-items:center;gap:0;position:relative;z-index:var(--z-body)}
.d-slab__meta{font:700 10px/1 var(--font);letter-spacing:.2em;text-transform:uppercase;
  padding:0 var(--s4);opacity:.7}
.d-slab__body{display:block;padding:var(--s5) var(--s9) var(--s6) var(--s5);
  position:relative;z-index:var(--z-body)}
.d-slab__t{display:block;margin:0 0 var(--s3);max-width:16ch;
  font:900 clamp(24px,3.2vw,44px)/1.04 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase}
.d-slab__x{display:block;margin:0;max-width:44ch;font-size:15px;line-height:1.5;opacity:.85}
/* a SOLID arrow, drawn as a filled shape, not a stroked chevron */
.d-slab__arrow{position:absolute;right:var(--s7);top:50%;transform:translateY(-50%);
  z-index:var(--z-body);width:66px;
  transition:transform var(--dur-flood) var(--ease-out)}
.d-slab__arrow path{fill:var(--cream);stroke:none}

.d-slab:hover .d-slab__flood,.d-slab:focus-visible .d-slab__flood,
.d-slab.is-hover .d-slab__flood,.d-slab.is-focus .d-slab__flood{clip-path:inset(0 0 0 0)}
.d-slab:hover,.d-slab:focus-visible,.d-slab.is-hover,.d-slab.is-focus{color:var(--black)}
/* focus must be DISTINCT from hover: the flood alone is not a focus indicator */
.d-slab:focus-visible,.d-slab.is-focus{outline:var(--focus-width) solid var(--yellow);
  outline-offset:var(--focus-offset)}
.d-slab:hover .d-slab__arrow path,.d-slab:focus-visible .d-slab__arrow path,
.d-slab.is-hover .d-slab__arrow path,.d-slab.is-focus .d-slab__arrow path{fill:var(--black)}
.d-slab:hover .d-slab__arrow,.d-slab.is-hover .d-slab__arrow{transform:translate(var(--s3),-50%)}
.d-slab.is-focus{outline:var(--focus-width) solid var(--focus-colour-on-dark);
  outline-offset:calc(var(--focus-offset) * -1)}
.d-slab.is-disabled{pointer-events:none;color:var(--hairline-on-dark);
  box-shadow:inset 0 0 0 var(--rule) var(--hairline-on-dark)}
/* the slab stays the warm black (black is black on any ground, never grey); what drops back is
   its ink, and the label loses its fill and keeps an edge */
.d-slab.is-disabled .d-label{background:none;color:var(--hairline-on-dark);
  box-shadow:inset 0 0 0 var(--rule) var(--hairline-on-dark)}
.d-slab.is-selected .d-slab__flood{clip-path:inset(0 0 0 0)}
.d-slab.is-selected{color:var(--black)}

/* the TOAST variant: the same tag-in-slab, slammed up from the bottom edge.
   (set-03 A7) Its verb is DROP, inverted. */
.d-slab--toast{display:flex;align-items:stretch;overflow:visible;
  box-shadow:0 0 0 var(--rule) var(--black)}
.d-slab--toast .d-label{padding:0 var(--s3);align-self:stretch}
.d-slab--toast .d-slab__body{padding:var(--s3) var(--s4);
  font:700 var(--size-small)/1.3 var(--font)}

.d-slab--sm .d-slab__t{font-size:clamp(17px,2vw,26px)}
.d-slab--sm .d-slab__body{padding:var(--s2) var(--s6) var(--s4) var(--s4)}
.d-slab--lg .d-slab__t{font-size:clamp(30px,4.4vw,64px)}
@media (max-width:700px){.d-slab__arrow{width:44px;right:var(--s4)}
  .d-slab__body{padding-right:var(--s7)}}

/* ============================================================================
   4. .d-wave , THE SOUNDWAVE
   Three bars. The single most flexible device: it is a level METER, a live EQ, a
   map PIN, a list BULLET and a PROGRESS bar, because it already looks like all
   five. NEVER stretched: below 40px it goes illegible, so it repeats at native
   proportion instead. Motion verb: BOUNCE (the bars are a meter).
   ============================================================================ */
/* MEASURED off K2 at 18.08.15: the meter bars are THIN with NARROW gaps, about
   10px wide at a 46px height with a 4px gap, and they sit on a common baseline.
   Fat bars with wide gaps read as a chart; these read as a level. */
.d-wave{display:flex;align-items:flex-end;gap:4px;height:46px;flex:none}
.d-wave i,.d-wave b{display:block;width:10px;height:100%;background:var(--dc);
  transform-origin:50% 100%;transform:scaleY(.5);border-radius:var(--radius-slab)}

/* METER , static levels, read by looking. Used for a tier ladder's rungs. */
.d-wave--meter i:nth-child(1){transform:scaleY(.9)}
.d-wave--meter i:nth-child(2){transform:scaleY(.66)}
.d-wave--meter i:nth-child(3){transform:scaleY(1)}
.d-wave--meter i:nth-child(4){transform:scaleY(.58)}
.d-wave--meter i:nth-child(5){transform:scaleY(.82)}
.d-wave--meter.is-disabled i,.d-wave--meter.is-idle i{transform:scaleY(.15);opacity:.45}
.d-wave--meter.is-gone i{transform:scaleY(1);opacity:.22}

/* EQ , running, because something is PLAYING. Stops under reduced motion. */
.d-wave--eq i{animation:d-eq .62s ease-in-out infinite alternate}
.d-wave--eq i:nth-child(2){animation-delay:.14s}
.d-wave--eq i:nth-child(3){animation-delay:.28s}
.d-wave--eq i:nth-child(4){animation-delay:.42s}
.d-wave--eq i:nth-child(5){animation-delay:.56s}
@keyframes d-eq{from{transform:scaleY(.35)}to{transform:scaleY(1)}}
.d-wave--eq.is-paused i{animation:none;transform:scaleY(.35)}
@media (prefers-reduced-motion:reduce){
  .d-wave--eq i{animation:none;transform:scaleY(.55)}
}

/* PIN , the drawn glyph itself, used as the map's marker. One pin is one thing.
   It carries the real SVG, so it is never a stretched three-rect fake. */
.d-wave--pin{height:auto;display:block;width:clamp(38px,4.4vw,58px)}
.d-wave--pin u-svg path{transform-box:fill-box;transform-origin:50% 100%}
.d-wave--pin.is-bob u-svg{animation:d-bob var(--dur-drop) var(--ease-bounce)}
@keyframes d-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-12px)}}

/* BULLET , the wave at list scale, standing in for a disc. Never below 40px wide:
   it repeats at native proportion instead of shrinking into mush. */
.d-wave--bullet{height:auto;width:40px;flex:0 0 40px}

/* PROGRESS , the bars as a filled track. --p is 0..1. */
.d-wave--progress{gap:3px;width:100%;height:22px}
.d-wave--progress i{flex:1 1 0;width:auto;transform:scaleY(1);opacity:.25}
.d-wave--progress i:nth-child(-n+3){opacity:1}

/* sizes */
.d-wave--sm{height:22px;gap:2px} .d-wave--sm i{width:5px}
.d-wave--lg{height:78px;gap:6px} .d-wave--lg i{width:15px}
.d-wave--pin.d-wave--sm{width:40px;height:auto}     /* the 40px floor, honoured */
.d-wave--pin.d-wave--lg{width:clamp(64px,8vw,110px);height:auto}

/* ============================================================================
   5. .d-ribbon , THE RIBBON
   One drawn stroke. It bleeds off an edge or ends in its own round cap; it is
   never cut flat in mid air and never sits behind a word. This class is for ONE
   placed stroke; a whole river is UNION.river(host, opts), not markup.
   Motion verb: DRAW (it is a stroke, so it draws along its own length).
   ============================================================================ */
.d-ribbon{position:absolute;left:var(--x,50%);top:var(--y,50%);width:var(--w,18vw);
  transform:translate(-50%,-50%) rotate(var(--r,0deg));pointer-events:none;
  z-index:var(--z-river)}
.d-ribbon svg{width:100%;height:auto;overflow:visible}
/* a ribbon used INLINE, as a rule or a bullet rather than a placed stroke */
.d-ribbon--inline{position:static;transform:none;width:var(--w,120px);display:inline-block;
  vertical-align:middle}
/* the BAR variant: a fat round-ended stroke drawn as a box, used for a timeline
   lane, a skeleton line, a progress ribbon. Both ends are round CAPS. */
.d-ribbon--bar{position:static;transform:none;height:var(--h,18px);width:var(--w,100%);
  background:var(--dc);border-radius:var(--radius-round);display:block;pointer-events:auto}
.d-ribbon--bar.is-loading{transform-origin:0 50%;animation:d-draw var(--dur-draw) var(--ease-verb-draw)}
@keyframes d-draw{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@media (prefers-reduced-motion:reduce){.d-ribbon--bar.is-loading{animation:none}}

/* ============================================================================
   .pattern-slot , THE SWAPPABLE PATTERN BAND
   The one treatment Louis has liked: a river seen through a WINDOW that fades at
   its edges (lab set-06 K4, the waiting room). The band fades into the ground
   rather than stopping on a hard edge, which is what kept it from reading as a
   cut-off swatch.

   It is a SLOT because the pattern work is not finished. Its content comes from
   either a CSS variable or a UNION.river() call, so when Louis supplies wide drawn
   pattern artwork it drops straight in with no rebuild:

     <div class="pattern-slot" style="--pattern-image:url(assets/img/band.png)"></div>
     <div class="pattern-slot" data-river-slot></div>   <!-- filled by UNION.river -->

   --pattern-height  the band's height (default 150px, as measured on K4)
   --pattern-fade    how far in the fade reaches from each edge (default 10%)
   --pattern-image   any image, repeated along X at its own height
   ============================================================================ */
.pattern-slot{position:relative;overflow:hidden;width:100%;
  height:var(--pattern-height,150px);
  /* the fade IS the edge treatment. Both ends dissolve into the ground, so the
     band never stops on a guillotine. Prefer this over any hard pattern edge. */
  -webkit-mask-image:linear-gradient(90deg,#0000,#000 var(--pattern-fade,10%),
    #000 calc(100% - var(--pattern-fade,10%)),#0000);
  mask-image:linear-gradient(90deg,#0000,#000 var(--pattern-fade,10%),
    #000 calc(100% - var(--pattern-fade,10%)),#0000)}
/* the image form: drawn artwork, repeated along the band at its own height */
.pattern-slot[style*="--pattern-image"]::before{content:"";position:absolute;inset:0;
  background-image:var(--pattern-image);background-repeat:repeat-x;
  background-size:auto 100%;background-position:center}
/* the fade on the Y axis instead, for a band that is taller than it is wide */
.pattern-slot--y{-webkit-mask-image:linear-gradient(#0000,#000 var(--pattern-fade,14%),
    #000 calc(100% - var(--pattern-fade,14%)),#0000);
  mask-image:linear-gradient(#0000,#000 var(--pattern-fade,14%),
    #000 calc(100% - var(--pattern-fade,14%)),#0000)}
/* the K4 run: strokes overlapping into one mass along the track, each at its own
   scale and angle, ground breaking through, the window taller than the tallest
   stroke so nothing is cut top or bottom. */
/* the run is centred in the window and the strokes OVERLAP heavily (negative
   margin), so they read as one continuous mass rather than as separate shapes.
   Sized so the tallest stroke is comfortably inside the window's height: the
   window must be taller than the pattern, or the mass gets cut top and bottom. */
.pattern-slot__run{position:absolute;top:0;bottom:0;left:0;display:flex;align-items:center}
/* MEASURED: at a 150px window these widths put the tallest stroke at -15px and
   +156px, i.e. cut at both edges, which is the one thing this treatment must not
   do. Scaled down and the vertical offsets tightened so the whole mass sits
   inside the window with room to spare. */
.pattern-slot__run u-svg{flex:0 0 auto;width:82px;margin-right:-34px}
.pattern-slot__run u-svg:nth-child(4n+1){width:64px;transform:translateY(-10px) rotate(-12deg)}
.pattern-slot__run u-svg:nth-child(4n+2){width:90px;transform:translateY(8px) rotate(8deg)}
.pattern-slot__run u-svg:nth-child(4n+3){width:72px;transform:translateY(14px) rotate(-6deg)}
.pattern-slot__run u-svg:nth-child(4n){width:98px;transform:translateY(-6px) rotate(14deg)}

/* the river layer that UNION.river() builds. Declared here so the geometry lives
   with the devices, not in a lane. */
.rv{position:absolute;inset:0;pointer-events:none;z-index:var(--z-river)}
.rv-s{position:absolute}
.rv-s svg{width:100%;height:auto;overflow:visible}
/* the boundary rule, when a host must clip its river */
.rv--fade-y{-webkit-mask-image:linear-gradient(#0000,#000 14%,#000 86%,#0000);
  mask-image:linear-gradient(#0000,#000 14%,#000 86%,#0000)}
.rv--fade-x{-webkit-mask-image:linear-gradient(90deg,#0000,#000 12%,#000 88%,#0000);
  mask-image:linear-gradient(90deg,#0000,#000 12%,#000 88%,#0000)}

/* ============================================================================
   6. .d-sticker , THE STICKER (minor)
   The real die-cut sticker from the sheet. It is a PNG, not a drawn bubble and
   not an SVG: the four UNION_STICKER-* SVGs have a hole through the sticker
   field and have been deleted from the system. Use the 4x PNGs in
   assets/img/brand/ through an <img> with width, height and alt. Do not rebuild
   one from the primary logo on a drawn bubble; the PNG sticker IS the sticker.
   Motion verb: SQUASH.

     <img class="d-sticker" src="assets/img/brand/UNION_STICKER-union-london-black-on-pink@4x.png"
          width="1125" height="654" alt="UNIÓN London sticker, black on pink">

   The image carries its own cut edge and its own colour, so this class only sets
   scale and tilt. It never adds a background, a radius or a ring: doing so would
   put a second edge round a shape that already has one.
   ============================================================================ */
.d-sticker{display:block;width:var(--mark,240px);height:auto;max-width:100%;
  transform:rotate(var(--tilt,-3deg));transform-origin:50% 100%}
.d-sticker--sm{width:var(--mark,150px)}
.d-sticker--lg{width:var(--mark,420px)}
button.d-sticker,a.d-sticker{appearance:none;border:0;padding:0;background:none;
  cursor:pointer;min-height:var(--tap-min);
  transition:transform var(--dur-squash) var(--ease-verb-squash)}
button.d-sticker:hover,button.d-sticker.is-hover{transform:rotate(var(--tilt,-3deg)) scale(1.05,.93)}
button.d-sticker:active,button.d-sticker.is-active{transform:rotate(var(--tilt,-3deg)) scale(.94,1.07)}
.d-sticker.is-disabled{opacity:.45;pointer-events:none}

/* the SWITCH variant: the bubble's outline as a track, the knob pressing across.
   (set-02 N10) The chosen label squashes like the bubble lettering. */
.d-sticker--switch{position:relative;display:inline-grid;grid-template-columns:1fr 1fr;
  width:auto;max-width:none;padding:6px;gap:0;transform:none;box-shadow:none;
  border:var(--rule-heavy) solid var(--black);border-radius:var(--radius-round);
  background:var(--cream)}
/* the knob is exactly one option wide and sits UNDER one label, never both */
.d-sticker__knob{position:absolute;top:6px;bottom:6px;left:6px;width:calc(50% - 6px);
  background:var(--dc);border-radius:var(--radius-round);
  transition:transform var(--dur-bounce) var(--ease-verb-bounce),
             background var(--dur-state) var(--ease-out)}
.d-sticker--switch[data-on="b"] .d-sticker__knob{transform:translateX(100%)}
.d-sticker--switch:active .d-sticker__knob,.d-sticker--switch.is-active .d-sticker__knob{
  scale:1.08 .88}
.d-sticker__opt{position:relative;z-index:var(--z-body);appearance:none;border:0;
  background:none;cursor:pointer;padding:0 22px;min-width:72px;min-height:var(--tap-min);
  border-radius:var(--radius-round);color:var(--black);text-align:center;
  font:900 16px/1 var(--font);font-stretch:var(--wdth-condensed);letter-spacing:.06em;
  transition:transform var(--dur-squash) var(--ease-verb-squash)}
.d-sticker--switch[data-on="a"] .d-sticker__opt:nth-of-type(1),
.d-sticker--switch[data-on="b"] .d-sticker__opt:nth-of-type(2){transform:scale(1.06,.94)}

/* ============================================================================
   7. .d-mascot , THE Ó (minor)
   The dancing accented O with headphones. LARGE OR NOT AT ALL: he is a character,
   not an icon, and he goes illegible and twee at small sizes. Never on a form
   field, never as a bullet, never repeated across a grid.

   HE NEVER SITS ON BLACK. His body IS the warm near-black, so on a black ground
   he disappears into it. His grounds are pink, coral, yellow and cream, and there
   is deliberately NO dark-ground modifier for him: the only way to put him on
   black is to fight the system, which is the point.
   Motion verb: BOUNCE (he dances).
   ============================================================================ */
.d-mascot{display:block;width:clamp(190px,24vw,340px);flex:none}
.d-mascot--sm{width:clamp(150px,18vw,220px)}   /* the FLOOR. Nothing smaller exists. */
.d-mascot--lg{width:clamp(280px,42vw,620px)}
.d-mascot.is-dancing{animation:d-dance 1.6s var(--ease-bounce) infinite}
@keyframes d-dance{
  0%,100%{transform:translateY(0) rotate(0)}
  30%{transform:translateY(-10px) rotate(-3deg)}
  60%{transform:translateY(0) rotate(2.5deg)}
}
@media (prefers-reduced-motion:reduce){.d-mascot.is-dancing{animation:none}}

/* ============================================================================
   THE ONE PILL. Not a device: the single CTA shape in the system, from the
   mockups' GET TICKETS button. Pink, black caps, arrow right. If a second pill
   appears anywhere in the system, one of them is wrong.
   ============================================================================ */
.u-pill{display:inline-flex;align-items:center;gap:var(--s4);
  appearance:none;border:0;cursor:pointer;text-decoration:none;
  padding:var(--s4) var(--s5) var(--s4) var(--s6);min-height:var(--tap-min);
  background:var(--primary);color:var(--black);border-radius:var(--radius-round);
  font:900 clamp(17px,1.8vw,24px)/1 var(--font);font-stretch:var(--wdth-condensed);
  text-transform:uppercase;letter-spacing:.02em;
  transition:transform var(--dur-squash) var(--ease-verb-squash)}
.u-pill svg{width:34px;height:20px;flex:none}
.u-pill svg path{fill:none;stroke:currentColor;stroke-width:4.5;
  stroke-linecap:round;stroke-linejoin:round}
.u-pill:hover,.u-pill.is-hover{transform:scale(1.04,.94)}
.u-pill:active,.u-pill.is-active{transform:scale(.96,1.05)}
.u-pill.is-focus{outline:var(--focus-width) solid var(--focus-colour);
  outline-offset:var(--focus-offset)}
.u-pill.is-disabled,.u-pill[disabled]{background:none;color:var(--disabled-ink);
  box-shadow:inset 0 0 0 var(--rule) var(--disabled-edge);cursor:not-allowed;transform:none}
.u-pill.is-loading{pointer-events:none}
.u-pill.is-loading svg{opacity:.4}
