  /* Brand palette — plum ground, magenta sky, pink/purple/orange furniture.
     Literal hexes below are the same tokens: SVG presentation attributes and
     colour-stop lists can't read CSS custom properties. */
  :root {
    --ink: #150610;
    --sky1: #2A0518;
    --sky2: #8E1050;
    --tarmac: #4C0A2C;
    --belt: #DCDFE9;
    --caution: #F0972B;
    --brand: #EC2A7C;
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body {
    height: 100%;
    overflow: hidden;
    touch-action: none;
    overscroll-behavior: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
    background: var(--sky1);
  }
  /* ponytail: the game fills the viewport up to 720px wide (874pt tall) —
     centred in a letterbox beyond that, shrunk to fit on a smaller phone.
     Everything else measures off element rects, so nothing else changes. */
  /* letterbox continues whatever's on screen so wide embeds blend instead of
     showing bars: the overlay gradient on menu screens, the sky in play */
  body { display: flex; align-items: center; justify-content: center; background: linear-gradient(var(--sky1), var(--sky2) 70%); }
  body:has(.overlay:not(.hidden)) { background: linear-gradient(#540525, #FB1671); }
  /* feather the screen edge into the letterbox — a blur strip straddles each
     seam, masked so it fades to nothing toward the middle of the game */
  @media (min-width: 749px) {   /* only when a letterbox actually shows */
    body::before, body::after {
      content: '';
      position: fixed;
      top: 0;
      height: 100%;
      width: 28px;
      z-index: 90;
      pointer-events: none;
      backdrop-filter: blur(5px);
      -webkit-backdrop-filter: blur(5px);
    }
    body::before {
      left: calc(50% - 374px);
      mask-image: linear-gradient(to right, #000, transparent);
      -webkit-mask-image: linear-gradient(to right, #000, transparent);
    }
    body::after {
      right: calc(50% - 374px);
      mask-image: linear-gradient(to left, #000, transparent);
      -webkit-mask-image: linear-gradient(to left, #000, transparent);
    }
    /* overlay screens (start, game over, ad, pause) have bright cards at the
       edges — blurring them reads as random glows, so feather gameplay only */
    body:has(.overlay:not(.hidden))::before,
    body:has(.overlay:not(.hidden))::after { display: none; }
    /* paint the game's and overlays' gradients against the viewport so they line
       up with the body's letterbox gradient — no seam at the game edge. Desktop
       only: iOS mangles fixed attachment, and there's no letterbox there anyway */
    #game, .overlay { background-attachment: fixed; }
  }
  #game {
    width: min(100vw, 720px);
    height: min(100dvh, 874px);
    overflow: hidden;   /* the frame is the screen: nothing spills into the letterbox */
    display: flex;
    flex-direction: column;
    background: linear-gradient(var(--sky1), var(--sky2) 70%);
    font-family: "Arial Rounded MT Bold", ui-rounded, system-ui, -apple-system, sans-serif;
    color: var(--ink);
    position: relative;
  }

  /* ---------- HUD ---------- */
  #hud {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 10px;
    padding: calc(env(safe-area-inset-top) + 8px) 12px 6px;
    z-index: 60;
  }
  #hudleft { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
  #hearts { display: flex; gap: 4px; }
  .heart {
    color: #FF3E86;
    width: 38px;
    height: 38px;
    transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), color 0.35s;
  }
  .heart.lost { color: #6B2748; transform: scale(0.82) rotate(-10deg); }
  .heart svg { display: block; width: 100%; height: 100%; }
  #hearts.flash { animation: blink 0.22s linear 4; }
  /* Power-up meter: docked under the HUD. It lived bottom-centre, but the bottom
     edge of a phone is dead space — thumbs cover it and Safari's toolbar crowds
     it — and playtests kept missing the bar there. Up here it joins the status
     cluster (hearts / meter / score). It's a flow row, not absolute: #field
     shrinks to fit, so nothing overlaps and no strip needs reserving.
     --fill (0 → 1) scales the inner bar left-to-right. Centred with auto margins,
     never translateX: the ready/urgent pulses own `transform`. */
  #powerbtn {
    position: relative;
    z-index: 60;
    flex: none;
    width: 240px;
    height: 52px;
    margin: 2px auto 8px;
    padding: 0;
    border: 2.5px solid var(--ink);
    border-radius: 999px;
    background: #fff;
    overflow: hidden;
    box-shadow: 0 5px 0 var(--ink);
    color: var(--ink);
    display: flex;
    align-items: center;
    justify-content: center;
    touch-action: manipulation;
  }
  /* charging: full colour, gradient fill grows with the meter — only taps
     fall through to the bags riding the belt's exit lane behind it */
  #powerbtn:not(.ready) { pointer-events: none; }
  /* armed: the bar becomes the button, so it says so and takes a pointer */
  #powerbtn.ready { cursor: pointer; }
  #powerbtn i {
    position: absolute;
    inset: 0;
    transform: scaleX(var(--fill, 0));
    transform-origin: left;
    background: linear-gradient(90deg, #540525, #FB1671);
    transition: transform 0.25s ease-out;
  }
  /* the label rides on top of the fill — it names the power-up while charging,
     then flips to the call to action when armed (see armPower) */
  #powerbtn b {
    position: relative;
    font-family: "Archivo Black", sans-serif;
    font-size: 14px;
    letter-spacing: 0.05em;
    white-space: nowrap;
  }
  /* state 3: armed — white label on the full fill, heartbeat pulse */
  #powerbtn.ready b { color: #fff; }
  #powerbtn.ready { animation: powerready 0.9s ease-in-out infinite; }
  @keyframes powerready {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.04); }
  }
  /* Drowning with a full meter: same pulse, twice the tempo and a red ring. */
  #powerbtn.ready.urgent { animation: powerurgent 0.45s ease-in-out infinite; }
  @keyframes powerurgent {
    0%, 100% { transform: scale(1); box-shadow: 0 5px 0 var(--ink), 0 0 0 4px rgba(251, 22, 113, 0.5); }
    50% { transform: scale(1.16); box-shadow: 0 5px 0 var(--ink), 0 0 0 9px rgba(251, 22, 113, 0.9); }
  }

  /* Full meter announcement: the ⚡ pops centre-stage, holds a beat, then
     dives into the button — the eye follows it to the thing to tap. */
  .powercall {
    position: absolute;
    left: 50%;
    top: 42%;
    z-index: 91;
    display: flex;
    flex-direction: column;
    align-items: center;
    pointer-events: none;
    animation: powercall 1.8s cubic-bezier(0.55, 0, 0.55, 1) both;
  }
  .powercall b {
    font-size: 76px;
    line-height: 1;
    text-shadow: 0 0 18px rgba(240, 151, 43, 0.9), 0 4px 0 var(--ink);
  }
  .powercall span {
    font-size: clamp(21px, 6.5vw, 30px);
    font-weight: 900;
    letter-spacing: 0.04em;
    color: #fff;
    white-space: nowrap;
    text-shadow: -3px 0 var(--ink), 3px 0 var(--ink), 0 -3px var(--ink), 0 3px var(--ink), 0 5px 0 var(--ink);
  }
  @keyframes powercall {
    0% { opacity: 0; transform: translate(-50%, -50%) scale(0.3) rotate(-5deg); }
    9% { opacity: 1; transform: translate(-50%, -50%) scale(1.2) rotate(2deg); }
    16% { transform: translate(-50%, -50%) scale(1) rotate(0deg); }
    62% { opacity: 1; transform: translate(-50%, -50%) scale(1.05); }
    100% { opacity: 0; transform: translate(calc(-50% + var(--tx)), calc(-50% + var(--ty))) scale(0.15); }
  }

  /* Flash bang: fires when the meter is spent, then gets out of the way. */
  /* Full-bleed FX (#flashbang, #damage, the frenzy vignette) sit on #game, not
     #field: they wash the whole phone screen — HUD row and button strip
     included — instead of just the play field. */
  #flashbang {
    position: absolute;
    inset: 0;
    z-index: 90;
    display: grid;
    place-items: center;
    pointer-events: none;
    opacity: 0;
  }
  #flashbang::before {
    content: "";
    position: absolute;
    inset: -20%;
    background: radial-gradient(circle, #fff 0%, #F0972B 35%, rgba(236, 42, 124, 0) 70%);
  }
  #flashbang span {
    position: relative;
    font-size: clamp(34px, 12vw, 64px);
    font-weight: 900;
    letter-spacing: 0.06em;
    color: #fff;
    -webkit-text-stroke: 3px var(--ink);
    paint-order: stroke fill;
    text-shadow: 0 6px 0 var(--ink);
  }
  #flashbang.go { animation: flashbang 0.75s ease-out both; }
  #flashbang.go span { animation: flashbangtext 0.75s cubic-bezier(0.2, 1.5, 0.4, 1) both; }
  @keyframes flashbang {
    0% { opacity: 0; }
    12% { opacity: 1; }
    100% { opacity: 0; }
  }
  @keyframes flashbangtext {
    0% { transform: scale(0.4) rotate(-6deg); }
    45% { transform: scale(1.1) rotate(2deg); }
    100% { transform: scale(1.35) rotate(0deg); }
  }
  @media (prefers-reduced-motion: reduce) {
    #powerbtn.ready { animation: none; box-shadow: 0 5px 0 var(--ink), 0 0 0 5px rgba(251, 22, 113, 0.85); }
    #powerbtn.ready.urgent { animation: none; box-shadow: 0 5px 0 var(--ink), 0 0 0 6px rgba(251, 22, 113, 0.95); }
    #flashbang.go { animation-duration: 0.4s; }
    #flashbang.go span { animation: none; }
  }

  #hudright { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
  /* Combo chip: the whole combo state in ~40px beside the score — ×tier plus a
     progress sliver to the next one. Replaces the old centre-screen slab. */
  #combo {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 3px 9px 4px;
    border: 2.5px solid var(--ink);
    border-radius: 999px;
    background: #fff;
    box-shadow: 0 2px 0 var(--ink);
    opacity: 0;
    transform: scale(0.5);
    transition: opacity 0.18s, transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
    pointer-events: none;
  }
  #combo.on { opacity: 1; transform: scale(1); }
  #combo.max { background: var(--caution); }
  #combo b {
    font-size: 17px;
    font-weight: 900;
    line-height: 1;
    text-align: center;
    min-width: 26px;
    font-variant-numeric: tabular-nums;
  }
  #combo i { display: block; height: 3px; border-radius: 999px; background: rgba(21, 6, 16, 0.16); }
  #combo i::after {
    content: "";
    display: block;
    height: 100%;
    border-radius: 999px;
    background: linear-gradient(90deg, #F0972B, #EC2A7C);
    transform: scaleX(var(--p, 0));
    transform-origin: left;
    transition: transform 0.2s ease-out;
  }
  #combo.max i::after { background: var(--ink); }
  #combo.up { animation: combopop 0.45s cubic-bezier(0.3, 1.6, 0.5, 1); }
  .chip {
    background: #fff;
    border: 3px solid var(--ink);
    border-radius: 999px;
    padding: 3px 14px;
    font-weight: 800;
    font-size: 18px;
    min-width: 68px;
    text-align: center;
    box-shadow: 0 3px 0 var(--ink);
  }
  #score.pulse { animation: chipbump 0.4s cubic-bezier(0.3, 1.6, 0.5, 1); }
  /* score wears the same chunky white-pill chrome as the rest of the HUD —
     bare outlined digits read as floating debris on the plum sky */
  #score {
    font-size: 24px;
    font-weight: 900;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
    min-width: 88px;
    text-align: center;
    line-height: 1;
    background: #fff;
    border: 3px solid var(--ink);
    border-radius: 999px;
    padding: 6px 16px 8px;
    box-shadow: 0 3px 0 var(--ink);
  }
  .btn:focus-visible { outline: 3px dashed var(--ink); outline-offset: 3px; }

  /* ---------- admin / tuning panel ---------- */
  #admin {
    position: absolute;
    top: 0; right: 0; bottom: 0;
    width: min(340px, 94vw);
    background: #fff;
    border-left: 4px solid var(--ink);
    box-shadow: -6px 0 0 rgba(21, 6, 16, 0.15);
    z-index: 115;
    display: flex;
    flex-direction: column;
    touch-action: pan-y;
  }
  #admin.hidden { display: none; }
  #adminhead {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: calc(env(safe-area-inset-top) + 10px) 14px 10px;
    border-bottom: 3px solid var(--ink);
    font-size: 16px;
    font-weight: 900;
  }
  .adm-x {
    background: #fff;
    border: 2.5px solid var(--ink);
    border-radius: 9px;
    width: 30px; height: 30px;
    font-weight: 900;
    color: var(--ink);
    cursor: pointer;
    font-family: inherit;
  }
  #adminbody { flex: 1; overflow-y: auto; padding: 4px 14px 16px; overscroll-behavior: contain; }
  .adm-h {
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    opacity: 0.55;
    margin: 14px 0 6px;
  }
  .adm-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 1px 10px;
    align-items: center;
    margin-bottom: 9px;
    font-size: 13px;
    font-weight: 700;
    color: var(--ink);
  }
  .adm-row b { font-size: 13px; font-variant-numeric: tabular-nums; }
  .adm-row input { grid-column: 1 / -1; width: 100%; accent-color: var(--caution); height: 20px; margin: 0; }
  #adminfoot {
    border-top: 3px solid var(--ink);
    padding: 10px 14px calc(env(safe-area-inset-bottom) + 10px);
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
  }
  .adm-btn { font-size: 14px; padding: 8px 16px; box-shadow: 0 3px 0 var(--ink); border-width: 3px; }
  .adm-mini {
    background: #fff;
    border: 2.5px solid var(--ink);
    border-radius: 999px;
    padding: 6px 12px;
    font-weight: 800;
    font-size: 12px;
    color: var(--ink);
    cursor: pointer;
    font-family: inherit;
  }
  .adm-note { font-size: 10.5px; opacity: 0.6; font-weight: 700; width: 100%; }

  /* ---------- field ---------- */
  /* touch-action on body is ignored by iOS, so mashing TAP! TAP! TAP! read as a
     double-tap and zoomed the page. Declare it on the tap surface itself.
     Not on #game: that would also kill the admin panel's pan-y scrolling. */
  #field { flex: 1; position: relative; overflow: hidden; z-index: 0; touch-action: none; }
  .cloud {
    position: absolute;
    background: #fff;
    border-radius: 999px;
    opacity: 0.07;   /* haze, not cotton — white clouds would punch holes in the plum sky */
    pointer-events: none;
  }
  .cloud::before, .cloud::after {
    content: "";
    position: absolute;
    background: #fff;
    border-radius: 50%;
  }
  .cloud.c1 { width: 90px; height: 26px; top: 14%; left: -100px; animation: drift 52s linear infinite; }
  .cloud.c1::before { width: 38px; height: 38px; top: -18px; left: 16px; }
  .cloud.c1::after { width: 26px; height: 26px; top: -12px; left: 48px; }
  .cloud.c2 { width: 64px; height: 20px; top: 42%; left: -80px; animation: drift 75s linear infinite 18s; }
  .cloud.c2::before { width: 28px; height: 28px; top: -13px; left: 12px; }
  .cloud.c2::after { width: 20px; height: 20px; top: -8px; left: 36px; }
  .cloud.c3 { width: 46px; height: 15px; top: 6%; left: -60px; opacity: 0.05; animation: drift 95s linear infinite 40s; }
  .cloud.c3::before { width: 20px; height: 20px; top: -9px; left: 9px; }
  .cloud.c3::after { width: 14px; height: 14px; top: -6px; left: 26px; }
  /* ---------- sponsor plane towing a banner across the sky ---------- */
  .plane {
    /* on #game, not #field: flies the open sky band between the HUD row and
       the belt's top run, clear of the pills. */
    position: absolute;
    top: calc(env(safe-area-inset-top) + 70px);
    left: 0;
    z-index: 55;
    pointer-events: none;
    display: flex;
    align-items: center;
    width: 100%;
    transform: translateX(-100%);
    animation: flyby 46s linear infinite 5s;
  }
  /* no fly-bys during the tutorial — the sky waits until school's out
     (display flip restarts the animation, so the first pass starts fresh) */
  #game.school .plane { display: none; }
  /* 0.95x over the SVGs' native attribute sizes */
  .plane > svg { flex: none; width: 65px; height: 29px; display: block; animation: planebob 2.6s ease-in-out infinite alternate; }
  .banner { flex: none; margin-right: -10px; }
  .banner svg {
    height: 40px;
    width: auto; /* keeps the text-fitted aspect ratio from the width attribute */
    animation: bannerwave 1.9s ease-in-out infinite alternate;
    transform-origin: right center;
  }
  /* always left-to-right; -100% is the towed banner's own width, so any banner
     length still starts fully off-screen instead of poking in at the edge */
  @keyframes flyby {
    from { transform: translateX(-100%); }
    to { transform: translateX(100%); }
  }
  @keyframes bannerwave {
    from { transform: rotate(0.8deg) translateY(-1px); }
    to { transform: rotate(-1.2deg) translateY(1.5px); }
  }
  @keyframes planebob {
    from { transform: translateY(0) rotate(1.5deg); }
    to { transform: translateY(-7px) rotate(-2deg); }
  }

  /* ---------- belt track (SVG path drawn by JS) ---------- */
  #belts {
    position: absolute;
    inset: 0;
    z-index: 5; /* behind the bags riding it */
    pointer-events: none;
  }
  #belts svg { display: block; }
  #belts .rollers { stroke: #A9AFC2; animation: rollermove 0.9s linear infinite; }

  /* ---------- items ---------- */
  .item { position: absolute; left: 0; top: 0; z-index: 10; will-change: transform; cursor: pointer; transition: color 0.2s; }
  .item.boss { z-index: 11; }   /* never buried under later-spawned bags */
  /* the picked bag — the one a gate tap sends — wears full colour and a halo */
  .item.sel { z-index: 12; }
  .item.dragging { z-index: 40; }   /* a carried bag rides over the gates it's aimed at */
  .item.sel .itemin { filter: drop-shadow(0 0 4px #fff) drop-shadow(0 0 9px rgba(240, 151, 43, 0.95)); }
  .item.sel svg { animation: frontpulse 0.9s ease-in-out infinite; }
  /* the VIP bag carries the sponsor's badge — the logo never pulses or flashes */
  .item.boss.sel svg { animation: none; }
  .item svg { display: block; }
  /* bags wear their destination gate's colour from spawn — colour matches tag,
     so players can sort by hue at a glance; the picked bag is marked by its halo */
  /* overloaded bags flash a red warning glow on top of their signal-red shell */
  .item.banned .itemin { animation: bannedpulse 1s ease-in-out infinite; pointer-events: none; }
  /* ...and only the painted case takes the tap: its box is the biggest on the belt and was
     swallowing taps aimed at bags behind it, lid gap included */
  .item.banned svg * { pointer-events: auto; }
  @keyframes bannedpulse {
    0%, 100% { filter: drop-shadow(0 0 3px rgba(229, 72, 77, 0.6)); }
    50% { filter: drop-shadow(0 0 5px #fff) drop-shadow(0 0 15px #E5484D); }
  }
  /* the boss never hides in cream — a steady gold aura, nothing flashing */
  .item.boss:not(.sel):not(.mashing) .itemin {
    filter: drop-shadow(0 0 4px #FFE14D) drop-shadow(0 0 16px #FFB020);
  }
  /* the glow never said what it was: the boss looms larger than the crowd */
  .item.boss:not(.mashing) .itemin { transform: scale(1.22); }
  /* boss livery lives in the sprite itself now — the Tanssu case (sprites.js) */
  /* boss under mash fire: centre stage, above everything, furious glow */
  .item.mashing { z-index: 60; }
  .item.mashing .itemin { filter: drop-shadow(0 0 12px #F0972B) drop-shadow(0 0 26px rgba(236, 42, 124, 0.85)); }
  /* under fire the bag never sits still: constant shiver on the svg,
     per-tap squash on the wrapper — separate elements so they compose */
  .item.mashing svg { animation: mashshiver 0.28s ease-in-out infinite; }
  /* squash anchors at the wheel line, per the tap-tap spec bag —
     spec: proposals.clickfrenzy.com.au/bag-rush-start-spec-847291 */
  .item.mashing.smack .itemin { animation: bagsmack 0.22s cubic-bezier(0.34, 1.56, 0.64, 1); transform-origin: 50% 88%; }
  /* the spec's stage dressing, injected on the bag at frenzy start (game.js) */
  .tpfx { position: absolute; inset: 0; pointer-events: none; }
  .tpfx .tpglow {
    position: absolute;
    left: 50%;
    top: 50%;
    z-index: -1;
    width: 200px;
    height: 200px;
    margin: -100px 0 0 -100px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(251, 22, 113, 0.75), rgba(245, 182, 63, 0.25) 55%, transparent 72%);
    animation: tpglow 0.85s ease-in-out infinite alternate;
  }
  .tpfx .tpspark {
    position: absolute;
    left: 50%;
    top: 50%;
    font-family: "Archivo Black", sans-serif;
    font-size: 17px;
    color: #F5B63F;
    opacity: 0;
    animation: tpspark 1s ease-out infinite;
  }
  .tpfx .tppts {
    position: absolute;
    font-family: "Archivo Black", sans-serif;
    font-size: 15px;
    color: #fff;
    text-shadow: 0 2px 0 var(--ink);
    opacity: 0;
    animation: tppts 1.4s ease-out infinite;
  }
  .tpfx .tptap {
    position: absolute;
    left: 50%;
    top: 62%;
    width: 26px;
    height: 26px;
    margin: -13px 0 0 -13px;
    border: 2.5px solid var(--ink);
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.85);
    animation: tptap 0.55s ease-in-out infinite;
  }
  #field.shake { animation: fieldshake 0.12s; }
  #field.hit { animation: fieldhit 0.4s ease-out; }
  .heart.gone { animation: heartgone 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); }
  #game.frenzy::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 55;
    pointer-events: none;
    background: radial-gradient(circle at 50% 40%, transparent 42%, rgba(236, 42, 124, 0.35));
    animation: frenzypulse 0.4s ease-in-out infinite alternate;
  }
  #frenzyhud {
    position: absolute;
    top: 6px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 62;
    text-align: center;
    pointer-events: none;
  }
  #frenzyhud.hidden { display: none; }
  #fhits {
    display: block;
    font-family: "Archivo Black", sans-serif;
    font-size: 22px;
    color: #fff;
    white-space: nowrap;
    text-shadow: -3px 0 var(--ink), 3px 0 var(--ink), 0 -3px var(--ink), 0 3px var(--ink), -2px -2px var(--ink), 2px -2px var(--ink), -2px 2px var(--ink), 2px 2px var(--ink), 0 5px 0 var(--ink);
  }
  #fhits.pulse { animation: chipbump 0.18s cubic-bezier(0.3, 1.6, 0.5, 1); }
  #fbar {
    width: 210px;
    height: 13px;
    margin: 6px auto 0;
    border: 2.5px solid var(--ink);
    border-radius: 999px;
    background: #fff;
    overflow: hidden;
  }
  #fbar i {
    display: block;
    height: 100%;
    width: 100%;
    background: linear-gradient(90deg, #540525, #FB1671);
  }
  .itemin, .gatebody { width: 100%; height: 100%; }
  .itemin { position: relative; transition: transform 0.16s cubic-bezier(0.34, 1.56, 0.64, 1); }
  .itemin.pop { animation: pop 0.45s ease-out forwards; }
  .itemin.poof { animation: poof 0.5s ease-in forwards; }
  .itemin.wiggle { animation: wigglex 0.35s ease-in-out; }
  .qmark { display: none; }
  .item.magic:not(.locked) .qmark { display: block; }
  .brand { display: none; }         /* sponsor slot idle — the boss wears the Tanssu case instead */
  .item.magic:not(.locked) .itemin::after {
    content: "\2726";
    position: absolute;
    top: -8px;
    right: 1px;
    font-size: 15px;
    font-weight: 900;
    line-height: 1;
    color: #fff;
    text-shadow: -1.5px 0 var(--ink), 1.5px 0 var(--ink), 0 -1.5px var(--ink), 0 1.5px var(--ink);
    animation: twinkle 0.85s ease-in-out infinite;
    pointer-events: none;
  }
  .spark {
    position: absolute;
    z-index: 44;
    display: block;
    width: 9px;
    height: 9px;
    border: 2px solid var(--ink);
    border-radius: 3px;
    pointer-events: none;
    transform: translate(-50%, -50%);
    will-change: transform, opacity;
    animation: sparkfly 0.62s cubic-bezier(0.15, 0.85, 0.4, 1) forwards;
  }

  /* ---------- gate layer (two columns flanking the belt; JS places each gate) ---------- */
  #binbar {
    position: absolute;
    inset: 0;
    z-index: 30;
    pointer-events: none;
  }
  #binbar::before {
    /* tarmac ground strip the belt runs down into */
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: calc(34px + env(safe-area-inset-bottom));
    background: linear-gradient(180deg, #61103A 0, var(--tarmac) 24%, #33061D 100%);
    border-top: 4px solid var(--ink);
  }
  #field::before {
    /* distant terminal skyline sitting on the tarmac horizon — on #field, not
       #binbar, so it can sit behind the belt instead of over it */
    content: "";
    position: absolute;
    z-index: 4; /* under the belt (5) */
    bottom: calc(38px + env(safe-area-inset-bottom));
    left: 0; right: 0;
    height: 38px;
    background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 44" width="360" height="44"><g fill="%23400725"><rect x="8" y="27" width="70" height="17" rx="2"/><rect x="20" y="20" width="10" height="9"/><rect x="44" y="20" width="10" height="9"/><rect x="96" y="12" width="7" height="32"/><path d="M89 13 h21 l-4 9 h-13 Z"/><rect x="87" y="5" width="25" height="10" rx="5"/><path d="M146 44 V30 q0 -7 9 -9 l5 23 Z"/><path d="M196 44 v-10 a17 17 0 0 1 34 0 v10 Z"/><rect x="250" y="30" width="28" height="14" rx="2"/><rect x="286" y="20" width="13" height="24"/><rect x="284" y="16" width="17" height="6" rx="3"/><rect x="312" y="31" width="42" height="13" rx="2"/></g></svg>') left bottom / auto 100% repeat-x;
    opacity: 0.6;
    pointer-events: none;
  }
  .gate {
    position: absolute;
    left: 0; top: 0;
    transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1), width 0.3s, height 0.3s;
    will-change: transform;
    pointer-events: auto; /* gates are the tap targets; the layer around them stays inert */
    cursor: pointer;
  }
  .gatebody.press { animation: gatepress 0.18s ease-out; }
  /* gates read as baggage trolleys: slot opening up top, wheels below */
  .gatebody {
    position: relative;
    border: 3.5px solid var(--ink);
    border-radius: 13px 13px 15px 15px;
    box-shadow:
      inset 0 4px 0 rgba(255, 255, 255, 0.28),
      inset 0 -9px 0 rgba(0, 0, 0, 0.16),
      0 4px 0 rgba(21, 6, 16, 0.25);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 13px 6px 8px;
  }
  .slot {
    position: absolute;
    top: 3px; left: 12%; right: 12%;
    height: 8px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.3);
    box-shadow: 0 1.5px 0 rgba(255, 255, 255, 0.25);
  }
  .wheels { position: absolute; left: 0; right: 0; bottom: -7px; height: 0; }
  .wheels::before, .wheels::after {
    content: "";
    position: absolute;
    bottom: 0;
    width: 15px;
    height: 15px;
    box-sizing: border-box;
    border: 3px solid var(--ink);
    border-radius: 50%;
    background: radial-gradient(circle at 50% 42%, #C9D2E2 2.6px, #55617E 3px);
  }
  .wheels::before { left: 13%; }
  .wheels::after { right: 13%; }
  .gatebody.bounce { animation: bounce 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); }
  .gatebody.shake { animation: shake 0.4s ease-in-out; }
  .gate.wobble .gatebody { animation: wobble 0.3s ease-in-out infinite; }
  .plate {
    background: #fff;
    border: 2.5px solid var(--ink);
    border-radius: 9px;
    padding: 2px 9px 3px;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    max-width: 100%;
    box-shadow: 0 2px 0 rgba(21, 6, 16, 0.18);
    display: flex;
    flex-direction: column;
    align-items: center;
    line-height: 1.05;
  }
  .plate b { font-size: 14.5px; font-weight: 900; letter-spacing: 0.08em; }
  .plate i {
    font-style: normal;
    font-weight: 800;
    font-size: 7.5px;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    opacity: 0.6;
  }

  /* ---------- feedback ---------- */
  #toasts {
    position: absolute;
    top: 44px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    z-index: 50;
    pointer-events: none;
    width: max-content;
    max-width: 92%;
  }
  .toast {
    background: #fff;
    border: 3px solid var(--ink);
    border-radius: 999px;
    padding: 6px 18px;
    font-weight: 800;
    font-size: 14px;
    color: var(--ink);
    box-shadow: 0 3px 0 var(--ink);
    animation: toastin 2.4s both;
    text-align: center;
  }
  .toast.alert {
    background: #FFE3E3;
    color: #B3261E;
    border-color: #B3261E;
    box-shadow: 0 3px 0 #B3261E;
    animation: toastalertin 2.4s both;
  }
  /* Life lost: the edges of the screen bleed. Centre stays clear so the belt is
     still readable, but nothing about it is missable. */
  #damage {
    position: absolute;
    inset: 0;
    z-index: 88;
    pointer-events: none;
    opacity: 0;
    background: radial-gradient(ellipse at 50% 50%, rgba(229, 72, 77, 0) 34%, rgba(229, 72, 77, 0.9) 100%);
    box-shadow: inset 0 0 0 7px #E5484D;
  }
  #damage.go { animation: damagehit 0.6s ease-out both; }
  /* the heart is torn out where the player is looking and thrown into the HUD row */
  .lostheart {
    position: absolute;
    z-index: 92;
    width: 64px;
    height: 64px;
    margin: -32px 0 0 -32px;
    color: #FF3E86;
    pointer-events: none;
    animation: heartrip 0.85s cubic-bezier(0.4, 0, 0.55, 1) both;
  }
  .lostheart svg { display: block; width: 100%; height: 100%; }
  .reviveheart {
    position: absolute;
    z-index: 92;
    width: 72px;
    height: 72px;
    margin: -36px 0 0 -36px;
    color: #FF3E86;
    pointer-events: none;
    filter: drop-shadow(0 4px 0 rgba(21, 6, 16, 0.3));
    animation: heartback 1.25s cubic-bezier(0.5, 0, 0.6, 1) both;
  }
  .reviveheart svg { display: block; width: 100%; height: 100%; }
  .floater {
    position: absolute;
    z-index: 45;
    font-weight: 900;
    font-size: 21px;
    color: #fff;
    text-shadow: -2px 0 var(--ink), 2px 0 var(--ink), 0 -2px var(--ink), 0 2px var(--ink), 0 3px 0 var(--ink);
    transform: translate(-50%, -50%);
    animation: floatup 0.9s ease-out forwards;
    pointer-events: none;
    white-space: nowrap;
  }
  .floater.bad { color: #FF6B6B; }

  /* first-run coach bubbles — one rides the first bag, one hangs under the power bar.
     Left-anchored on the bag: it spawns at the belt's left edge, so the bubble
     must extend rightward into the field instead of clipping off-screen. */
  .tuthint {
    position: absolute;
    bottom: 100%;
    left: -6px;
    margin-bottom: 8px;
    width: max-content;
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 999px;
    padding: 5px 12px;
    font-size: 13px;
    font-weight: 800;
    color: var(--ink);
    white-space: nowrap;
    pointer-events: none;
    box-shadow: 0 3px 0 rgba(21, 6, 16, 0.3);
    animation: ovin 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.35s both;
    z-index: 45;
  }
  .tuthint.flip { left: auto; right: -6px; }   /* bag on the right half: extend leftward instead */
  /* faded clone of the first bag gliding to its gate — the drag, demonstrated */
  .item.ghost {
    z-index: 39;   /* over the gates it glides onto, under a really-dragged bag */
    opacity: 0;
    pointer-events: none;
  }
  .ghosthand {
    position: absolute;
    right: -12px;
    bottom: -8px;
    font-size: 32px;
    transform: rotate(-15deg);
  }
  /* tap-tap lesson: just the hand, centred on whatever it's pressing */
  .taphand { width: 0; height: 0; }
  .taphand .ghosthand { right: auto; bottom: auto; left: -16px; top: -16px; }
  /* finale: the hand rides the armed ⚡ button (which pulses at z 60) */
  .powerhand {
    position: absolute;
    right: auto;
    bottom: auto;
    z-index: 62;
    font-size: 30px;
    pointer-events: none;
  }
  .ghosthand.tap { animation: handtap 0.45s ease-in-out 2; }
  @keyframes handtap {
    0%, 100% { transform: rotate(-15deg) scale(1); }
    50% { transform: rotate(-15deg) scale(0.7) translateY(4px); }
  }
  .powertut {
    bottom: auto;
    left: 0;
    right: 0;
    margin: 0 auto;
    z-index: 62;
  }
  /* School finale spotlight: darkness over everything — field, HUD, frozen
     bags — except a warm cone of light on the armed ⚡ (aimSpot() pins
     --spotx/--spoty to the button, wherever layout docked it). The veil eats
     every tap that isn't the button, so the field is locked without touching
     its handlers; the light breathes on the button's own pulse tempo. */
  #game.powerlock::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 61;
    background: radial-gradient(
      ellipse 340px 190px at var(--spotx, 50%) var(--spoty, 88%),
      rgba(240, 151, 43, 0.55),
      rgba(240, 151, 43, 0.18) 34%,
      rgba(21, 6, 16, 0.45) 55%,
      rgba(21, 6, 16, 0.8) 78%
    );
    /* fade the darkness in first, then breathe on the button's pulse tempo */
    animation: spotfade 0.5s ease-out both, spotbreathe 0.9s ease-in-out 0.5s infinite;
  }
  @keyframes spotfade { from { opacity: 0; } }
  @keyframes spotbreathe {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.88; }
  }
  #game.powerlock #powerbtn { z-index: 62; }
  /* The jammed bags are spotlit too — the field rides above the veil (its own
     stacking context at z 0 traps #items, so the whole field comes up), the
     scenery inside it dims via filter, and the frozen bags stay full-bright
     with a warm glow. pointer-events off: taps still fall through to the veil. */
  #game.powerlock #field { z-index: 62; pointer-events: none; }
  #game.powerlock #field > :not(#items) {
    filter: brightness(0.35) saturate(0.75);
    transition: filter 0.5s ease-out;
  }
  #game.powerlock .item .itemin { filter: drop-shadow(0 0 14px rgba(240, 151, 43, 0.7)); }
  /* lesson bags plop onto the belt one by one (delays inlined by spawnTut) —
     on .itemin, never .item: JS owns the outer transform for positioning */
  .item.jam .itemin { animation: jamdrop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) backwards; }
  @keyframes jamdrop { from { opacity: 0; transform: translateY(-22px) scale(0.75); } }
  @media (prefers-reduced-motion: reduce) {
    #game.powerlock::after { animation: none; }
    .item.jam .itemin { animation: none; }
  }

  /* ---------- overlays ---------- */
  .overlay {
    position: absolute;
    inset: 0;
    z-index: 100;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* auto-margins on the first/last children centre the stack when it fits and
       scroll it intact when it doesn't — justify-content: center would clip the top */
    justify-content: flex-start;
    overflow-y: auto;
    touch-action: pan-y;
    gap: 14px;
    /* fully opaque — dead game chrome (hearts, score 0, empty meter) must not ghost through */
    background: linear-gradient(#540525, #FB1671);
    text-align: center;
    padding: 24px;
  }
  .overlay > :first-child { margin-top: auto; }
  .overlay > :last-child { margin-bottom: auto; }
  .overlay.hidden { display: none; }
  .overlay > * { animation: ovin 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) both; }
  .overlay > *:nth-child(2) { animation-delay: 0.06s; }
  .overlay > *:nth-child(3) { animation-delay: 0.11s; }
  .overlay > *:nth-child(4) { animation-delay: 0.16s; }
  .overlay > *:nth-child(5) { animation-delay: 0.21s; }
  .overlay > *:nth-child(6) { animation-delay: 0.26s; }
  .overlay > *:nth-child(7) { animation-delay: 0.31s; }
  .overlay > *:nth-child(8) { animation-delay: 0.36s; }
  .hero {
    position: relative;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    margin-bottom: -4px;
    pointer-events: none;
  }
  .hero svg { display: block; }
  .zzz {
    position: absolute;
    top: -16px;
    right: -20px;
    display: flex;
    align-items: flex-end;
    gap: 2px;
  }
  .zzz span {
    font-family: "Archivo Black", sans-serif;
    font-weight: 900;
    color: #fff;
    opacity: 0;
    animation: zfloat 2.1s ease-in-out infinite;
  }
  .zzz span:nth-child(1) { font-size: 15px; }
  .zzz span:nth-child(2) { font-size: 12px; animation-delay: 0.35s; }
  .zzz span:nth-child(3) { font-size: 9px; animation-delay: 0.7s; }
  /* ---------- pause — 1:1 from the pause mock on the start-spec page ---------- */
  #pauseov {
    font-family: "DM Sans", -apple-system, sans-serif;
    padding: 34px 18px 30px;
  }
  #pauseov > :first-child { margin-top: 0; }   /* spacers centre the stack, like #start */
  #pauseov > :last-child { margin-bottom: 0; }
  #pauseov .overtitle { font-size: 40px; margin-top: 10px; }
  #pauseov .cta { margin-top: 10px; }
  #pauseov .byline { margin-top: 0; margin-bottom: 2px; }
  #pauseov .presented { margin-bottom: 6px; }
  /* ---------- start screen — 1:1 from the start-spec mock (402x874) ----------
     dims/type: proposals.clickfrenzy.com.au/bag-rush-start-spec-847291 */
  #start { padding: 34px 18px 30px; font-family: "DM Sans", -apple-system, sans-serif; }
  #start > :first-child { margin-top: 0; }   /* top-anchored; .spacer pushes the CTA down */
  #start > :last-child { margin-bottom: 0; }
  #start .brlogo { width: 218px; }
  .presented { display: flex; align-items: center; gap: 9px; }
  .presented span {
    font-size: 8.5px;
    font-weight: 800;
    letter-spacing: 1.19px;
    text-transform: uppercase;
    color: #FFD3E4;
    margin-right: 3px;
  }
  .presented i { width: 1px; height: 14px; background: rgba(255, 255, 255, 0.35); }
  .presented .t { display: block; width: 66px; height: auto; }
  .presented .s { display: block; width: 54px; height: auto; }
  .card {
    width: 100%;
    background: rgba(21, 6, 16, 0.5);
    border: 0.625px solid rgba(255, 255, 255, 0.16);
    border-radius: 18px;
    padding: 14px 16px;
    text-align: left;
    color: #fff;
  }
  .card.scores { margin-top: 12px; }
  .card.prize { margin-top: 4px; }
  .lab { font-size: 10px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase; }
  .lab.gold { display: flex; align-items: center; justify-content: space-between; gap: 8px; color: #F5B63F; }
  .lab.pink { color: #FF9EC5; }
  .atchip {
    background: #fff;
    color: var(--ink);
    border-radius: 999px;
    padding: 3px 11px;
    font-size: 10.5px;
    font-weight: 900;
    letter-spacing: 0.21px;
    text-transform: none;
  }
  .atchip.hidden, .skipcount.hidden { display: none; }
  #boardrows, #overrows { margin-top: 6px; font-size: 13px; line-height: 1.4; color: #F6E3ED; }
  .boardrow {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 5.5px 0;
    font-size: 16px;
    font-weight: 800;
  }
  .boardrow b { flex: none; min-width: 14px; font-weight: 900; color: #D98CB0; }
  .boardrow span {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: #fff;
    letter-spacing: 0.02em;
  }
  .boardrow em {
    font-style: normal;
    font-variant-numeric: tabular-nums;
    font-weight: 800;
    color: #fff;
  }
  .toprow { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
  .daychip {
    flex: none;
    background: rgba(255, 255, 255, 0.14);
    border: 0.625px solid rgba(255, 255, 255, 0.25);
    border-radius: 999px;
    padding: 3px 10px 4px;
    font-size: 9.5px;
    font-weight: 800;
    letter-spacing: 0.76px;
    text-transform: uppercase;
  }
  .prow { display: flex; align-items: center; gap: 14px; margin-top: 12px; }
  .ptile {
    flex: none;
    width: 92px;
    height: 92px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #fff;
    border-radius: 16px;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
    overflow: hidden;
  }
  .ptile img { display: block; width: 92px; height: 92px; object-fit: cover; }
  .pmeta { flex: 1; min-width: 0; }
  .pname { font-family: "Archivo Black", sans-serif; font-size: 18px; line-height: 1.08; }
  .pval { font-family: "Archivo Black", sans-serif; font-size: 12.5px; color: #FF9EC5; margin-top: 4px; }
  .pval:empty { display: none; }
  .prule { font-size: 12px; line-height: 1.4; color: #F0CFE0; margin-top: 6px; }
  .divide { border-top: 0.625px solid rgba(255, 255, 255, 0.14); margin: 12px 0 11px; }
  .coffee { display: flex; align-items: center; gap: 11px; }
  .ctile { flex: none; width: 40px; height: 40px; border-radius: 50%; overflow: hidden; }
  .ctile img { display: block; width: 40px; height: 40px; }
  .cmeta { flex: 1; min-width: 0; }
  .cname { font-size: 13.5px; font-weight: 900; color: #fff; }
  .crule { font-size: 11.5px; line-height: 1.35; color: #F0CFE0; margin-top: 2px; }
  .skipcount { flex: none; text-align: right; }
  .skipcount b { display: block; font-family: "Archivo Black", sans-serif; font-size: 16px; line-height: 1.05; color: #fff; }
  .skipcount span { font-size: 8.5px; font-weight: 800; letter-spacing: 0.85px; text-transform: uppercase; color: #F0CFE0; }
  .cta {
    display: block;
    background: #fff;
    color: #C5005F;
    border: 2.5px solid var(--ink);
    border-radius: 999px;
    padding: 15px 48px;
    font-family: "Archivo Black", sans-serif;
    font-size: 19px;
    letter-spacing: 0.76px;
    box-shadow: 0 5px 0 var(--ink);
    cursor: pointer;
  }
  .cta:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--ink); }
  /* ---------- coffee verdict — quiet info card by default, loud when they won ---------- */
  .coffeecard {
    width: min(320px, 88vw);
    background: rgba(21, 6, 16, 0.5);
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: 16px;
    padding: 10px 16px 11px;
    color: #fff;
  }
  .cc-row { display: flex; align-items: center; gap: 12px; text-align: left; }
  .cc-body { flex: 1; min-width: 0; }
  .cc-head { font-size: 14px; font-weight: 900; line-height: 1.3; }
  .cc-sub { margin-top: 4px; font-size: 12px; line-height: 1.45; color: #F0CFE0; }
  /* claimed and settled: the quiet green receipt */
  .coffeecard.is-done { border-color: rgba(110, 231, 135, 0.45); }
  .coffeecard.is-done .cc-head { color: #6EE787; }
  /* the win: same gold slab the old instant-win banner used */
  .coffeecard.is-won {
    background: var(--caution);
    border: 3px solid var(--ink);
    color: var(--ink);
    box-shadow: 0 3px 0 var(--ink);
    animation: ovin 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) both, btnbeat 1.7s ease-in-out 0.8s infinite;
  }
  .coffeecard.is-won .cc-head { font-size: 15px; }
  .coffeecard.is-won .cc-sub { color: rgba(21, 6, 16, 0.72); font-weight: 700; }
  /* unlocked but unspent: the same gold slab, sitting still — the beat belongs to
     the button, so the eye lands on the thing that has to be tapped */
  .coffeecard.is-claim {
    background: #fff;
    border: 3px solid var(--ink);
    color: var(--ink);
    box-shadow: 0 3px 0 var(--ink);
    padding: 12px 14px 13px;
    animation: ovin 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) both;
  }
  /* the skip script is a transparent cutout in the white roundel — on the white
     card it needs a backing colour to exist at all */
  .coffeecard.is-claim .ctile { border: 2px solid var(--ink); background: #E65742; }
  .coffeecard.is-claim .cc-head { font-size: 15px; }
  .coffeecard.is-claim .cc-sub { color: rgba(21, 6, 16, 0.72); font-weight: 700; }
  /* #filmbtn's language, card-width and thumb-sized */
  #cc-claim {
    display: block;
    width: 100%;
    min-height: 44px;
    margin-top: 10px;
    /* #filmbtn's pink darkened: white on #FB1671 is 3.85:1, and at 14.5px this
       label isn't "large" under WCAG, so AA wants 4.5:1. #E5005F reads 4.67:1
       and is the nearest pink that gets there — this is the one tap target on
       the screen that pays out $7, outdoors, on a phone. */
    background: #E5005F;
    color: #fff;
    border: 3px solid var(--ink);
    border-radius: 999px;
    padding: 12px 10px;
    font-family: "Archivo Black", sans-serif;
    font-size: 14.5px;
    letter-spacing: 0.04em;
    box-shadow: 0 4px 0 var(--ink);
    cursor: pointer;
    animation: btnbeat 1.7s ease-in-out 0.8s infinite;
  }
  #cc-claim:active { transform: translateY(2px); box-shadow: 0 2px 0 var(--ink); }
  #cc-claim:disabled { opacity: 0.6; animation: none; cursor: default; }   /* claim in flight — the label says CLAIMING… too */
  /* the UA ring disappears into a saturated pink field; .btn gets the same treatment */
  #cc-claim:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
  #coffeecard:focus { outline: none; }   /* focused only to land the screen reader on the verdict */
  #cc-claim.hidden { display: none; }
  .coffeecard.hidden { display: none; }

  .btn {
    background: var(--caution);
    border: 4px solid var(--ink);
    border-radius: 999px;
    padding: 13px 40px;
    font-size: 21px;
    font-weight: 900;
    letter-spacing: 0.05em;
    color: var(--ink);
    cursor: pointer;
    box-shadow: 0 5px 0 var(--ink);
    font-family: inherit;
    transition: transform 0.06s;
  }
  .btn:hover { filter: brightness(1.05); }
  .btn:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--ink); }
  /* ---------- extra-life film — spec: proposals.clickfrenzy.com.au/bag-rush-start-spec-847291 ---------- */
  #filmov {
    font-family: "DM Sans", -apple-system, sans-serif;
    padding: 34px 18px 30px;
  }
  #filmov > :first-child { margin-top: 4px; }   /* top-anchored; the film flexes to fill */
  #filmov > :last-child { margin-bottom: 0; }
  #filmov .presented .t { width: 92px; }
  .filmvidwrap {
    position: relative;
    width: 100%;
    flex: 1;
    min-height: 0;
    margin-top: 6px;
    border: 3px solid var(--ink);
    border-radius: 22px;
    background: var(--ink);
    overflow: hidden;
  }
  #filmvid { width: 100%; height: 100%; object-fit: cover; display: block; }
  #filmtimer {
    position: absolute;
    top: 12px;
    right: 12px;
    padding: 4px 12px;
    border-radius: 999px;
    background: rgba(21, 6, 16, 0.75);
    color: #fff;
    font-weight: 900;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
  }
  #filmtimer:empty { display: none; }
  #filmbar {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 4px;
    background: rgba(255, 255, 255, 0.25);
  }
  #filmbar i {
    display: block;
    height: 100%;
    width: 0;
    background: #FB1671;
    /* timeupdate only ticks ~4x/s — glide between ticks instead of stepping */
    transition: width 0.3s linear;
  }
  .filmheart {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 10px;
    font-weight: 800;
    font-size: 13.5px;
    color: #fff;
  }
  .filmheart span { color: var(--ink); font-size: 16px; }   /* spec's pink heart vanishes into the gradient */
  #filmshop {
    background: transparent;
    border: 2px solid rgba(255, 255, 255, 0.55);
    color: #fff;
    border-radius: 999px;
    padding: 9px 22px;
    font-family: inherit;
    font-weight: 800;
    font-size: 13px;
    cursor: pointer;
    margin-top: 2px;
  }
  #startbtn {
    background: #fff;
    color: var(--brand);
    padding: 10px 44px;
    font-size: 18px;
    line-height: 1;
    box-shadow: 0 4px 0 var(--ink);
    animation: ovin 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) 0.31s both,
      btnbeat 1.7s ease-in-out 1.4s infinite;
  }
  #start .divide { margin: 8px 0 7px; }
  #start .coffee { gap: 9px; }
  #start .ctile,
  #start .ctile img { width: 34px; height: 34px; }
  #start .cname { font-size: 13px; }
  #start .crule { margin-top: 1px; font-size: 10.75px; line-height: 1.25; }
  #start .skipcount b { font-size: 15px; }
  /* ---------- game over — spec: proposals.clickfrenzy.com.au/bag-rush-start-spec-847291 ---------- */
  #gameover {
    font-family: "DM Sans", -apple-system, sans-serif;
    padding: 34px 18px 30px;
  }
  /* top-anchored stack, spacer pushes the skip link to the bottom edge */
  #gameover > :first-child { margin-top: 0; }
  #gameover > :last-child { margin-bottom: 0; }
  .spacer { flex: 1 0 0; }
  .brlogo { display: block; width: 170px; height: auto; margin-top: 4px; }
  .byline {
    z-index: 2;   /* rides up over the logo art's built-in breathing room */
    margin-top: -11px;
    color: #FFD3E4;
    font-size: 8.5px;
    font-weight: 800;
    letter-spacing: 2.21px;
    text-transform: uppercase;
  }
  .overtitle { font-family: "Archivo Black", sans-serif; font-size: 31px; line-height: 1; margin-top: 2px; color: #fff; }
  .scorecard {
    width: min(320px, 88vw);
    background: rgba(21, 6, 16, 0.5);
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: 18px;
    padding: 13px 16px 12px;
    color: #fff;
  }
  .scorecard .lab { color: #FF9EC5; font-size: 10px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase; }
  .finalscore { font-family: "Archivo Black", sans-serif; font-size: 44px; line-height: 1; margin-top: 4px; }
  .newbest {
    font-family: "Archivo Black", sans-serif;
    font-size: 22px;
    color: #F5B63F;
    margin-top: 7px;
    transform: rotate(-2deg);
    text-shadow: -2px 0 var(--ink), 2px 0 var(--ink), 0 -2px var(--ink), 0 2px var(--ink),
      -1px -1px var(--ink), 1px -1px var(--ink), -1px 1px var(--ink), 1px 1px var(--ink), 0 3px 0 var(--ink);
  }
  .rankline { margin-top: 7px; font-size: 12px; font-weight: 700; color: #F0CFE0; }
  #overstats { font-weight: 400; }
  .newbest.hidden, .rankline.hidden, .filmwrap.hidden, .overboard.hidden { display: none; }
  .overboard { margin-top: 10px; }
  /* extra-life offer: white partner card with the +1 LIFE sticker */
  .filmwrap { position: relative; width: 100%; margin-top: 10px; }
  .lifebadge {
    position: absolute;
    top: -14px;
    right: 10px;
    z-index: 2;
    background: #FB1671;
    border: 3px solid var(--ink);
    border-radius: 999px;
    padding: 6px 13px;
    transform: rotate(6deg);
    font-family: "Archivo Black", sans-serif;
    font-size: 13px;
    color: #fff;
    box-shadow: 0 3px 0 var(--ink);
  }
  .filmcard {
    background: #fff;
    border: 3px solid var(--ink);
    border-radius: 20px;
    padding: 18px 18px 16px;
    text-align: left;
    color: var(--ink);
    box-shadow: 0 6px 0 var(--ink);
  }
  .filmcard img { display: block; width: 82px; height: auto; }
  .filmhead { font-family: "Archivo Black", sans-serif; font-size: 19px; line-height: 1.15; margin-top: 11px; }
  .filmsub { margin-top: 6px; font-size: 12.5px; line-height: 1.45; color: #6A6A74; }
  #filmbtn {
    width: 100%;
    margin-top: 14px;
    background: #FB1671;
    color: #fff;
    border: 3px solid var(--ink);
    border-radius: 999px;
    padding: 13px 10px;
    font-family: "Archivo Black", sans-serif;
    font-size: 14.5px;
    letter-spacing: 0.04em;
    box-shadow: 0 4px 0 var(--ink);
    cursor: pointer;
    animation: btnbeat 1.7s ease-in-out 0.8s infinite;
  }
  #filmbtn:active { transform: translateY(2px); box-shadow: 0 2px 0 var(--ink); }
  .againbtn {
    background: none;
    border: 0;
    color: #fff;
    font-family: inherit;
    font-weight: 800;
    font-size: 14px;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
  }
  .subline { font-weight: 800; font-size: 14px; color: #FFE3EF; }
  @media (max-height: 620px) {
    .hero { display: none; }
    .overlay { gap: 10px; }
  }

  /* ---------- keyframes ---------- */
  @keyframes rollermove { to { stroke-dashoffset: -26; } }
  @keyframes fieldshake { 25% { transform: translate(3px, -2px); } 75% { transform: translate(-3px, 2px); } }
  @keyframes frenzypulse { from { opacity: 0.55; } to { opacity: 1; } }
  @keyframes frontpulse { 50% { transform: scale(1.06); } }
  @keyframes gatepress { 40% { transform: translateY(3px) scale(0.96); } }
  @keyframes combopop {
    0% { transform: scale(1); }
    35% { transform: scale(1.4) rotate(-4deg); box-shadow: 0 2px 0 var(--ink), 0 0 0 7px rgba(240, 151, 43, 0.55); }
    100% { transform: scale(1); }
  }
  @keyframes damagehit {
    0% { opacity: 0; }
    7% { opacity: 1; }
    34% { opacity: 0.5; }
    52% { opacity: 0.95; }
    100% { opacity: 0; }
  }
  @keyframes heartrip {
    0% { transform: scale(0.3); opacity: 0; }
    12% { transform: scale(1.55) rotate(-9deg); opacity: 1; }
    24% { transform: scale(1.15) rotate(7deg); }
    38% { transform: scale(1.32) rotate(-4deg); opacity: 1; }
    100% { transform: translate(var(--tx), var(--ty)) scale(0.26) rotate(26deg); opacity: 0; }
  }
  @keyframes heartback {
    0% { transform: scale(0) rotate(-540deg); opacity: 0; }
    38% { transform: scale(1.9) rotate(0deg); opacity: 1; }
    50% { transform: scale(1.45) rotate(-7deg); }
    62% { transform: scale(1.75) rotate(5deg); }
    100% { transform: translate(var(--tx), var(--ty)) scale(0.3) rotate(0deg); opacity: 1; }
  }
  @keyframes heartgone {
    0% { transform: scale(1.75); color: #fff; }
    40% { transform: scale(0.7) rotate(16deg); }
    100% { transform: scale(0.82) rotate(-10deg); }
  }
  @keyframes fieldhit {
    0%, 100% { transform: translate(0, 0); }
    12% { transform: translate(-10px, 5px); }
    30% { transform: translate(9px, -6px); }
    50% { transform: translate(-6px, 3px); }
    74% { transform: translate(4px, -2px); }
  }
  /* px, not %: translateX percentages resolve against the cloud's own width */
  @keyframes drift { to { transform: translateX(calc(min(100vw, 402px) + 220px)); } }
  @keyframes blink { 50% { opacity: 0.25; } }
  @keyframes pop {
    30% { transform: scale(1.32) rotate(-5deg); opacity: 1; }
    55% { transform: scale(1.08) rotate(3deg); opacity: 1; }
    100% { transform: scale(0.3) rotate(8deg); opacity: 0; }
  }
  @keyframes poof {
    30% { transform: scale(1.15, 0.8) rotate(-10deg); opacity: 1; }
    100% { transform: translateY(-8px) scale(0.1) rotate(45deg); opacity: 0; }
  }
  @keyframes wigglex {
    25% { transform: translateX(-5px) rotate(-5deg) scale(1.05, 0.95); }
    75% { transform: translateX(5px) rotate(5deg) scale(1.05, 0.95); }
  }
  @keyframes bounce {
    35% { transform: translateY(-10px) scale(1.08, 0.9); }
    65% { transform: translateY(2px) scale(0.95, 1.07); }
  }
  @keyframes shake {
    20%, 60% { transform: translateX(-6px) rotate(-2deg); }
    40%, 80% { transform: translateX(6px) rotate(2deg); }
  }
  @keyframes wobble { 0%, 100% { transform: rotate(-2.5deg); } 50% { transform: rotate(2.5deg); } }
  @keyframes mashshiver { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(3deg); } }
  @keyframes tpglow { from { transform: scale(0.95); opacity: 0.7; } to { transform: scale(1.15); opacity: 1; } }
  @keyframes tpspark {
    0% { transform: translate(-50%, -50%) scale(0.3) rotate(0deg); opacity: 0; }
    30% { opacity: 1; }
    100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(1) rotate(40deg); opacity: 0; }
  }
  @keyframes tppts {
    0% { transform: translateY(6px) scale(0.6); opacity: 0; }
    30% { opacity: 0.9; }
    100% { transform: translateY(-36px) scale(1); opacity: 0; }
  }
  @keyframes tptap {
    0%, 100% { transform: scale(0.75); opacity: 0.35; }
    50% { transform: scale(1); opacity: 0.9; }
  }
  @keyframes bagsmack {
    0% { transform: scale(1); }
    35% { transform: scale(1.142, 0.847); }
    70% { transform: scale(0.95, 1.06); }
    100% { transform: scale(1); }
  }
  @keyframes floatup {
    0% { transform: translate(-50%, -50%) scale(0.6); opacity: 1; }
    25% { transform: translate(-50%, -80%) scale(1.18); opacity: 1; }
    100% { transform: translate(-50%, -190%) scale(1); opacity: 0; }
  }
  @keyframes toastin {
    0% { opacity: 0; transform: translateY(-18px) scale(0.85); }
    9% { opacity: 1; transform: translateY(3px) scale(1.05); }
    14%, 85% { opacity: 1; transform: translateY(0) scale(1); }
    100% { opacity: 0; transform: translateY(-8px) scale(0.95); }
  }
  @keyframes toastalertin {
    0% { opacity: 0; transform: translateY(-18px) scale(0.85); }
    8% { opacity: 1; transform: translateY(2px) scale(1.06); }
    12% { transform: translateX(-5px); }
    16% { transform: translateX(5px); }
    20%, 85% { opacity: 1; transform: translateX(0); }
    100% { opacity: 0; transform: translateY(-8px) scale(0.95); }
  }
  @keyframes ovin { from { opacity: 0; transform: translateY(16px) scale(0.92); } }
  @keyframes btnbeat { 50% { transform: scale(1.045); } }
  @keyframes chipbump { 40% { transform: scale(1.14); } }
  @keyframes sparkfly {
    to {
      transform: translate(-50%, -50%) translate(var(--dx), var(--dy)) rotate(var(--rz)) scale(0.4);
      opacity: 0;
    }
  }
  @keyframes twinkle {
    0%, 100% { opacity: 0.35; transform: scale(0.75) rotate(-8deg); }
    50% { opacity: 1; transform: scale(1.2) rotate(8deg); }
  }
  @keyframes zfloat {
    0% { opacity: 0; transform: translateY(7px) scale(0.8); }
    35% { opacity: 0.85; }
    100% { opacity: 0; transform: translateY(-15px) rotate(12deg); }
  }
  @media (prefers-reduced-motion: reduce) {
    .plane { display: none; }
    .cloud.c1, .cloud.c2, .cloud.c3, #belts .rollers, .item.sel svg,
    .item.mashing svg, .item.boss .itemin, .item.boss svg, .overlay > *, .tuthint,
    .item.magic:not(.locked) .itemin::after, .zzz span, #startbtn,
    #field.shake, .heart.gone, #game.frenzy::after, #filmbtn, #cc-claim,
    /* .overlay > * above loses to .coffeecard.is-won on specificity, so the won
       card kept pulsing forever with no way to stop it */
    .coffeecard.is-won { animation: none; }
    .item.banned .itemin { animation: none; filter: drop-shadow(0 0 8px rgba(229, 72, 77, 0.8)); }
    .zzz span { opacity: 0.6; }
  }
