/* Winter Utsav 2027 — the event page.
   Separate from app.css, which the pass / door / admin pages share.

   The page is a street of Christmas lights. Everything else is quiet so the
   bulbs are the thing you remember: footage is texture at a third opacity,
   the ground is a near-black plum, and the only other colour is the gold the
   lights throw. */

/* Bodoni Moda at 500, optical size 96 — the owner's pick. A didone's thin
   hairlines need the display optical size or they fatten and go generic.
   Figtree does the reading. Both OFL, self-hosted. */
@font-face { font-family: 'BodoniModa'; src: url(fonts/bodoni-moda.woff2) format('woff2'); font-weight: 400 900; font-display: swap; }
@font-face { font-family: 'Figtree'; src: url(fonts/figtree.woff2) format('woff2'); font-weight: 300 900; font-display: swap; }

:root {
  --night:   #120610;
  --plum:    #1E0C1B;
  --gold:    #F0B95E;
  --gold-lo: #8A6632;
  --cream:   #FBEFD8;
  --mist:    #DACCC8;
  --magenta: #D91E6E;

  --display: 'BodoniModa', 'Didot', 'Bodoni MT', Georgia, serif;
  --text: 'Figtree', ui-sans-serif, system-ui, sans-serif;
  --gut: clamp(20px, 5vw, 76px);
  --lit: 0;                       /* lights.js writes the bulbs' brightness here */
  color-scheme: dark;
}

* { box-sizing: border-box; }
/* The bar is fixed, so an anchor jump (#tickets from the dock or a CTA) would
   otherwise land the target underneath it. */
html { -webkit-text-size-adjust: 100%; background: var(--night); scroll-padding-top: 76px; }
body {
  margin: 0; background: var(--night); color: var(--mist);
  font: 400 17px/1.65 var(--text);
  overflow-x: hidden; -webkit-font-smoothing: antialiased;
}
img, video { display: block; max-width: 100%; }
[hidden] { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

.skip { position: absolute; left: -999px; top: 0; z-index: 200; background: var(--gold); color: var(--night); padding: 12px 18px; text-decoration: none; }
.skip:focus { left: 8px; top: 8px; }

/* ── type ────────────────────────────────────────────────────────── */

.club-name, .fact-big, .book-h, .sheet-top h3, .closed p:first-child,
.sec-h, .comp-h, .step output, .enq-tel {
  font-family: var(--display);
  font-variation-settings: 'opsz' 96;
  font-weight: 500; color: var(--cream); margin: 0; letter-spacing: .005em;
}
/* the words under a string are lit by it: --lit is the live bulb brightness */
.club-name, .fact-big, .book-h, .sec-h {
  text-shadow:
    0 1px 2px rgba(8,3,7,.7),
    0 6px 34px rgba(8,3,7,.75),
    0 0 calc(26px + 34px * var(--lit)) rgba(240,185,94, calc(.10 + .34 * var(--lit)));
}
.presents-word {
  margin: 10px 0 0; font-family: var(--display);
  font-variation-settings: 'opsz' 48;
  font-weight: 500; font-style: italic;
  font-size: clamp(17px, 2.1vw, 27px); color: var(--gold);
  text-shadow: 0 0 calc(18px + 26px * var(--lit)) rgba(240,185,94, calc(.2 + .4 * var(--lit)));
}
.eyebrow {
  display: flex; align-items: center; justify-content: center; gap: 13px;
  margin: 0 0 clamp(16px, 2.4vw, 30px);
  font: 600 11px/1 var(--text); letter-spacing: .38em; text-indent: .38em;
  text-transform: uppercase; color: var(--gold);
}
.eyebrow i { display: block; width: clamp(20px, 5vw, 52px); height: 1px; background: linear-gradient(90deg, transparent, var(--gold-lo)); }
.eyebrow i:last-child { background: linear-gradient(90deg, var(--gold-lo), transparent); }

.ln { display: block; overflow: hidden; padding-block: .08em; }
.ln > span { display: block; }

/* ── the lights canvas sits between the footage and the words ────── */
#lights { position: fixed; inset: 0; z-index: 1; pointer-events: none; }

/* ── film layers ─────────────────────────────────────────────────── */

.act { position: relative; }
.stage { position: relative; min-height: 100svh; display: grid; place-items: center; overflow: hidden; }
.film { position: absolute; inset: 0; overflow: hidden; z-index: 0; background: var(--night); }
.shot { position: absolute; inset: 0; will-change: transform, opacity; }
/* footage is texture now, not the act: it sits well back behind the bulbs */
.shot-v { width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; opacity: .55; filter: saturate(.85); }

.grade { position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(118% 82% at 50% 42%, transparent 30%, rgba(8,3,7,.62) 74%, rgba(8,3,7,.94) 100%),
    linear-gradient(180deg, rgba(18,6,16,.7) 0%, rgba(18,6,16,.2) 30%, rgba(18,6,16,.3) 62%, rgba(18,6,16,.88) 100%); }
.grade-deep { background:
    radial-gradient(110% 76% at 50% 46%, rgba(8,3,7,.34) 0%, rgba(8,3,7,.78) 70%, rgba(8,3,7,.95) 100%),
    linear-gradient(0deg, rgba(30,12,27,.5), rgba(30,12,27,.5)); }
.grade-book { background:
    radial-gradient(110% 80% at 26% 30%, rgba(8,3,7,.46) 0%, rgba(8,3,7,.9) 64%, rgba(8,3,7,.98) 100%),
    linear-gradient(0deg, rgba(18,6,16,.7), rgba(18,6,16,.7)); }

.layer { position: relative; z-index: 3; width: 100%; max-width: 1180px; padding: 124px var(--gut) 96px; text-align: center; }
.layer::before {
  content: ''; position: absolute; inset: -18% -10%; z-index: -1; pointer-events: none;
  background: radial-gradient(ellipse 72% 56% at 50% 50%, rgba(8,3,7,.52) 0%, rgba(8,3,7,.3) 38%, rgba(8,3,7,.08) 66%, transparent 86%);
}

.grain {
  position: fixed; inset: -60px; z-index: 60; pointer-events: none;
  background: url(img/grain.png) repeat; background-size: 128px 128px;
  opacity: .05; mix-blend-mode: overlay;
}

/* ── top bar ─────────────────────────────────────────────────────── */

.testmode { position: relative; z-index: 120; background: var(--magenta); color: #fff; text-align: center; font-size: 14px; line-height: 1.5; padding: 7px 16px; }
/* The banner is in flow and the bar is fixed, so they only ever share the top
   of the screen at scroll 0 — where the bar is still hidden. The banner's
   higher z-index settles it; pushing the bar down instead left it floating
   over a gap for the rest of the page. */
.bar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 14px clamp(16px, 4vw, 40px);
  padding-top: calc(14px + env(safe-area-inset-top, 0px));
  background: linear-gradient(180deg, rgba(10,4,9,.85), rgba(10,4,9,0));
  backdrop-filter: blur(6px);
  opacity: 0; transform: translateY(-100%); pointer-events: none;
}
.bar.on { opacity: 1; transform: none; pointer-events: auto; }
.bar-club { font: 600 12px/1 var(--text); letter-spacing: .24em; text-transform: uppercase; color: var(--cream); white-space: nowrap; }
.bar-cta {
  display: inline-flex; align-items: center; min-height: 44px;   /* a thumb, not a pointer */
  font: 600 12px/1 var(--text); letter-spacing: .14em; text-transform: uppercase;
  color: var(--night); background: var(--gold); text-decoration: none; padding: 0 18px; white-space: nowrap;
  box-shadow: 0 0 calc(10px + 18px * var(--lit)) rgba(240,185,94,.35);
}
.bar-cta:hover { background: var(--cream); }

/* ── act 1 · the lights come on ──────────────────────────────────── */

.crest { width: clamp(104px, 12vw, 164px); height: auto; margin: 0 auto clamp(22px, 3vw, 34px);
  filter: drop-shadow(0 8px 30px rgba(0,0,0,.6)) drop-shadow(0 0 calc(12px + 26px * var(--lit)) rgba(240,185,94,calc(.14 + .3 * var(--lit)))); }
.club-name { font-size: clamp(34px, 6.4vw, 92px); line-height: 1.02; }
.presents-word { letter-spacing: .02em; }

/* The title card's sponsor block. No panels and no plates: the marks sit on the
   night itself, lifted off it by a shadow and by the string of lights above
   them, because a flat box is the thing that got the earlier versions binned. */
.open-by, .presents-word { margin: clamp(16px, 2.2vw, 26px) 0 0; }
.open-by {
  font: 400 clamp(11px, 1.15vw, 14px)/1 var(--text); letter-spacing: .34em; text-indent: .34em;
  color: rgba(251, 239, 216, .62);
}
/* ── sponsor sizing, one ratio set ───────────────────────────────────
   "The same size" means optically the same, which is NOT the same pixel
   height: Indo Bazaar is a solid plate and Motorhome is thin line art, so
   equal heights make Motorhome look half the size. Each mark was measured
   (alpha bounding box, checked by eye on the night background at 2x) and
   given a multiplier below. A group then sets only --u, its unit, and the
   whole set rescales together without the balance drifting.
     club lead  · Reliance 164 · Geco 128 — the two leads, optically equal.
                  Geco's 128 is its measured 85 carried onto Reliance's scale
                  (85 x 164/109), not a guess.
     club mid   · Indo Bazaar 82, a tier of its own: above the rest, below
                  the leads, on its own centred line
     club row   · Home Foods 66 · Motorhome 58 · Rcorp 80 · Kanthari 74
                  (these four equal)
     event six  · Lifeline 50 · Ente Keralam 82 · Abaarco 42
                · Liverpool Gifts 90 · Liverpool Dreams 94 · Spotless Pro 76 */
.s-rel img { height: calc(164 * var(--u)); }
.s-ib  img { height: calc( 82 * var(--u)); }
.s-hf  img { height: calc( 66 * var(--u)); }
.s-mh  img { height: calc( 58 * var(--u)); }
.s-rc  img { height: calc( 80 * var(--u)); }
.s-ka  img { height: calc( 74 * var(--u)); }
/* Geco is emblem + caption, so the emblem comes down from its measured 85 and
   the two together carry the weight. Its source is 148x109, so it is never
   asked to render much past ~120px, where it would start to go soft. */
.s-ge  img { height: calc(116 * var(--u)); }
/* Beside Kanthari rather than Reliance, Geco drops to its own measured value
   so the boat club's two read level with each other. */
.spon-boat-row .s-ge img { height: calc( 85 * var(--u)); }
.s-rel, .s-ge, .s-ka, .e-ld { flex-direction: column; gap: calc(7 * var(--u)); }
/* Only in the boat club does Kanthari carry a caption; in the title card's
   small row it is a bare mark like the rest of that line. */
.open-row .s-ka { flex-direction: row; }
.sp-name {
  display: block; font: 600 calc(15 * var(--u))/1 var(--text);
  /* The ratio still sets the size; 11px is only a floor. On a 360 phone the
     lead pair's --u of .58 put these captions at 8.7px, where a tracked
     uppercase line stops being a name and becomes texture. Measured, not
     guessed — see the mobile QA pass. */
  font-size: max(11px, calc(15 * var(--u)));
  letter-spacing: .14em; text-indent: .14em; text-transform: uppercase;
  color: #DCB97B; text-align: center; max-width: 100%;
}
/* A mark whose column is narrower than its natural width at the ratio height
   used to be squashed to fit — the global img{max-width:100%} with an explicit
   height is object-fit:fill, and Abaarco (6.5:1, the widest of the six) came
   out 38% narrow on a 360 phone. Contain scales it down instead of distorting
   it; the layouts below are sized so it rarely has to. */
.open-lead img, .open-mid img, .open-row img, .ev-row img, .spon-row img { object-fit: contain; }
/* The lead pair's names may run to two lines; forcing them onto one is what
   would push the two logos apart and break the "never stack" rule. */
.open-lead .sp-name, .spon-lead .sp-name { line-height: 1.25; }
.ev-row .sp-name, .ev-row-lg .sp-name { white-space: nowrap; }
.e-lf  img { height: calc( 50 * var(--u)); }
.e-ek  img { height: calc( 82 * var(--u)); }
.e-ab  img { height: calc( 42 * var(--u)); }
.e-lg  img { height: calc( 90 * var(--u)); }
/* Liverpool Dreams is emblem + caption, so the emblem comes down a little and
   the two together carry the same weight as the other five. */
.e-ld  img { height: calc( 84 * var(--u)); }
.e-sp  img { height: calc( 76 * var(--u)); }

/* The main sponsor, alone and on its own larger unit. */
/* The lead pair never stacks — it is two across at every width, 360 included,
   so the unit is sized to fit two logos and two captions on one line. */
.open-lead {
  --u: clamp(.58px, .056vw, .84px);
  list-style: none; display: flex; align-items: flex-end; justify-content: center;
  flex-wrap: nowrap; gap: clamp(14px, 2.6vw, 48px);
  margin: clamp(12px, 1.6vw, 18px) auto 0; padding: 0;
}
.open-mid {
  --u: clamp(.52px, .05vw, .70px);
  list-style: none; display: flex; justify-content: center;
  margin: clamp(12px, 1.5vw, 19px) auto 0; padding: 0;
}
.open-mid li, .open-lead li { display: flex; align-items: center; }
.open-mid img { display: block; width: auto; filter: drop-shadow(0 4px 16px rgba(0, 0, 0, .62)); }
.open-lead li { display: flex; align-items: center; }
.open-lead img {
  display: block; width: auto;
  filter: drop-shadow(0 6px 22px rgba(0, 0, 0, .66))
          drop-shadow(0 0 calc(8px + 20px * var(--lit)) rgba(240, 185, 94, calc(.08 + .2 * var(--lit))));
}
/* The title card's row carries six small marks, so Geco's caption is dropped
   here: set in a strip this dense it is wider than the mark itself and pushed
   Geco onto a second line on its own. The oval already reads "GECO", and the
   full name is spelled out under the large Geco in the boat club group. */
.open-row .sp-name { display: none; }
.open-row {
  --u: clamp(.38px, .036vw, .50px);
  list-style: none; display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: clamp(16px, 2.6vw, 36px);
  margin: clamp(12px, 1.5vw, 19px) auto 0; padding: 0;
}
.open-row li { display: flex; align-items: center; }
.open-row img {
  display: block; width: auto; filter: drop-shadow(0 4px 16px rgba(0, 0, 0, .62));
}

/* Phone: the lead pair stays two across even at 360 — the owner asked for it
   explicitly and it must never stack — then Indo Bazaar on its own centred
   line, then the four small marks 2 x 2. Captions wrap to two lines rather
   than forcing the pair apart. */
@media (max-width: 640px) {
  .open-lead { --u: .58px; max-width: 352px; gap: 12px; }
  .open-mid  { --u: .56px; }
  .open-row {
    --u: .44px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px 10px; max-width: 300px; justify-items: center; align-items: center;
  }
  .open-row li { justify-content: center; }
}

.cue { position: absolute; bottom: 26px; left: 0; right: 0; z-index: 4; margin: 0; text-align: center;
  font: 600 10px/1 var(--text); letter-spacing: .4em; text-indent: .4em; text-transform: uppercase; color: rgba(251,239,216,.55); }
.cue-rule { display: block; width: 1px; height: 44px; margin: 0 auto 13px; background: linear-gradient(180deg, transparent, var(--gold-lo)); transform-origin: top; }

/* ── act 2 · the name ────────────────────────────────────────────── */

/* The event's own six, directly under the name once it has landed. Same
   multipliers as everywhere else, a smaller --u so they sit under the mark
   without competing with it. */
.ev-by {
  margin: clamp(14px, 1.7vw, 21px) 0 0;
  font: 400 clamp(11px, 1.15vw, 14px)/1 var(--text);
  letter-spacing: .34em; text-indent: .34em; text-transform: uppercase;
  color: rgba(251, 239, 216, .62); text-align: center;
}
.ev-row {
  --u: clamp(.60px, .057vw, .83px);
  list-style: none; display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: clamp(18px, 2.2vw, 36px);
  margin: clamp(10px, 1.3vw, 16px) auto 0; padding: 0;
}
.ev-row li { flex: 0 0 auto; display: flex; align-items: center; }
.ev-row img {
  display: block; width: auto;
  filter: drop-shadow(0 4px 16px rgba(0, 0, 0, .62))
          drop-shadow(0 0 calc(6px + 16px * var(--lit)) rgba(240, 185, 94, calc(.05 + .16 * var(--lit))));
}
/* Phone: 2 + 2 + 2, never a stray sixth on a line of its own.
   It was 3 + 3, which gave each mark a 105px column — narrower than Abaarco
   needs at its equal-mass height (6.5:1 needs ~170px), so the widest of the
   six was squashed to fit. Two columns give ~165px at 360 and more above it,
   which all six clear. Same six, same order, same multipliers. */
@media (max-width: 640px) {
  .ev-row {
    --u: .62px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 18px 10px; max-width: 350px; justify-items: center; align-items: center;
  }
  /* Two to a line leaves room for the captions to wrap instead of running out
     of their column. */
  .ev-row .sp-name, .ev-row-lg .sp-name { white-space: normal; line-height: 1.25; }
}

.mark { margin: 0; display: flex; justify-content: center; }
/* max-width is the guard: 88vw (94vw on a phone) plus the layer's own gutters
   is wider than the screen, and a grid item that overflows a centred track is
   not clipped evenly — the whole act shifted right and the V of UTSAV was cut
   off the edge of a 360 phone. */
.mark-box { position: relative; display: block; width: min(820px, 88vw); max-width: 100%; aspect-ratio: 1220 / 795; }
.mark-part { position: absolute; inset: 0; width: 100%; height: 100%;
  filter: drop-shadow(0 0 calc(22px + 40px * var(--lit)) rgba(242,104,42,calc(.2 + .3 * var(--lit)))); }
.star-flare {
  position: absolute; left: 67.6%; top: 58.5%; width: 34%; aspect-ratio: 1;
  background: radial-gradient(circle, rgba(255,246,220,.95) 0%, rgba(255,196,86,.6) 24%, rgba(242,104,42,.3) 46%, transparent 70%);
  opacity: 0; pointer-events: none; mix-blend-mode: screen;
}
.bloom {
  position: absolute; left: 50%; top: 46%; width: 150%; aspect-ratio: 1;
  background: radial-gradient(circle, rgba(255,222,170,.6) 0%, rgba(242,104,42,.22) 32%, transparent 62%);
  opacity: 0; pointer-events: none; mix-blend-mode: screen;
}
.strap { margin: clamp(24px, 3.4vw, 46px) 0 0; font: 600 clamp(11px, 1.25vw, 15px)/1 var(--text);
  letter-spacing: .34em; text-indent: .34em; text-transform: uppercase; color: var(--cream); }

/* ── act 3 · the sponsors, all together ──────────────────────────── */

.layer-spon { max-width: 1320px; }
.spon-row { display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: clamp(26px, 3vw, 46px); list-style: none; margin: 0; padding: 0; }
.spon-row li { flex: 0 0 auto; display: flex; align-items: center; }
.spon-row img { display: block; width: auto; opacity: .95;
  filter: drop-shadow(0 0 calc(8px + 22px * var(--lit)) rgba(240,185,94, calc(.06 + .2 * var(--lit)))); }

/* Two groups: the club's marks keep the title card's hierarchy, the event's
   six all read at one size. Only --u differs from the opening. */
.spon-group + .spon-group { margin-top: clamp(34px, 4.4vw, 62px); }
.spon-h {
  margin: 0 0 clamp(16px, 2vw, 26px);
  font: 400 clamp(11px, 1.15vw, 14px)/1 var(--text);
  letter-spacing: .34em; text-indent: .34em; text-transform: uppercase;
  color: rgba(251, 239, 216, .62); text-align: center;
}
.spon-lead { --u: clamp(.70px, .085vw, 1.22px); }
.spon-mid  { --u: clamp(.56px, .066vw, .95px); margin-top: clamp(20px, 2.6vw, 34px); }
.spon-rest { --u: clamp(.46px, .054vw, .76px); margin-top: clamp(18px, 2.3vw, 30px); }
/* The boat club's two read a step BELOW Indo Bazaar — not beside Reliance.
   Owner, 15 Nov: "make geeco and kanthari smaller". They were on --u 1.84 at
   1440, which put both at an optical 184 (the geometric mean of the rendered
   box, as the ratio note above measures it) against Reliance's own 183: the
   two boat-club marks were the joint largest things in the block, and on a
   phone they were half again bigger than Reliance. This unit lands them at
   154 and 156 against Indo Bazaar's 160 — Indo's size, a shade under it.
   Geco is an SVG now, so nothing here is a resolution ceiling. */
.spon-boat-row { --u: clamp(.80px, .108vw, 1.56px); gap: clamp(24px, 3.2vw, 52px); }
/* The caption is a name, not a second mark: at the lead pair's proportions a
   caption is ~9% of its logo's height, and 8 x --u is that on this unit. The
   12px floor is the legibility one, as everywhere else on this block. */
.spon-boat-row .sp-name { font-size: max(12px, calc(8 * var(--u))); }
.ev-row-lg { --u: clamp(.62px, .072vw, 1px); }

/* ── act 4 · the night ───────────────────────────────────────────── */

.facts { margin: 0; padding: 0; }
.fact { margin: 0; }
.fact + .fact { margin-top: clamp(64px, 11vh, 132px); }
.fact-big { font-size: clamp(32px, 5.6vw, 78px); line-height: 1.04; }
.fact-sub { margin: clamp(16px, 2.2vw, 26px) auto 0; max-width: 36ch; font-size: clamp(15px, 1.5vw, 18px); color: var(--mist);
  text-shadow: 0 1px 3px rgba(8,3,7,.85), 0 2px 18px rgba(8,3,7,.8); }

/* ── act 5 · book ────────────────────────────────────────────────── */

.act-book { position: relative; overflow: hidden; padding: clamp(96px, 14vh, 170px) 0 0; }
.film-book { position: absolute; inset: 0; overflow: hidden; z-index: 0; }
.film-book .shot { position: absolute; inset: 0; }
.book-in {
  position: relative; z-index: 3;
  display: grid; grid-template-columns: minmax(0, .92fr) minmax(0, 1fr);
  gap: clamp(36px, 6vw, 88px); align-items: start;
  max-width: 1180px; margin: 0 auto; padding: 0 var(--gut);
}
.book-h { font-size: clamp(38px, 5.2vw, 76px); line-height: 1; }
/* This column sits straight on the crowd footage with the last string of
   bulbs burning behind it, and on a phone the bokeh lands right under the
   headings. The words need their own ground. */
.book-say { text-shadow: 0 1px 3px rgba(8,3,7,.9), 0 2px 22px rgba(8,3,7,.85); }
.book-p { margin: clamp(22px, 3vw, 32px) 0 0; max-width: 34ch; font-size: clamp(16px, 1.55vw, 18px); }
.book-pts { list-style: none; margin: clamp(28px, 4vw, 42px) 0 0; padding: 0; }
.book-pts li { display: grid; gap: 3px; padding: 15px 0; border-top: 1px solid rgba(138,102,50,.42); }
.book-pts li:last-child { border-bottom: 1px solid rgba(138,102,50,.42); }
.book-pts b { font: 600 12px/1 var(--text); letter-spacing: .22em; text-transform: uppercase; color: var(--gold); }
.book-pts span { font-size: 16px; color: var(--cream); }

.sheet {
  background: linear-gradient(180deg, rgba(30,12,27,.93), rgba(14,5,12,.96));
  border: 1px solid rgba(138,102,50,.45);
  box-shadow: 0 34px 90px rgba(0,0,0,.6), 0 0 calc(20px + 50px * var(--lit)) rgba(240,185,94,calc(.05 + .13 * var(--lit)));
  backdrop-filter: blur(14px);
}
.sheet-top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 24px clamp(20px, 2.4vw, 32px) 18px; border-bottom: 1px solid rgba(138,102,50,.32); }
.sheet-top h3 { font-size: clamp(22px, 2.2vw, 30px); }
.sheet-top .each { font: 600 clamp(22px, 2.3vw, 30px)/1 var(--display);
                   font-variation-settings: 'opsz' 16; font-variant-numeric: tabular-nums;
                   color: var(--gold); white-space: nowrap; }
.sheet-top .each small { font: 400 14px var(--text); color: var(--mist); }
#buy, .closed { padding: 22px clamp(20px, 2.4vw, 32px) 28px; }

.qty { display: grid; grid-template-columns: 60px 1fr 60px; align-items: stretch; border: 1px solid rgba(138,102,50,.5); background: rgba(8,3,7,.5); }
.qty button { height: 68px; border: 0; background: transparent; color: var(--gold); font: 300 30px/1 var(--text); cursor: pointer; transition: background .18s; }
.qty button:hover:not(:disabled) { background: rgba(240,185,94,.14); }
.qty button:disabled { color: rgba(138,102,50,.45); cursor: not-allowed; }
#qty { display: grid; place-content: center; gap: 4px; font: 600 30px/1 var(--display); font-variation-settings: 'opsz' 96; color: var(--cream); }
#qty small { font: 600 10px/1 var(--text); letter-spacing: .3em; text-indent: .3em; text-transform: uppercase; color: var(--gold-lo); }
.hint { margin: 10px 0 24px; font-size: 14px; color: rgba(218,204,200,.72); }

/* scroll-margin is what a browser's own "scroll the focused field into view"
   honours, and what cine.js's keyboard handler uses: enough at the top to
   clear the fixed bar, enough at the bottom to clear the dock. */
.field { display: grid; gap: 7px; margin-bottom: 17px; scroll-margin: 90px 0 110px; }
.code-row, .tiers, .summary, .pay { scroll-margin: 90px 0 110px; }
.field > span { font: 600 11px/1 var(--text); letter-spacing: .22em; text-transform: uppercase; color: var(--gold); }
.field > span i { font-size: 13px; font-style: normal; font-weight: 400; letter-spacing: .02em; text-transform: none; color: rgba(218,204,200,.72); }
.field input {
  font: 400 17px/1.4 var(--text); color: var(--cream);
  background: rgba(8,3,7,.55); border: 1px solid rgba(138,102,50,.45);
  padding: 13px 14px; width: 100%; transition: border-color .18s, background .18s;
}
.field input:hover { border-color: rgba(240,185,94,.6); }
.field input:focus { outline: none; border-color: var(--gold); background: rgba(8,3,7,.8); }

.pay {
  display: flex; align-items: center; justify-content: space-between; gap: 14px; width: 100%;
  margin-top: 8px; padding: 18px 22px; cursor: pointer; border: 0;
  background: var(--gold); color: var(--night);
  font: 700 14px/1 var(--text); letter-spacing: .16em; text-transform: uppercase;
  box-shadow: 0 0 calc(12px + 34px * var(--lit)) rgba(240,185,94,calc(.14 + .3 * var(--lit)));
  transition: background .2s, transform .12s;
}
.pay:hover:not(:disabled) { background: var(--cream); }
.pay:active { transform: translateY(1px); }
.pay:disabled { opacity: .6; cursor: progress; }
.pay .sum { font: 600 16px/1 var(--display); font-variation-settings: 'opsz' 16;
            font-variant-numeric: tabular-nums; letter-spacing: 0; }
.secure { margin: 14px 0 0; font-size: 15px; line-height: 1.6; color: rgba(218,204,200,.72); }
.err { margin: 14px 0 0; padding: 11px 13px; font-size: 15px; color: #FFD9D4; background: rgba(163,32,27,.32); border-left: 2px solid #E05A50; }
.note { margin: 14px 0 0; padding: 11px 13px; font-size: 15px; color: var(--cream); background: rgba(240,185,94,.14); border-left: 2px solid var(--gold); }
.closed p:first-child { margin: 0 0 8px; font-size: clamp(26px, 2.8vw, 34px); }
.closed p:last-child { margin: 0; color: var(--mist); }

.print {
  position: relative; z-index: 3;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: clamp(24px, 4vw, 56px);
  max-width: 1180px; margin: clamp(72px, 11vh, 130px) auto 0; padding: 0 var(--gut) clamp(72px, 10vh, 120px);
}
.print h4 { margin: 0 0 8px; font: 600 11px/1 var(--text); letter-spacing: .26em; text-transform: uppercase; color: var(--gold); }
.print p { margin: 0; font-size: 15px; line-height: 1.7; color: rgba(218,204,200,.82); }

/* ── footer · powered by Rcorp ───────────────────────────────────── */

.foot { position: relative; z-index: 3; display: flex; flex-direction: column; align-items: center; gap: 18px;
  padding: 52px var(--gut) 56px; background: var(--night); border-top: 1px solid rgba(138,102,50,.26); }
.foot-crest { width: 44px; height: auto; opacity: .75; }
/* line-height 1 was fine while this fitted on one line; on a 360 phone it wraps
   and the two lines all but touched. */
.foot-club { margin: 0; font: 600 11px/1.5 var(--text); letter-spacing: .26em; text-transform: uppercase; color: rgba(218,204,200,.55); text-align: center; }
.powered {
  display: flex; align-items: center; gap: 14px; margin: 10px 0 0;
  padding-top: 20px; border-top: 1px solid rgba(138,102,50,.22);
}
.powered span { font: 600 10px/1 var(--text); letter-spacing: .3em; text-transform: uppercase; color: rgba(218,204,200,.5); }
.powered img { width: auto; height: 26px; opacity: .8; }



/* ── the night, and the competitions ─────────────────────────────── */

.act-on, .act-comps { position: relative; background: var(--night); padding: clamp(76px, 12vh, 140px) 0; }
.act-comps { border-top: 1px solid rgba(138,102,50,.22); }
.on-in, .comps-in { position: relative; z-index: 3; max-width: 1080px; margin: 0 auto; padding: 0 var(--gut); }

.sec-h { font-size: clamp(36px, 5.2vw, 72px); line-height: 1.02; text-align: center; }
.lede {
  margin: clamp(22px, 3vw, 32px) auto 0; max-width: 40ch; text-align: center;
  font-size: clamp(17px, 1.75vw, 21px); line-height: 1.62; color: rgba(251,239,216,.9);
}

/* the reference card: what someone scanning actually needs */
.glance {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px; margin: clamp(44px, 6vw, 68px) 0 0; padding: 0;
  background: rgba(138,102,50,.3); border: 1px solid rgba(138,102,50,.3);
}
.glance > div { margin: 0; padding: clamp(18px, 2.4vw, 26px); background: #160A14; }
.glance dt { margin: 0 0 9px; font: 600 10px/1 var(--text); letter-spacing: .3em; text-transform: uppercase; color: var(--gold); }
/* Prices live in here, so it takes the text optical size as well — see the
   note by .cta-sum. */
.glance dd { margin: 0; font: 600 clamp(16px, 1.5vw, 19px)/1.3 var(--display); font-variation-settings: 'opsz' 16; color: var(--cream); text-wrap: balance; }
.glance dd span { display: block; margin-top: 5px; font: 400 15px/1.55 var(--text); color: rgba(218,204,200,.78); }
.glance-note { margin: 14px 0 0; font-size: 15px; color: var(--gold); }

/* what's on: a title, one line, and a frame of the night itself */
.bill {
  list-style: none; margin: clamp(50px, 7vw, 76px) 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: clamp(14px, 1.8vw, 24px);
}
.bill li { margin: 0; }
.bill figure { margin: 0 0 13px; overflow: hidden; border: 1px solid rgba(138,102,50,.32); }
.bill img { width: 100%; height: clamp(118px, 11vw, 156px); object-fit: cover; display: block; filter: saturate(.92) brightness(.92); }
.bill h3 { margin: 0; font: 500 clamp(17px, 1.55vw, 21px)/1.2 var(--display); font-variation-settings: 'opsz' 96; color: var(--cream); text-wrap: balance; }
.bill p { margin: 7px 0 0; font-size: 15px; line-height: 1.55; color: rgba(218,204,200,.82); }

/* competitions: a panel each, rules scannable. No prizes and no registration
   package — the owner took the cash prizes and the £60 package off on 14 Nov. */
.comps { list-style: none; margin: clamp(44px, 6vw, 68px) 0 0; padding: 0; display: grid; gap: clamp(16px, 2vw, 22px); }
.comp {
  display: grid; gap: 22px clamp(24px, 4vw, 56px); align-items: center;
  padding: clamp(24px, 3.2vw, 36px) clamp(22px, 3vw, 38px);
  background: linear-gradient(180deg, rgba(30,12,27,.72), rgba(16,6,14,.82));
  border: 1px solid rgba(138,102,50,.34);
}
.comp-h { margin: 0; font-size: clamp(22px, 2.6vw, 34px); line-height: 1.1; }
.rules { margin: 14px 0 0; padding: 0 0 0 1.1em; }
.rules li { margin: 0 0 7px; font-size: clamp(15px, 1.4vw, 16px); line-height: 1.55; color: rgba(218,204,200,.84); }
.rules li::marker { color: var(--gold-lo); }
/* "Want to register? <number>" — one line, no name against the number */
.register {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 10px clamp(14px, 2vw, 22px); margin: clamp(26px, 3.4vw, 40px) 0 0; text-align: center;
  padding: clamp(18px, 2.4vw, 26px); border: 1px solid rgba(240,185,94,.42); background: rgba(240,185,94,.07);
}
.register > span { font: 600 11px/1 var(--text); letter-spacing: .3em; text-transform: uppercase; color: var(--gold); }

.enq-list { list-style: none; margin: clamp(20px, 2.6vw, 28px) 0 0; padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 12px clamp(26px, 4vw, 48px); }
.enq-list li { display: flex; align-items: center; gap: 11px; }
/* The number people ring to enter a competition, so it has to survive a phone.
   At 'opsz' 96 and 17px Bodoni's hairlines drop out and "+44 7733 020909" read
   as broken stems — the same trap already documented for the money and the
   steppers. Text optical size, the weight to hold on the dark ground, tabular
   figures, and a 44px box around it. */
.enq-tel {
  display: inline-flex; align-items: center; min-height: 44px;
  font: 500 clamp(19px, 1.8vw, 22px)/1 var(--display);
  font-variation-settings: 'opsz' 16; font-weight: 600; font-variant-numeric: tabular-nums;
  letter-spacing: .01em; padding-inline: 2px;   /* Bodoni's + has a tight left bearing */
  color: var(--cream); text-decoration: none;
}
.enq-tel:hover { color: var(--gold); }
.enq-wa {
  display: inline-flex; align-items: center; min-height: 44px;
  font: 600 11px/1 var(--text); letter-spacing: .14em; text-transform: uppercase; text-decoration: none;
  color: var(--gold); border: 1px solid rgba(138,102,50,.6); padding: 0 14px;
}
.enq-wa:hover { background: rgba(240,185,94,.14); }

/* every section ends where the page is going */
.cta-wrap { margin: clamp(46px, 6vw, 72px) 0 0; text-align: center; }
.cta {
  display: inline-flex; align-items: center; gap: 16px; padding: 17px 26px;
  background: var(--gold); color: var(--night); text-decoration: none;
  font: 700 13px/1 var(--text); letter-spacing: .16em; text-transform: uppercase;
  box-shadow: 0 0 calc(10px + 30px * var(--lit)) rgba(240,185,94, calc(.1 + .26 * var(--lit)));
  transition: background .2s, transform .12s;
}
.cta:hover { background: var(--cream); }
.cta:active { transform: translateY(1px); }
/* Every live money figure, and all of them small. Bodoni Moda's 'opsz' 96 cut
   is a display face: below ~28px its hairlines drop out, and the hairline here
   is the 1 — "£37.12" rendered as "£37.2" on the owner's phone, which is how
   this was found. Money gets the text optical size, the weight to survive on
   the dark ground, and tabular figures so the pence column does not dance as
   the steppers change. The same trap already ate the steppers' zeroes.

   The settings go *after* the `font:` shorthand in every one of these rules,
   never grouped above them: the shorthand resets font-variation-settings to
   normal, which is how the first attempt at this fix silently did nothing. */
.cta-sum { font: 600 18px/1 var(--display); font-variation-settings: 'opsz' 16;
           font-variant-numeric: tabular-nums; letter-spacing: 0; }

.reveal { opacity: 1; }
.cine .reveal { opacity: 0; }

/* ── the booking form ────────────────────────────────────────────── */

.sheet-top .left { font: 600 11px/1 var(--text); letter-spacing: .2em; text-transform: uppercase; color: var(--gold-lo); }

.tiers { display: grid; gap: 10px; margin-bottom: 14px; }
.tier {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 12px 14px; border: 1px solid rgba(138,102,50,.4); background: rgba(8,3,7,.45);
}
.tier-what b { display: block; font: 600 15px/1.2 var(--text); color: var(--cream); }
.tier-what span { font-size: 15px; color: rgba(218,204,200,.8); }
.step { display: grid; grid-template-columns: 48px 44px 48px; align-items: center; }
/* 48px square: a thumb on a phone, not a mouse pointer */
.step button {
  height: 48px; border: 1px solid rgba(138,102,50,.5); background: rgba(8,3,7,.4);
  color: var(--gold); font: 300 24px/1 var(--text); cursor: pointer; transition: background .16s;
}
.step button:hover:not(:disabled) { background: rgba(240,185,94,.16); }
.step button:disabled { color: rgba(138,102,50,.4); cursor: not-allowed; }
/* Bodoni Moda at 'opsz' 96 is a display cut: at 22px its hairlines disappear and
   a 0 reads as two bare stems, so the ticket counts looked like "()" on the
   booking form. The steppers are a control, not a title - give them the text
   optical size and the weight to survive on the dark ground. */
.step output { text-align: center; font-size: 22px; color: var(--cream); font-variant-numeric: tabular-nums;
  font-variation-settings: 'opsz' 16; font-weight: 600; }
/* The fee note sits with the price, never under the total: the price shown is
   already all-in (DMCC Act 2024), and this says so. */
/* nowrap: at 360 the stepper left this column ~108px and "+ platform fees"
   broke across two lines in all three tiers, which read as a layout fault
   rather than a note. */
.tier-fee { display: block; margin-top: 4px; font: 400 11px/1.2 var(--text); font-style: normal; letter-spacing: .05em; text-transform: uppercase; color: var(--gold-lo); white-space: nowrap; }
.tier-what { min-width: 0; }
.tiers-note { margin: 0 0 16px; font-size: 15px; line-height: 1.5; color: rgba(218,204,200,.78); }

/* The order summary, and the last thing read before the pay button: the
   tickets at the advertised price, the platform fees on their own line, then
   the total. The fee row is full size — it is a charge, not a footnote. */
.summary { margin: 0 0 18px; padding: 14px; border: 1px solid rgba(138,102,50,.4); background: rgba(8,3,7,.45); display: grid; gap: 8px; }
.summary > div { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; }
.summary dt, .summary dd { margin: 0; font: 400 15px/1.35 var(--text); color: rgba(218,204,200,.85); }
.summary dd { font-variant-numeric: tabular-nums; }
.summary-fee dt, .summary-fee dd { color: var(--gold-lo); }
.summary.nofee .summary-fee { display: none; }
.summary-tot { padding-top: 8px; border-top: 1px solid rgba(138,102,50,.32); }
.summary-tot dt { font-weight: 600; color: var(--cream); }
.summary-tot dd { font: 600 19px/1 var(--display); font-variation-settings: 'opsz' 16;
                  font-variant-numeric: tabular-nums; color: var(--gold); }

.codebox { margin: 0 0 18px; }
.code-toggle {
  display: inline-flex; align-items: center; min-height: 44px;
  border: 0; background: none; padding: 0; cursor: pointer;
  font: 600 13px/1 var(--text); letter-spacing: .14em; text-transform: uppercase; color: var(--gold);
  text-decoration: underline; text-underline-offset: 4px;
}
.code-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; margin-top: 12px; }
.code-row input {
  font: 400 16px/1.4 var(--text); color: var(--cream); text-transform: uppercase;
  background: rgba(8,3,7,.55); border: 1px solid rgba(138,102,50,.45); padding: 13px 14px; width: 100%;
}
.code-row input:focus { outline: none; border-color: var(--gold); }
.code-apply {
  border: 1px solid var(--gold); background: none; color: var(--gold); cursor: pointer;
  font: 600 12px/1 var(--text); letter-spacing: .14em; text-transform: uppercase; padding: 0 18px;
}
.code-apply:hover:not(:disabled) { background: rgba(240,185,94,.16); }
.code-msg { margin: 10px 0 0; font-size: 15px; color: var(--gold); }
.code-msg.bad { color: #FFB4AC; }

/* a booking button that is always one tap away on a phone */
.book-dock {
  position: fixed; left: 12px; right: 12px; z-index: 110;
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));   /* clear of the home bar */
  display: none; align-items: center; justify-content: space-between; gap: 12px;
  min-height: 52px; padding: 16px 20px; background: var(--gold); color: var(--night); text-decoration: none;
  font: 700 14px/1 var(--text); letter-spacing: .14em; text-transform: uppercase;
  box-shadow: 0 10px 34px rgba(0,0,0,.5);
  transition: transform .25s ease, opacity .25s ease;
}
.book-dock span { font: 600 17px/1 var(--display); font-variation-settings: 'opsz' 16;
                  font-variant-numeric: tabular-nums; letter-spacing: 0; }
/* `away` is app.js's — it drops the dock once the form itself is on screen.
   `hushed` is this stylesheet's and cine.js's, for the moments when the dock
   would otherwise sit directly on top of something that already says the same
   thing: an in-flow "Book tickets" button, or an open keyboard. Two names so
   the two owners never overwrite each other's decision. */
.book-dock.away, .book-dock.hushed { transform: translateY(140%); opacity: 0; pointer-events: none; }

.book-close { margin: clamp(26px, 4vw, 36px) 0 0; font-size: clamp(16px, 1.6vw, 19px); color: var(--cream); max-width: 32ch; }
.foot-enq { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px 18px; margin: 0; }
.foot-enq a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 6px;
  font: 600 15px/1 var(--text); letter-spacing: .06em; font-variant-numeric: tabular-nums;
  color: rgba(218,204,200,.82); text-decoration: none; }
.foot-enq a:hover { color: var(--gold); }

/* ─────────────────────────────────────────────────────────────────
   CINEMA MODE — added by cine.js once GSAP is driving the page.
   Everything above is the static, readable fallback.
   ───────────────────────────────────────────────────────────────── */

.cine .stage { height: 100svh; }
.cine .act-arrive { height: 220svh; }
.cine .act-logo   { height: 340svh; }
.cine .act-spon   { height: 240svh; }
.cine .act-info   { height: 460svh; }

.cine .act-info .facts { display: grid; }
.cine .act-info .fact { grid-area: 1 / 1; align-self: center; opacity: 0; }
.cine .act-info .fact + .fact { margin-top: 0; }
.cine .ln > span { transform: translateY(112%); }
.cine .strap, .cine .layer-spon [data-beat] { opacity: 0; }
/* The event six wait for the name to land, then come up under it. Scoped to
   act 2 — the same six appear again in the sponsors block, which has its own
   reveal and must not be held hidden by this rule. */
.cine #act-logo .ev-by, .cine #act-logo .ev-row li { opacity: 0; }
/* The title card's sponsors catch one beat at a time. A fromTo later in a
   timeline does not apply its from-state until it starts, so without this the
   whole block is on screen from the first frame and there is no sequence. Gated
   on .cine, so with no JS or reduced motion they are simply visible. */
.cine .open-by .ln > span { transform: translateY(112%); }
.cine .open-lead img, .cine .open-row li { opacity: 0; }
.cine .mark-part { opacity: 0; }

.cine .shot-sadhya { opacity: 0; }
.cine .cue { opacity: 0; }

/* ── mobile ──────────────────────────────────────────────────────── */

@media (max-width: 1040px) {
  .bill { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* The fourth tile used to be pinned to column 1 here. At three columns that
     is where auto-placement already puts it, so the rule did nothing — but it
     survived into the two-column phone layout below, where it pushed tiles 4
     and 5 onto rows of their own and left the right-hand column empty down the
     page. That is the ragged grid the owner saw. Let it place itself. */
  .bill li:nth-child(4) { grid-column: auto; }
  .glance { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .book-in { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  .book-p, .fact-sub { max-width: none; }
}

@media (max-width: 640px) {
  body { font-size: 16px; }
  .layer { padding: 104px var(--gut) 84px; }
  /* the phone's hero clip is already cropped to the lanterns in ffmpeg */
  .shot-lanterns .shot-v { object-position: 50% 50%; }
  .shot-palms .shot-v  { object-position: 50% 62%; }
  .shot-sadhya .shot-v { object-position: 56% 50%; }
  .shot-crowd .shot-v  { object-position: 60% 50%; }
  .shot-lamp .shot-v   { object-position: 50% 58%; }
  /* 3vw + 94vw = 100vw exactly, so the mark gets the width the owner asked for
     and the act still centres on the screen rather than on a box wider than it. */
  #act-logo .layer, .layer-spon { padding-inline: 3vw; }
  /* The title card ran 22px past the bottom of a 360x780 screen: "presents"
     sat under the dock and the Scroll cue was behind it completely. The bar is
     off at scroll 0, so the top padding was paying rent for nothing — spend it
     at the bottom instead, where the dock and the cue live. */
  #act-arrive .layer { padding-top: 48px; padding-bottom: 150px; }
  .cue { bottom: calc(80px + env(safe-area-inset-bottom, 0px)); }
  .cue-rule { height: 30px; margin-bottom: 10px; }
  .mark-box { width: 94vw; }
  .eyebrow { font-size: 10px; letter-spacing: .3em; text-indent: .3em; }
  .eyebrow i { width: 18px; }
  /* Marks free-wrapping at 390 fell 2 / 3 / 1 and left one orphaned on a row
     of its own, which reads as a mistake. Explicit grids instead: Reliance
     alone, Indo Bazaar on its own line over the other three, event six 3 + 3. */
  .spon-row { display: grid; gap: 22px 14px; max-width: 340px;
              margin-inline: auto; justify-items: center; align-items: center; }
  .spon-row li { justify-content: center; }
  /* The lead pair is the one row that must NOT become a grid: it stays two
     across at every width. */
  .spon-lead { --u: .70px; grid-template-columns: 1fr; }
  .spon-mid  { --u: .56px; grid-template-columns: 1fr; }
  .spon-rest { --u: .46px; grid-template-columns: repeat(3, minmax(0, 1fr));
               gap: 16px 9px; max-width: 340px; }
  /* At the size they used to be the boat club's two had to stack, and that
     cost the act 175px of pinned height — the single biggest reason the
     "Event sponsored by" group had nowhere to be. Smaller, they fit side by
     side on a 360, which is also how they read as a pair. */
  .spon-boat-row { --u: .88px; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
  /* Two across for the same reason as the row under the logo. */
  .ev-row-lg { --u: .62px; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 10px; }
  /* The group is a wall of marks and the act that holds it is pinned, so every
     pixel of height here is a pixel cine.js has to tilt past. */
  /* The bottom padding is the dock's seat: the tilt ends with the last row of
     marks resting above the fixed Book tickets bar, not underneath it. */
  .layer-spon { padding-top: 76px; padding-bottom: calc(112px + env(safe-area-inset-bottom, 0px)); }
  .layer-spon .spon-group + .spon-group { margin-top: 26px; }
  .bar { padding: 10px 16px; }
  .bar-club { font-size: 11px; letter-spacing: .14em; }
  .bar-cta { font-size: 12px; padding: 9px 13px; }
  .strap { letter-spacing: .24em; text-indent: .24em; }
  .qty { grid-template-columns: 54px 1fr 54px; }
  .pay { letter-spacing: .12em; padding: 17px 18px; }
  .cine .act-arrive { height: 200svh; }
  .cine .act-info   { height: 430svh; }
  .foot { padding: 44px var(--gut) 48px; }
  .book-dock { display: flex; }
  .act-book { padding-bottom: calc(86px + env(safe-area-inset-bottom, 0px)); }
  /* Five tiles, two columns: two full rows and the last one across the foot,
     so nothing is orphaned beside a hole. */
  .bill { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 14px; }
  .bill li:last-child { grid-column: 1 / -1; }
  .bill img { height: 124px; }
  .bill li:last-child img { height: 150px; }
  /* The tile titles are Bodoni's display cut too, and at 17px on a phone they
     read as grey scratches. Same fix as the money. */
  .bill h3 { font-size: 16px; font-variation-settings: 'opsz' 16; font-weight: 600; }
  /* was 14px, which is the one place the phone undercut the 15px floor */
  .bill p { font-size: 15px; }
  .comp { gap: 18px; }
  .register { justify-content: flex-start; text-align: left; }
  .glance { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .glance > div:nth-child(n+3) { grid-column: 1 / -1; }
  .cta { width: 100%; justify-content: space-between; }
  .tier { padding: 11px 12px; }
  .step { grid-template-columns: 46px 40px 46px; }
}

/* The pay button used to stack its label over its price under 380px, which
   left the total small and tucked into the left corner — the one figure a
   buyer checks before paying. It fits on one line at 360 with the tracking
   eased off, so it stays a row and the total stays on the right. */
@media (max-width: 380px) {
  .pay { padding: 18px 16px; letter-spacing: .08em; gap: 10px; }
}

@media (max-width: 400px) {
  /* The stepper and the tier's words share one row, and at 360 the longest
     fee note ("no platform fees") needs 113px of the 108 the stepper left it.
     The buttons give back 8px and stay exactly on the 44px floor. */
  .step { grid-template-columns: 44px 36px 44px; }
  .step button { min-width: 44px; }
}

/* ── when the device cannot keep up ──────────────────────────────────
   cine.js watches real frame times while you scroll and sets .lite the first
   time a stretch of the page drops a quarter of its frames. Measured on this
   page at 4x CPU, in order of what costs most:

     · the blur() tweens — the fact changeovers and the sponsor reveal each
       animate an 8px blur across a full-screen element, which re-rasterises
       every frame. These are inline styles GSAP writes, so !important is what
       takes them back.
     · the grain — a fixed, full-screen mix-blend-mode: overlay layer composited
       over everything on every frame, for a texture a phone cannot resolve.
     · backdrop-filter on the sheet — it reads the whole backdrop to blur it,
       and the sheet is already 93–96% opaque, so almost nothing shows through.
     · a filter on a video that is simultaneously being scaled.

   Nothing here is load-bearing: the acts still play, the lights still run,
   the page just stops paying for texture it cannot show. */
.lite .grain { display: none; }
.lite .sheet { backdrop-filter: none; }
.lite .bar { backdrop-filter: none; background: rgba(10,4,9,.92); }
.lite .shot-v { filter: none; }
.lite .fact,
.lite .presents,
.lite .spon-row li,
.lite .layer-spon [data-beat] { filter: none !important; }
.lite .layer::before { display: none; }

/* The grain is a 128px tile at 5% through an overlay blend: on a phone it is
   below the threshold of what the screen can show, and it is the one layer
   that composites over the entire page on every single frame. */
@media (max-width: 640px) {
  .grain { display: none; }
  .sheet { backdrop-filter: none; }
}

/* ── reduced motion: the lights are on, and they stay on ─────────── */

@media (prefers-reduced-motion: reduce) {
  .bar { position: sticky; opacity: 1; transform: none; pointer-events: auto; background: rgba(10,4,9,.92); }
  .cue { display: none; }
  .act + .act .stage { border-top: 1px solid rgba(138,102,50,.2); }
}
