/* ================= TRAINER LEVEL =================
   The first attempt at this was a settings screen with a yellow bar and a list
   of "+$2,400 / claimed" rows, under a conic-gradient sunburst. It was the
   generic mobile-game achievement popup, and it looked nothing like this app.

   This is built out of what the game is already made of:

     · a VENDING MACHINE — so the strip is a readout on its fascia, recessed
       behind a bezel, and the level-up is the machine dropping something down
       the chute into the tray
     · a POKÉ BALL — so what drops is a ball, and it bounces, and it WOBBLES
       before it opens, which is the single most recognisable animation in
       Pokémon and the first version didn't use it
     · the TRAINER ID CARD already in friends.css — guilloche, red band, sheen,
       ink stamp. The level screen is that card, so the two feel like the same
       object in the same wallet
     · BADGES. Pokémon's progression has never been a bar, it's eight badges in
       a case. A ladder of cash rows is a spreadsheet; a case with ten slots
       where three are filled and seven are dark silhouettes is a collection —
       and this is a collecting game. The empty slots do the work.
*/

/* ============================================================
   1 · THE STRIP — a readout on the machine, not a progress bar
   ============================================================ */
.xpbar{position:sticky;top:0;z-index:39;display:flex;align-items:center;gap:9px;
  margin:0 0 13px;padding:5px 9px 5px 5px;cursor:pointer;border-radius:12px;
  background:linear-gradient(180deg,#1b2039,#12162a 60%,#0e1224);
  border:1px solid #2b3154;
  box-shadow:0 4px 14px rgba(0,0,0,.45),inset 0 1px 0 rgba(255,255,255,.07),
    inset 0 -1px 0 rgba(0,0,0,.5);
  -webkit-tap-highlight-color:transparent;transition:border-color .18s,transform .1s}
.xpbar:hover{border-color:#3b4373}
.xpbar:active{transform:scale(.993)}
.xpbar:focus-visible{outline:2px solid var(--gold);outline-offset:3px}

/* the level, on a ball — same ball as the logo, so it's the app's own mark */
.xplv{flex:none;display:flex;align-items:center;gap:6px;padding:3px 9px 3px 4px;
  border-radius:99px;background:rgba(0,0,0,.34);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.07)}
.xpball{width:19px;height:19px;border-radius:50%;flex:none;position:relative;
  background:linear-gradient(180deg,var(--red) 0 46%,#fff 46% 54%,#eef0f8 54% 100%);
  border:1.5px solid #0c0d18;box-shadow:0 2px 6px rgba(227,53,13,.5)}
.xpball::after{content:'';position:absolute;inset:0;margin:auto;width:6px;height:6px;
  border-radius:50%;background:#fff;border:1.5px solid #0c0d18}
.xplv b{font-family:'Bebas Neue';font-size:16px;line-height:1;color:var(--gold);
  letter-spacing:.03em;text-shadow:0 0 9px rgba(255,203,5,.5)}
.xplv i{font-style:normal;font-family:'Outfit';font-size:7.5px;font-weight:800;
  letter-spacing:.11em;color:var(--dim2);margin-right:-3px}

/* THE BAR — one smooth run of light in a glass tube.
   It was notched into 20 segments so progress could be "counted". Nobody counts
   segments; they just made a smooth thing look like a battery icon. Gone.

   Padding on the track rather than insets on the fill, so `width:var(--p)` is a
   percentage of the CONTENT box and 100% lands exactly flush — an inset fill
   with a percentage width overshoots its own tube by the inset at the top end.

   The fill eases over .62s, so points visibly FLOW in instead of teleporting. */
.xptrack{flex:1;min-width:0;position:relative;display:block;height:14px;padding:2px;
  border-radius:99px;
  background:linear-gradient(180deg,#04050c,#0a0d1a 60%,#0d1120);
  box-shadow:inset 0 2px 5px rgba(0,0,0,.95),inset 0 0 0 1px rgba(255,255,255,.055),
    inset 0 -1px 0 rgba(255,255,255,.05)}

.xpfill{display:block;height:100%;width:var(--p,0%);position:relative;overflow:hidden;
  border-radius:99px;
  background:linear-gradient(180deg,#fff6c9 0%,#ffdf5e 26%,var(--yellow) 52%,#e0a200 82%,#bd8600 100%);
  box-shadow:0 0 12px rgba(255,203,5,.7),0 0 26px rgba(255,203,5,.32),
    inset 0 1px 0 rgba(255,255,255,.65),inset 0 -1px 0 rgba(120,80,0,.45);
  transition:width .62s cubic-bezier(.22,.68,.28,1),filter .3s ease}

/* a highlight travelling along the liquid. It's an oversized wedge that fades
   to nothing at both ends, translated across — animating background-position on
   a gradient snaps at the loop point, and on a repeating one it drags a seam. */
.xpfill::before{content:'';position:absolute;top:0;bottom:0;left:0;width:55%;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.6) 48%,transparent);
  animation:xpshine 2.9s linear infinite}
@keyframes xpshine{from{transform:translateX(-200%)}to{transform:translateX(360%)}}

/* the leading edge, bright and soft — a wedge inside the fill rather than a
   glowing box, because the fill has to clip its own shine and a box-shadow
   would be clipped along with it */
.xpfill::after{content:'';position:absolute;top:0;bottom:0;right:0;width:16px;
  border-radius:99px;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.35) 55%,rgba(255,255,255,.92));
  animation:xptip 1.7s ease-in-out infinite}
@keyframes xptip{0%,100%{opacity:.6}50%{opacity:1}}

/* points landing: the tube brightens for a beat as the level fills */
.xpbar.tick .xpfill{filter:brightness(1.5) saturate(1.15)}

/* the three dailies live here as balls, so the strip says WHAT is left, not
   just that something is */
.xppips{flex:none;display:flex;gap:3px}
.xppips i{width:9px;height:9px;border-radius:50%;background:#0b0e1c;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.13)}
.xppips i.on{background:linear-gradient(180deg,var(--red) 0 46%,#fff 46% 100%);
  box-shadow:0 0 7px rgba(227,53,13,.8),inset 0 0 0 1px rgba(0,0,0,.5)}
.xppips.all i{animation:xppip .5s cubic-bezier(.2,1.6,.4,1) both}
.xppips.all i:nth-child(2){animation-delay:.06s}
.xppips.all i:nth-child(3){animation-delay:.12s}
@keyframes xppip{0%{transform:scale(1)}50%{transform:scale(1.5)}100%{transform:scale(1)}}

.xpnum{flex:none;font-size:10px;font-weight:800;color:var(--dim2);
  font-variant-numeric:tabular-nums;letter-spacing:.02em}
.xpnum b{color:var(--gold);font-weight:800}
.xpbar.maxed .xpfill{background:linear-gradient(180deg,#fff,#ffe98f 40%,var(--gold));
  animation:xpmax 2.6s ease-in-out infinite}
@keyframes xpmax{0%,100%{box-shadow:0 0 10px rgba(255,203,5,.6)}
  50%{box-shadow:0 0 20px rgba(255,255,255,.9)}}
@media(max-width:400px){
  .xpbar{gap:7px;padding:4px 7px 4px 4px}
  .xpnum{display:none}          /* the bar is the readout; the digits are a luxury */
}

/* xp landing, thrown off the strip */
.xpfloat{position:fixed;z-index:150;transform:translate(-50%,0);pointer-events:none;
  font-family:'Bebas Neue';font-size:18px;color:var(--yellow);letter-spacing:.04em;
  text-shadow:0 2px 12px rgba(0,0,0,.9),0 0 14px rgba(255,203,5,.6);
  animation:xpfloat 1.05s cubic-bezier(.2,.9,.3,1) forwards}
@keyframes xpfloat{
  0%{opacity:0;transform:translate(-50%,6px) scale(.75)}
  20%{opacity:1;transform:translate(-50%,-7px) scale(1.15)}
  100%{opacity:0;transform:translate(-50%,-38px) scale(1)}
}

/* ============================================================
   2 · THE SCREEN
   ------------------------------------------------------------
   What was here: six identical rounded rectangles in the same navy, stacked
   vertically, each introduced by small grey letterspaced caps over a hairline.
   That is a settings dashboard. Nothing on it was a Pokémon game, and the app's
   own vivid rarity palette — pink, cyan, orange, purple — appeared zero times.

   What it is now:
     · SKEWED TABS instead of grey caps. Pokémon UI is full of slanted panels;
       a red tab wedged over the content reads as a game, a hairline does not.
     · COLOUR. Every task carries a type colour on a raked stripe, drawn from
       the rarity palette that already exists in base.css.
     · TEXTURE. Halftone dots and raked speed lines over a three-point colour
       wash, so the panel has a surface instead of being a flat fill.
     · GHOST NUMERALS. A huge 1 / 2 / 3 sunk into each slab.
     · It DEALS IN. Card first, then each tab and slab from alternating sides.
   ============================================================ */
#levelOverlay{position:fixed;inset:0;z-index:160;display:none;
  background:rgba(6,8,20,.76);backdrop-filter:blur(8px);
  align-items:flex-end;justify-content:center}
#levelOverlay.show{display:flex;animation:ffade .18s ease}

.lvpanel{position:relative;width:100%;max-width:520px;max-height:92vh;max-height:92dvh;
  display:flex;flex-direction:column;overflow:hidden;
  border:1px solid #3a3f6b;border-bottom:none;border-radius:22px 22px 0 0;
  /* three coloured lights instead of one flat navy */
  background:
    radial-gradient(115% 55% at 12% 0%,rgba(227,53,13,.26),transparent 58%),
    radial-gradient(95% 50% at 92% 18%,rgba(59,108,255,.22),transparent 62%),
    radial-gradient(120% 55% at 45% 104%,rgba(176,108,255,.2),transparent 62%),
    linear-gradient(180deg,#161b36,#0c1023);
  box-shadow:0 -18px 54px rgba(0,0,0,.7);
  animation:fup .3s cubic-bezier(.2,.9,.3,1)}
/* halftone — the dot screen off a printed card */
.lvpanel::before{content:'';position:absolute;inset:0;z-index:0;pointer-events:none;opacity:.55;
  background-image:radial-gradient(circle,rgba(255,255,255,.06) 1px,transparent 1.5px);
  background-size:9px 9px}
/* raked lines, barely there — they give the flat areas a direction */
.lvpanel::after{content:'';position:absolute;inset:0;z-index:0;pointer-events:none;opacity:.4;
  background:repeating-linear-gradient(114deg,transparent 0 24px,rgba(255,255,255,.028) 24px 26px)}
@media(min-width:560px){
  #levelOverlay{align-items:center}
  .lvpanel{border-radius:22px;border-bottom:1px solid #3a3f6b;max-height:86vh;max-height:86dvh}
}
.lvx{position:absolute;top:13px;right:13px;z-index:9;width:32px;height:32px;border-radius:50%;
  cursor:pointer;border:2px solid rgba(255,255,255,.2);background:rgba(8,10,22,.7);
  color:#e6eaff;display:grid;place-items:center;transition:.15s}
.lvx svg{width:15px;height:15px}
.lvx:hover{background:var(--red);border-color:var(--red2);transform:rotate(90deg)}
.lvhead{position:relative;z-index:1;padding:15px 14px 0;flex:none}
.lvbody{position:relative;z-index:1;flex:1;overflow-y:auto;padding:0 14px 22px;
  -webkit-overflow-scrolling:touch}

/* --- the trainer card, tilted a hair off square so it reads as an object --- */
.lvcard{position:relative;overflow:hidden;border-radius:14px;
  background:linear-gradient(158deg,#232c56,#171d3d 52%,#101632);
  border:1px solid #444d8c;
  box-shadow:0 16px 40px rgba(0,0,0,.65),inset 0 1px 0 rgba(255,255,255,.12)}
.lvc-guilloche{position:absolute;inset:0;pointer-events:none;opacity:.55;
  background:
    repeating-radial-gradient(circle at 18% 118%,transparent 0 7px,rgba(140,160,255,.085) 7px 8px),
    repeating-radial-gradient(circle at 92% -14%,transparent 0 9px,rgba(255,203,5,.06) 9px 10px)}
/* one holo pass, the same move the card reveal uses */
.lvc-sheen{position:absolute;top:-30%;bottom:-30%;width:38%;pointer-events:none;left:-55%;
  transform:rotate(9deg);
  background:linear-gradient(105deg,transparent,rgba(255,255,255,.16) 46%,transparent);
  animation:lvsheen 1.6s cubic-bezier(.4,0,.3,1) .35s both}
@keyframes lvsheen{to{left:120%}}
/* the red band, cut on a slant at the right end like a type bar */
.lvc-band{position:relative;display:flex;align-items:center;gap:8px;padding:8px 26px 8px 13px;
  background:linear-gradient(90deg,#ff4a1c,#e3350d 45%,#8d1b04);
  clip-path:polygon(0 0,100% 0,calc(100% - 20px) 100%,0 100%);
  box-shadow:inset 0 -2px 0 rgba(0,0,0,.35),0 1px 0 rgba(255,255,255,.1)}
.lvc-band .pb{width:15px;height:15px;border-radius:50%;flex:none;position:relative;
  background:linear-gradient(180deg,#fff 0 46%,#ffd9d0 46% 100%);border:1.5px solid #4a0d02}
.lvc-band .pb::after{content:'';position:absolute;inset:0;margin:auto;width:5px;height:5px;
  border-radius:50%;background:#fff;border:1.5px solid #4a0d02}
.lvc-kick{flex:1;font-family:'Bebas Neue';font-size:15px;letter-spacing:.19em;color:#fff;
  text-shadow:0 1px 3px rgba(0,0,0,.5)}
.lvc-no{position:absolute;right:13px;top:9px;font-size:9px;font-weight:800;letter-spacing:.1em;
  color:#7b83b5;font-variant-numeric:tabular-nums}
.lvc-main{position:relative;display:flex;gap:13px;padding:13px 14px 12px}
.lvc-medal{flex:none;width:74px;display:flex;flex-direction:column;align-items:center;gap:5px}
/* Badges arrive from js/badges.js as <img> with the SVG as a data URI, so all
   this file does is size them — the frame is painted into the artwork now. */
.bsvg{display:block;width:100%;height:100%;pointer-events:none}
.lvc-medal .bsvg{width:60px;height:60px;filter:drop-shadow(0 4px 8px rgba(0,0,0,.6))}
.lvc-lvl{font-family:'Bebas Neue';font-size:12px;letter-spacing:.14em;color:var(--gold)}
.lvc-id{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}
.lvc-name{font-family:'Bebas Neue';font-size:29px;letter-spacing:.03em;color:#fff;line-height:1;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  text-shadow:0 2px 0 rgba(0,0,0,.35),0 0 22px rgba(120,150,255,.3)}
/* the rank, on a slanted plate rather than a rounded pill */
.lvc-title{display:inline-block;align-self:flex-start;margin-top:5px;padding:3px 13px 2px;
  transform:skewX(-11deg);
  font-family:'Bebas Neue';font-size:13px;letter-spacing:.16em;color:#2a1e02;
  background:linear-gradient(180deg,#ffe884,var(--yellow) 60%,#dda200);
  box-shadow:0 3px 10px -3px rgba(255,203,5,.9),inset 0 1px 0 rgba(255,255,255,.6)}
.lvc-title span{display:inline-block;transform:skewX(11deg)}
.lvc-bar{margin-top:11px;position:relative;display:block;height:15px;padding:2px;
  border-radius:99px;background:linear-gradient(180deg,#04050c,#0a0d1a 60%,#0d1120);
  box-shadow:inset 0 2px 5px rgba(0,0,0,.95),inset 0 0 0 1px rgba(255,255,255,.06)}
.lvc-bar>i{display:block;height:100%;width:var(--p,0%);position:relative;overflow:hidden;
  border-radius:99px;
  background:linear-gradient(180deg,#fff6c9 0%,#ffdf5e 26%,var(--yellow) 52%,#e0a200 82%,#bd8600 100%);
  box-shadow:0 0 12px rgba(255,203,5,.6),inset 0 1px 0 rgba(255,255,255,.65),
    inset 0 -1px 0 rgba(120,80,0,.45);
  transition:width .8s cubic-bezier(.22,.68,.28,1)}
.lvc-bar>i::before{content:'';position:absolute;top:0;bottom:0;left:0;width:55%;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.6) 48%,transparent);
  animation:xpshine 2.9s linear infinite}
.lvc-bar>i::after{content:'';position:absolute;top:0;bottom:0;right:0;width:18px;
  border-radius:99px;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.35) 55%,rgba(255,255,255,.92));
  animation:xptip 1.7s ease-in-out infinite}
.lvc-xp{display:flex;justify-content:space-between;margin-top:5px;font-size:9.5px;
  font-weight:800;color:#7b83b5;font-variant-numeric:tabular-nums}
.lvc-xp b{color:var(--gold)}
.lvc-badges{position:relative;display:flex;gap:5px;padding:9px 14px 11px;flex-wrap:wrap;
  border-top:1px solid rgba(255,255,255,.08);background:rgba(4,6,16,.4)}
/* 19px, down from 23: twenty of these on the trainer card wrapped to three
   rows and pushed the card taller than the name on it. */
.lvc-badges .bsvg{width:19px;height:19px}
.lvc-badges>span{flex:none;width:19px;height:19px}

/* ============================================================
   3 · SECTION TABS — wedged in on a slant, not grey caps
   ============================================================ */
.lvtab{position:relative;display:flex;align-items:center;gap:0;
  margin:22px 0 11px -14px;padding:0;pointer-events:none}
.lvtab b{display:inline-block;padding:6px 20px 5px 22px;transform:skewX(-13deg);
  font-family:'Bebas Neue';font-size:16px;letter-spacing:.15em;color:#fff;
  background:linear-gradient(90deg,var(--t1,#e3350d),var(--t2,#8d1b04));
  box-shadow:0 5px 16px -5px var(--t1,#e3350d),inset 0 1px 0 rgba(255,255,255,.28),
    inset 0 -2px 0 rgba(0,0,0,.3)}
.lvtab b span{display:inline-block;transform:skewX(13deg);text-shadow:0 1px 2px rgba(0,0,0,.4)}
/* the rule that runs off the end of the tab, in the tab's own colour */
.lvtab i{flex:1;height:2px;margin-left:9px;border-radius:2px;
  background:linear-gradient(90deg,var(--t1,#e3350d),transparent);opacity:.55}
.lvtab em{flex:none;margin-left:9px;font-style:normal;font-size:10px;font-weight:800;
  letter-spacing:.05em;color:#8b93c4;font-variant-numeric:tabular-nums}
.lvtab em b{all:unset;color:#dfe4ff;font-weight:800}

/* ============================================================
   4 · TODAY'S THREE — colour-coded slabs with a raked stripe
   ============================================================ */
.lvtasks{display:flex;flex-direction:column;gap:10px}
.lvtask{position:relative;display:flex;align-items:center;gap:12px;
  padding:13px 13px 13px 22px;border-radius:4px 13px 13px 4px;overflow:hidden;
  background:linear-gradient(100deg,#232a4d,#161c37 60%,#121730);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.09),0 5px 14px rgba(0,0,0,.45),
    inset 0 0 0 1px rgba(255,255,255,.05);
  transition:box-shadow .25s,transform .18s}
.lvtask:hover{transform:translateX(2px)}
/* the type stripe, raked so the slab has a leading edge */
.lvstripe{position:absolute;left:0;top:0;bottom:0;width:16px;
  clip-path:polygon(0 0,100% 0,58% 100%,0 100%);
  background:linear-gradient(180deg,var(--c1),var(--c2));
  box-shadow:0 0 18px -2px var(--c1)}
/* the ghosted numeral sunk into the slab */
.lvtask::after{content:attr(data-n);position:absolute;right:64px;top:50%;
  transform:translateY(-50%);font-family:'Bebas Neue';font-size:74px;line-height:1;
  color:#fff;opacity:.055;pointer-events:none;z-index:0}
/* progress runs along the foot of the slab in the task's own colour */
.lvtask::before{content:'';position:absolute;left:0;bottom:0;height:3px;width:var(--p,0%);
  background:linear-gradient(90deg,var(--c2),var(--c1));
  box-shadow:0 0 12px var(--c1);transition:width .6s cubic-bezier(.2,.9,.3,1);z-index:1}
.lvtask>*{position:relative;z-index:2}

/* done goes RED — the ball colour, not the productivity-app green, and not the
   task's own type colour either, so finishing visibly changes its state */
.lvtask.done{background:linear-gradient(100deg,#43171a,#2a1226 62%,#1b1030);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 0 26px -7px rgba(227,53,13,.8),
    inset 0 0 0 1px rgba(227,53,13,.45)}
.lvtask.done .lvstripe{--c1:#ff5a37;--c2:#8d1b04}
.lvtask.done::before{width:100%;background:linear-gradient(90deg,#8d1b04,#ff5a37);
  box-shadow:0 0 12px rgba(227,53,13,.9)}

/* an empty socket, and a Poké Ball sitting in it once the task lands */
.lvlamp{flex:none;width:32px;height:32px;border-radius:50%;position:relative;
  background:#080a15;box-shadow:inset 0 2px 6px rgba(0,0,0,.95),inset 0 0 0 1px rgba(255,255,255,.1)}
.lvlamp::before{content:'';position:absolute;inset:5px;border-radius:50%;
  background:#161c37;box-shadow:inset 0 0 0 1px rgba(255,255,255,.06)}
.lvtask.done .lvlamp::before{
  background:linear-gradient(180deg,#ff5a37 0 44%,#0c0d18 44% 56%,#f4f6ff 56% 100%);
  box-shadow:0 0 15px rgba(227,53,13,.95),inset 0 0 0 1.5px rgba(0,0,0,.6);
  animation:lvlamp .45s cubic-bezier(.2,1.6,.4,1)}
.lvtask.done .lvlamp::after{content:'';position:absolute;inset:0;margin:auto;
  width:8px;height:8px;border-radius:50%;background:#fff;
  box-shadow:0 0 0 1.7px #0c0d18,0 0 9px rgba(255,255,255,.85);
  animation:lvlamp .45s cubic-bezier(.2,1.6,.4,1)}
@keyframes lvlamp{0%{transform:scale(0)}62%{transform:scale(1.3)}100%{transform:scale(1)}}

.lvtxt{flex:1;min-width:0}
.lvtxt b{display:block;font-size:14px;font-weight:700;color:#fff;line-height:1.25;
  text-shadow:0 1px 2px rgba(0,0,0,.5)}
.lvtxt em{display:block;font-style:normal;font-size:10.5px;color:#8089b8;margin-top:2px;
  font-variant-numeric:tabular-nums}
.lvtask.done .lvtxt b{color:#ffd9cd}
/* the XP tag, raked to match the stripe */
.lvxp{flex:none;transform:skewX(-11deg);padding:4px 11px 3px;
  font-family:'Bebas Neue';font-size:16px;letter-spacing:.05em;color:#2a1e02;
  background:linear-gradient(180deg,#ffe884,var(--yellow) 60%,#dda200);
  box-shadow:0 3px 10px -4px rgba(255,203,5,.9),inset 0 1px 0 rgba(255,255,255,.55)}
.lvxp span{display:inline-block;transform:skewX(11deg)}
.lvtask.done .lvxp{background:linear-gradient(180deg,#ff8560,#e3350d 60%,#8d1b04);color:#fff;
  box-shadow:0 3px 10px -4px rgba(227,53,13,.95),inset 0 1px 0 rgba(255,255,255,.3)}
.lvstamp{position:absolute;right:70px;top:50%;z-index:3;pointer-events:none;
  transform:translateY(-50%) rotate(-11deg);
  padding:2px 9px 1px;border:2px solid rgba(255,82,48,.85);border-radius:4px;
  font-family:'Bebas Neue';font-size:13px;letter-spacing:.13em;color:rgba(255,82,48,.92);
  box-shadow:0 0 0 1px rgba(255,82,48,.2);text-shadow:0 0 10px rgba(255,82,48,.35);
  animation:lvstampin .42s cubic-bezier(.3,1.7,.4,1) both}
@keyframes lvstampin{
  0%{opacity:0;transform:translateY(-50%) rotate(-11deg) scale(2.2)}
  70%{opacity:1;transform:translateY(-50%) rotate(-11deg) scale(.93)}
  100%{opacity:1;transform:translateY(-50%) rotate(-11deg) scale(1)}}

/* the all-three bonus */
.lvbonus{position:relative;margin-top:10px;display:flex;align-items:center;gap:13px;
  padding:13px 13px 13px 18px;border-radius:4px 13px 13px 4px;overflow:hidden;
  background:linear-gradient(100deg,#232a4d,#161c37 60%,#121730);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.07),0 5px 14px rgba(0,0,0,.4);
  transition:.3s}
.lvbonus .lvballs{flex:none;display:flex;gap:6px;position:relative;z-index:2}
.lvbonus .lvballs i{width:17px;height:17px;border-radius:50%;position:relative;
  background:#080a15;box-shadow:inset 0 0 0 1px rgba(255,255,255,.14)}
.lvbonus .lvballs i.on{background:linear-gradient(180deg,#ff5a37 0 44%,#0c0d18 44% 56%,#fff 56% 100%);
  box-shadow:0 0 11px rgba(227,53,13,.9),inset 0 0 0 1px rgba(0,0,0,.5)}
.lvbonus .lvtxt,.lvbonus .lvxp{position:relative;z-index:2}
.lvbonus.lit{background:linear-gradient(100deg,#4a3a08,#3a2140 62%,#2a1030);
  box-shadow:inset 0 0 0 1px rgba(255,203,5,.6),0 0 30px -6px rgba(255,203,5,.75)}
/* a shine crossing the bonus once it's earned */
.lvbonus.lit::after{content:'';position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(105deg,transparent 32%,rgba(255,255,255,.18) 50%,transparent 68%);
  background-size:250% 100%;animation:lvjack 2.4s linear infinite}
@keyframes lvjack{from{background-position:250% 0}to{background-position:-60% 0}}

/* ------------------------------------------------------------
   THE DAILY PRIZE — clearing all three pays out a real Elite Trainer Box.
   The XP bonus alone is abstract; a box you can see is the thing worth coming
   back for. The prize is visible from the start, because a reward you can't see
   until you've earned it can't motivate anything.

   The reveal is a SLOT REEL. A static box was dead on arrival — instead the
   artwork rips through every ETB in the game while the box falls out of the
   port, decelerates, and snaps onto the one you won. Twenty-four real product
   photos off the same CDN the shop already uses.
   ------------------------------------------------------------ */

/* the chip in the bonus row: a live box, quietly cycling, so the row shows you
   what is actually on offer instead of the letters E T B */
.lvetb{position:relative;flex:none;width:42px;height:48px;border-radius:5px;overflow:hidden;
  background:linear-gradient(180deg,#ffffff,#eef1f7);
  box-shadow:0 4px 11px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.35);
  filter:grayscale(.75) brightness(.55);transition:filter .4s,transform .4s}
.lvetb img{position:absolute;inset:3px;width:calc(100% - 6px);height:calc(100% - 6px);
  object-fit:contain;display:block;transition:opacity .3s}
.lvetb.swap img{opacity:0}
.lvbonus.ready .lvetb,.lvbonus.lit .lvetb{filter:none;transform:scale(1.08) rotate(-3deg);
  box-shadow:0 5px 16px rgba(0,0,0,.7),0 0 16px -2px rgba(255,203,5,.7),
    inset 0 0 0 1px rgba(255,203,5,.5)}

/* ---- PRIZE READY ----
   This was a row: 40px thumbnail, some text, a small button — the same shape as
   the three task slabs stacked directly above it. The most important moment on
   the screen was wearing the costume of a list item.

   So it stops being a row. It becomes the machine's COLLECTION WINDOW: the
   product sitting behind lit glass, big enough to actually see, over a full
   width illuminated button. Different silhouette, different height, different
   job — and the box keeps cycling, which is how "a random ETB" gets explained
   without a sentence explaining it. */
.lvbonus.ready{display:block;padding:0;overflow:hidden;cursor:pointer;border-radius:4px 14px 14px 4px;
  background:
    radial-gradient(85% 130% at 20% 45%,rgba(255,203,5,.3),transparent 60%),
    linear-gradient(100deg,#3d2d09,#2b1c3f 58%,#1a1030);
  animation:lvready 2.2s ease-in-out infinite}
@keyframes lvready{
  0%,100%{box-shadow:inset 0 0 0 2px rgba(255,203,5,.55),0 0 26px -10px rgba(255,203,5,.7)}
  50%{box-shadow:inset 0 0 0 2px rgba(255,203,5,.95),0 0 44px -6px rgba(255,203,5,.95)}}
.lvbonus.ready:active{transform:scale(.988)}

.lvwin{position:relative;display:flex;align-items:center;gap:15px;padding:15px 16px 13px}
/* the glass the product sits behind */
.lvglass{position:relative;flex:none;width:88px;height:101px;perspective:400px}
.lvglass .bx{position:absolute;inset:0;border-radius:8px;overflow:hidden;
  transform:rotate(-5deg);
  background:linear-gradient(180deg,#ffffff,#eef1f7);
  box-shadow:0 12px 24px rgba(0,0,0,.75),0 0 0 1px rgba(255,255,255,.45);
  animation:lvbob 3.4s ease-in-out infinite}
.lvglass img{position:absolute;inset:4px;width:calc(100% - 8px);height:calc(100% - 8px);
  object-fit:contain;display:block;transition:opacity .28s}
.lvglass.swap img{opacity:0}
/* it floats rather than sits — the shelf is glass, there's nothing under it */
@keyframes lvbob{0%,100%{transform:rotate(-5deg) translateY(0)}
  50%{transform:rotate(-3.5deg) translateY(-6px)}}
/* the pool of light it casts on the shelf below */
.lvglass::after{content:'';position:absolute;left:6%;right:6%;bottom:-11px;height:12px;
  border-radius:50%;background:radial-gradient(ellipse,rgba(255,203,5,.5),transparent 70%);
  filter:blur(3px);animation:lvpool 3.4s ease-in-out infinite}
@keyframes lvpool{0%,100%{transform:scaleX(1);opacity:.75}50%{transform:scaleX(.82);opacity:.45}}
/* a hard reflection across the glass */
.lvglass .rfl{position:absolute;top:-20%;bottom:-20%;left:-40%;width:36%;z-index:3;
  transform:rotate(14deg);pointer-events:none;border-radius:50%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.34) 50%,transparent);
  animation:lvrfl 4.4s cubic-bezier(.5,0,.4,1) infinite}
@keyframes lvrfl{0%{left:-40%}42%,100%{left:130%}}
/* motes drifting up off it */
.lvmotes{position:absolute;inset:-14px -6px;pointer-events:none;z-index:2}
.lvmotes i{position:absolute;bottom:0;width:3px;height:3px;border-radius:50%;
  background:var(--gold);box-shadow:0 0 7px var(--gold);opacity:0;
  animation:lvmote 3.6s ease-in-out infinite}
.lvmotes i:nth-child(1){left:14%;animation-delay:0s}
.lvmotes i:nth-child(2){left:38%;animation-delay:.8s}
.lvmotes i:nth-child(3){left:62%;animation-delay:1.7s}
.lvmotes i:nth-child(4){left:84%;animation-delay:2.5s}
@keyframes lvmote{
  0%{opacity:0;transform:translateY(0) scale(.5)}
  20%{opacity:1}
  100%{opacity:0;transform:translateY(-72px) scale(1)}}

.lvwin-t{flex:1;min-width:0}
.lvwin-t b{display:block;font-family:'Bebas Neue';font-size:27px;letter-spacing:.04em;
  line-height:1;color:#fff;text-shadow:0 2px 14px rgba(255,203,5,.55),0 2px 0 rgba(0,0,0,.4)}
.lvwin-t em{display:block;font-style:normal;font-size:11.5px;color:#f0d99a;margin-top:5px;
  line-height:1.4}
.lvwin-t i{display:inline-flex;align-items:center;gap:6px;margin-top:9px;font-style:normal;
  font-size:9px;font-weight:800;letter-spacing:.2em;text-transform:uppercase;color:#2a1e02;
  background:linear-gradient(180deg,#ffe884,var(--yellow));padding:3px 9px;border-radius:99px}

/* the illuminated button across the foot of the window */
.lvcollect{position:relative;display:block;width:100%;padding:13px 0 12px;border:none;
  cursor:pointer;overflow:hidden;
  font-family:'Bebas Neue';font-size:20px;letter-spacing:.2em;color:#2a1e02;
  background:linear-gradient(180deg,#fff6cf,#ffd23a 45%,#e8a600 78%,#c78700);
  box-shadow:inset 0 2px 0 rgba(255,255,255,.85),inset 0 -3px 0 rgba(140,92,0,.55),
    0 -6px 22px -6px rgba(255,203,5,.9)}
/* light sweeping across the button face */
.lvcollect::after{content:'';position:absolute;top:0;bottom:0;left:-45%;width:38%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.75) 50%,transparent);
  animation:lvsweep 2.6s cubic-bezier(.45,0,.4,1) infinite}
@keyframes lvsweep{0%{left:-45%}55%,100%{left:120%}}
.lvcollect:active{filter:brightness(1.12)}

/* collected: back to a quiet row, with the box you actually won */
.lvbonus.claimed{opacity:.85;background:linear-gradient(100deg,#1e2444,#141a33 60%,#111629)}
.lvbonus.claimed .lvetb{filter:none;transform:none}
.lvbonus.claimed .lvxp{background:linear-gradient(180deg,#6c7350,#454a2f);color:#dfe6c9;
  box-shadow:none}

/* ---- the reel ----
   Same delivery port, same fall, same bounce as the level-up — a vending
   machine dispensing a PRODUCT is more on-brand for a box than a Poké Ball is,
   and the port already exists. It just doesn't wobble or split open. */
/* A WHITE PRODUCT TILE.
   Three goes at this. `cover` filled the frame and sliced 18% off the box —
   lid and base gone. `contain` showed the whole box but exposed the white
   background baked into the photo as a rectangle floating in a dark UI.

   Scaling the image up to push that white out of frame is the obvious third
   idea and it's a trap: the padding differs per product, so a number that trims
   one box cleanly clips the next one's lid.

   So the white stops being a leak and becomes the design. The frame IS a white
   tile — the photo's own background merges into it with no seam, nothing is
   cropped, and a white card in a dark panel is how every shop on earth shows a
   product anyway. It also matches the machine: a lit box behind glass. */
.lvbox{position:relative;width:150px;height:172px;border-radius:12px;overflow:hidden;
  background:linear-gradient(180deg,#ffffff,#eef1f7);
  box-shadow:0 20px 42px rgba(0,0,0,.8),0 0 0 1px rgba(255,255,255,.5),
    inset 0 -14px 22px -14px rgba(90,100,140,.35)}
.lvbox img{position:absolute;inset:6px;width:calc(100% - 12px);height:calc(100% - 12px);
  object-fit:contain;display:block}
.lvbox .lid{display:none}
/* if the CDN never answers, the drawn box takes the tile's place entirely */
.lvbox.noart{background:linear-gradient(160deg,#2f3a72,#10142c);
  box-shadow:0 20px 42px rgba(0,0,0,.8),inset 0 0 0 1px rgba(255,255,255,.16)}
.lvbox.noart .lid{display:block;position:absolute;top:0;left:0;right:0;height:25%;z-index:0;
  background:linear-gradient(180deg,#ffd94a,#e0a400)}
/* a glass glare riding over the artwork the whole time */
.lvbox .glare{position:absolute;top:-40%;bottom:-40%;left:-60%;width:45%;z-index:2;
  transform:rotate(11deg);pointer-events:none;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.3) 48%,transparent);
  animation:lvglare 2.6s cubic-bezier(.4,0,.4,1) 1.2s infinite}
@keyframes lvglare{0%{left:-60%}55%,100%{left:130%}}
/* while it's ripping through them, the edges smear */
.lvbox.spin{filter:blur(.4px) saturate(1.12)}
.lvbox.spin img{transform:scale(1.03)}

/* the snap when it lands on one */
.lvbox.lock{animation:lvlock .55s cubic-bezier(.2,1.55,.35,1)}
@keyframes lvlock{0%{transform:scale(1)}32%{transform:scale(1.18) rotate(-1.5deg)}
  62%{transform:scale(.97) rotate(.5deg)}100%{transform:scale(1)}}
.lvbox .snap{position:absolute;inset:0;z-index:3;pointer-events:none;opacity:0;
  border-radius:9px;background:#fff}
.lvbox.lock .snap{animation:lvsnap .42s ease-out}
@keyframes lvsnap{0%{opacity:.95}100%{opacity:0}}
/* a gold ring thrown outward as it locks */
.lvring{position:absolute;left:50%;top:50%;width:150px;height:172px;margin:-86px 0 0 -75px;
  border-radius:12px;border:3px solid var(--gold);pointer-events:none;opacity:0;z-index:4}
.lvbox.lock .lvring{animation:lvringout .6s cubic-bezier(.2,.7,.3,1)}
@keyframes lvringout{0%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(1.7)}}

/* the shadow must not fade — nothing bursts here, the box stays in the tray */
#lvUpOverlay.reward .lvshadow{animation:lvdrop2 .46s cubic-bezier(.55,0,.85,.6) .12s both,
  lvshadow2 .44s cubic-bezier(.3,0,.4,1) .58s forwards}
#lvUpOverlay.reward .lvstage{height:226px}

/* the text holds until the reel has locked, so it can't spoil the result */
.lvrw{opacity:0;animation:lvfade .4s ease both}
.lvrw-kick{margin-top:8px;font-size:10px;font-weight:800;letter-spacing:.32em;
  text-transform:uppercase;color:var(--gold);animation-delay:2.5s}
.lvrw-name{margin-top:5px;font-family:'Bebas Neue';font-size:31px;letter-spacing:.03em;
  color:#fff;line-height:1.05;text-shadow:0 3px 18px rgba(0,0,0,.8);animation-delay:2.6s}
/* what you actually won, under the set name — a booster box and an ETB look
   alike at reel size, so the packs figure is the thing that reads as "bigger" */
.lvrw-kind{margin-top:3px;font-size:11px;font-weight:700;letter-spacing:.09em;
  color:#9aa3d0;animation-delay:2.68s}
.lvrw-btn{margin-top:22px;transform:skewX(-11deg);padding:11px 28px 10px;border:none;cursor:pointer;
  font-family:'Bebas Neue';font-size:18px;letter-spacing:.13em;color:#2a1e02;
  background:linear-gradient(180deg,#fff3b8,var(--yellow) 55%,#dda200);
  box-shadow:0 8px 20px -6px rgba(255,203,5,1),inset 0 1px 0 rgba(255,255,255,.7);
  animation-delay:2.8s}
.lvrw-btn span{display:inline-block;transform:skewX(11deg)}
.lvrw-btn:active{filter:brightness(1.12)}

/* ============================================================
   5 · NEXT UP + THE CASE
   ============================================================ */
.lvnext{position:relative;display:flex;align-items:center;gap:13px;
  padding:13px 14px 13px 18px;border-radius:4px 13px 13px 4px;overflow:hidden;
  background:linear-gradient(100deg,#232a4d,#161c37 60%,#121730);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.07),0 5px 14px rgba(0,0,0,.4)}
.lvnext .n{flex:none;width:36px;height:36px;display:grid;place-items:center;
  transform:skewX(-11deg);font-family:'Bebas Neue';font-size:18px;color:#2a1e02;
  background:linear-gradient(180deg,#ffe884,var(--yellow) 60%,#dda200);
  box-shadow:0 3px 12px -4px rgba(255,203,5,.9),inset 0 1px 0 rgba(255,255,255,.6)}
.lvnext .n span{display:block;transform:skewX(11deg)}
.lvnext .t{flex:1;min-width:0;font-size:13px;color:#dfe4ff;font-weight:600}
.lvnext .t span{display:block;font-size:10px;color:#8089b8;margin-top:2px;font-weight:500}
.lvnext .c{flex:none;font-family:'Bebas Neue';font-size:21px;color:#3ff08a;
  font-variant-numeric:tabular-nums;text-shadow:0 0 18px rgba(47,213,115,.5)}

/* The lining is the SAME slab as the rows above — same gradient, same rim, same
   inner highlight — so the case reads as one more panel in the set rather than
   a different material dropped into the middle of the screen. It was plum, then
   white; white lit up the bottom third of the panel and pulled every eye to the
   least important section. */
.lvcase{position:relative;padding:18px 15px 16px;border-radius:14px;overflow:hidden;
  background:linear-gradient(100deg,#232a4d,#161c37 60%,#121730);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.09),inset 0 0 0 1px rgba(255,255,255,.06),
    0 6px 18px rgba(0,0,0,.5)}
/* the felt nap */
.lvcase::before{content:'';position:absolute;inset:0;pointer-events:none;opacity:.5;
  background:
    repeating-linear-gradient(51deg,rgba(255,255,255,.03) 0 1px,transparent 1px 4px),
    repeating-linear-gradient(129deg,rgba(0,0,0,.22) 0 1px,transparent 1px 5px)}
/* Twenty badges, not ten. Still five across — the badge is the thing you want
   big enough to read, and six across on a 360px phone makes each one 52px,
   which is smaller than the art can survive. Four rows instead of two, and the
   panel already scrolls internally. The row gap comes down because at four rows
   the old 14px added up to a lot of empty felt. */
.lvslots{position:relative;display:grid;grid-template-columns:repeat(5,1fr);gap:11px 6px}
.lvslot{display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center}
/* pressed into the lining — dark hollow, light catching the bottom lip */
.lvwell{position:relative;width:100%;aspect-ratio:1;display:grid;place-items:center;
  border-radius:50%;background:rgba(0,0,0,.42);
  box-shadow:inset 0 3px 8px rgba(0,0,0,.9),inset 0 -1px 0 rgba(255,255,255,.07)}
.lvwell .bsvg{width:76%;height:76%;filter:drop-shadow(0 3px 7px rgba(0,0,0,.6))}
/* an earned badge lifts when you touch it — the locked ones have nothing to show */
.lvslot.got .lvwell{transition:transform .18s cubic-bezier(.2,1.3,.4,1)}
.lvslot.got:hover .lvwell{transform:scale(1.08) rotate(-3deg)}
.lvslot.got .lvwell{box-shadow:inset 0 3px 8px rgba(0,0,0,.7),0 0 20px -3px var(--b1,#fff)}
/* an unearned badge sits a shade above the hollow, so the silhouette still reads */
/* the locked look is drawn by badges.js, not by a CSS override */
.lvslot em{font-style:normal;font-size:8.5px;font-weight:800;letter-spacing:.08em;
  text-transform:uppercase;color:#8189b0;line-height:1.2}
.lvslot.got em{color:#eef1ff}
.lvslot.next em{color:var(--gold)}
.lvslot.next .lvwell{animation:lvnextpulse 2.4s ease-in-out infinite}
@keyframes lvnextpulse{
  0%,100%{box-shadow:inset 0 3px 8px rgba(0,0,0,.9),0 0 0 2px rgba(255,203,5,.3)}
  50%{box-shadow:inset 0 3px 8px rgba(0,0,0,.9),0 0 0 2px rgba(255,203,5,.95),
    0 0 22px -2px rgba(255,203,5,.75)}}
.lvslot .lvat{font-family:'Bebas Neue';font-size:11px;letter-spacing:.1em;color:#767ea6}
.lvslot.got .lvat{color:#3ff08a}
.lvslot.next .lvat{color:var(--gold)}
.lvcase-note{position:relative;margin-top:15px;text-align:center;font-size:10.5px;
  color:#8b93c4;line-height:1.5}
.lvcase-note b{color:var(--gold)}
@media(max-width:400px){.lvslots{gap:9px 4px}.lvslot em{font-size:7.6px}
  .lvslot .lvat{font-size:10px}}

/* ============================================================
   the screen deals itself in rather than appearing all at once
   ============================================================ */
@keyframes lvdealIn{from{opacity:0;transform:translateY(18px) scale(.97)}to{opacity:1;transform:none}}
@keyframes lvwipeL{from{opacity:0;transform:translateX(-26px)}to{opacity:1;transform:none}}
@keyframes lvwipeR{from{opacity:0;transform:translateX(26px)}to{opacity:1;transform:none}}
.lvpanel .lvcard{animation:lvdealIn .45s cubic-bezier(.2,1.15,.4,1) both}
.lvpanel .lvtab{animation:lvwipeL .4s cubic-bezier(.2,1.15,.4,1) both}
.lvpanel .lvtasks .lvtask{animation:lvwipeR .4s cubic-bezier(.2,1.15,.4,1) both}
.lvpanel .lvtasks .lvtask:nth-child(1){animation-delay:.14s}
.lvpanel .lvtasks .lvtask:nth-child(2){animation-delay:.2s}
.lvpanel .lvtasks .lvtask:nth-child(3){animation-delay:.26s}
.lvpanel .lvbonus{animation:lvwipeR .4s cubic-bezier(.2,1.15,.4,1) .32s both}
.lvpanel .lvnext{animation:lvwipeR .4s cubic-bezier(.2,1.15,.4,1) .44s both}
.lvpanel .lvcase{animation:lvdealIn .45s cubic-bezier(.2,1.15,.4,1) .52s both}
.lvpanel .lvtab:nth-of-type(1){animation-delay:.08s}

/* ============================================================
   6 · THE LEVEL-UP — the machine drops a ball down the chute
   ============================================================ */
#lvUpOverlay{position:fixed;inset:0;z-index:200;display:none;
  align-items:center;justify-content:center;padding:22px;cursor:pointer;
  background:radial-gradient(115% 85% at 50% 42%,rgba(26,18,4,.9),rgba(4,6,14,.97));
  backdrop-filter:blur(10px)}
#lvUpOverlay.show{display:flex;animation:ffade .2s ease}
.lvup{position:relative;width:100%;max-width:330px;text-align:center}

/* THE DELIVERY PORT.
   The first version was a 26px bar above the stage, and the ball fell straight
   past it — so nothing was ever coming OUT of anything, which is the entire
   point. A chute is a hole. That means two things have to be true:

     · it needs DEPTH — a cavity with a black interior, a bezel around it and a
       flap on a hinge, not a rectangle with a line under it
     · the ball has to be genuinely HIDDEN inside the machine until it emerges,
       which is what .lvclip does — it crops everything above the port's mouth,
       so the ball exists but cannot be seen until it clears the lip

   The port also sits ABOVE the ball in z-order, so the ball slides out from
   underneath its bottom edge instead of floating in front of it. */
.lvport{position:relative;z-index:4;width:172px;height:58px;margin:0 auto -8px;
  padding:6px;border-radius:13px 13px 7px 7px;perspective:460px;
  background:linear-gradient(180deg,#333c63,#232a49 38%,#161b32);
  box-shadow:0 8px 22px rgba(0,0,0,.65),inset 0 1px 0 rgba(255,255,255,.13),
    inset 0 -2px 0 rgba(0,0,0,.55)}
/* screws in the fascia, because machines have fasteners */
.lvport::before,.lvport::after{content:'';position:absolute;top:4px;width:4px;height:4px;
  border-radius:50%;background:radial-gradient(circle at 35% 30%,#5b658f,#1b2038);
  box-shadow:0 1px 0 rgba(255,255,255,.1)}
.lvport::before{left:5px}.lvport::after{right:5px}

/* the cavity — pure black at the top so it reads as depth, not as a dark panel */
.lvhole{position:relative;height:100%;border-radius:7px;overflow:hidden;
  background:linear-gradient(180deg,#000 0%,#04050b 55%,#0a0d1a 100%);
  box-shadow:inset 0 8px 16px rgba(0,0,0,1),inset 0 0 0 1px rgba(0,0,0,.9),
    inset 0 -1px 0 rgba(255,255,255,.07)}
/* the rubber flap, hinged along its top edge, kicked outward by the ball */
.lvflap{position:absolute;inset:1px;transform-origin:50% 0;border-radius:6px;
  background:linear-gradient(180deg,#464f7d,#2b3355 55%,#1d2340);
  box-shadow:0 5px 12px rgba(0,0,0,.7),inset 0 1px 0 rgba(255,255,255,.15),
    inset 0 -3px 6px rgba(0,0,0,.4);
  animation:lvflap 1.05s cubic-bezier(.3,.9,.4,1) both}
/* the moulded grip bar across it */
.lvflap::after{content:'';position:absolute;left:24%;right:24%;top:52%;height:5px;
  border-radius:3px;background:linear-gradient(180deg,#1a1f38,#39406a);
  box-shadow:inset 0 1px 0 rgba(0,0,0,.6),0 1px 0 rgba(255,255,255,.09)}
@keyframes lvflap{
  0%{transform:rotateX(0)}
  11%{transform:rotateX(-84deg)}      /* punched open */
  58%{transform:rotateX(-79deg)}      /* held while the ball clears */
  86%{transform:rotateX(-7deg)}
  100%{transform:rotateX(0)}}         /* and swings shut behind it */
/* the lit lip at the mouth — this is what says "something comes out here" */
.lvlip{position:absolute;left:10px;right:10px;bottom:-2px;height:3px;border-radius:3px;
  background:linear-gradient(90deg,transparent,var(--gold) 22%,#fff 50%,var(--gold) 78%,transparent);
  box-shadow:0 0 16px rgba(255,203,5,.95),0 0 34px rgba(255,203,5,.5);
  animation:lvlip 1.1s ease both}
@keyframes lvlip{0%{opacity:.2;transform:scaleX(.3)}
  14%{opacity:1;transform:scaleX(1)}
  70%{opacity:1}100%{opacity:.45}}

.lvstage{position:relative;height:216px;margin:0 auto;display:grid;place-items:center}
/* Crops everything above the port's mouth. ONLY the falling ball goes in here —
   the flash, the sparks and the badge sit outside it, or they'd be cropped too
   at the exact moment they're supposed to burst past the frame. */
.lvclip{position:absolute;inset:0;overflow:hidden;display:grid;place-items:center;
  border-radius:0 0 14px 14px}

/* THE BALL, in three nested layers — one per phase.
   This was one element with three animations on it, and it did not work: when
   several animations on an element target the SAME property the last one wins,
   and lvwobble had fill-mode `both` with a delay, so its backwards fill pinned
   rotate(0) from frame zero and erased the drop and the bounce completely. The
   ball just sat there and wobbled.

   So: .lvfall owns the fall, .lvhop owns the squash and the hops, .lvball owns
   the wobble. Three elements, one transform each, nothing to fight over. The
   delayed ones use `forwards` rather than `both` — `both` would apply their
   first keyframe during the delay, which is how the squash would show up before
   the ball had landed. */
.lvfall{animation:lvdrop .46s cubic-bezier(.55,0,.85,.6) .12s both}
.lvhop{animation:lvbounce .44s cubic-bezier(.3,0,.4,1) .58s forwards}
.lvball{position:relative;width:100px;height:100px;transform-origin:50% 100%;
  animation:lvwobble .85s ease-in-out 1.02s forwards}

/* the contact shadow — it's what makes the floor exist, and squashing it wide
   on impact is most of why the bounce reads as weight rather than as movement */
.lvshadow{position:absolute;left:50%;bottom:16px;width:96px;height:13px;margin-left:-48px;
  border-radius:50%;background:radial-gradient(ellipse at center,rgba(0,0,0,.75),transparent 72%);
  filter:blur(3px);pointer-events:none}
@keyframes lvshadow2{
  0%{transform:scaleX(1.3) scaleY(.8);opacity:.9}
  30%{transform:scale(.55);opacity:.4}
  60%{transform:scaleX(1.15) scaleY(.9);opacity:.8}
  80%{transform:scale(.85);opacity:.6}
  100%{transform:scale(1);opacity:.8}}
/* and it goes when the ball cracks open — the third animation is last in the
   list, so it wins the opacity for its own active phase and nothing else */
.lvshadow{animation:lvdrop2 .46s cubic-bezier(.55,0,.85,.6) .12s both,
                    lvshadow2 .44s cubic-bezier(.3,0,.4,1) .58s forwards,
                    lvopen .26s ease 1.87s forwards}
@keyframes lvdrop2{from{transform:scale(.35);opacity:.15}to{transform:scale(1);opacity:.85}}
/* starts fully above the crop line, so it is inside the machine, not invisible */
@keyframes lvdrop{from{transform:translateY(-205px) scale(.82)}to{transform:translateY(0) scale(1)}}
@keyframes lvbounce{
  0%{transform:translateY(0) scaleY(.68) scaleX(1.3)}    /* the squash on impact */
  16%{transform:translateY(-6px) scaleY(1.14) scaleX(.9)} /* and the stretch off it */
  38%{transform:translateY(-46px) scaleY(1.06) scaleX(.95)}
  58%{transform:translateY(0) scaleY(.82) scaleX(1.15)}
  74%{transform:translateY(-14px) scaleY(1.04) scaleX(.97)}
  90%{transform:translateY(0) scaleY(.94) scaleX(1.04)}
  100%{transform:translateY(0) scale(1)}}
/* the catch wobble. This is the sound everyone hears in their head. */
@keyframes lvwobble{
  0%,100%{transform:rotate(0)}
  10%{transform:rotate(-15deg)} 22%{transform:rotate(13deg)}
  34%{transform:rotate(-10deg)} 46%{transform:rotate(8deg)}
  58%{transform:rotate(-5deg)}  70%{transform:rotate(3deg)}
  84%{transform:rotate(-1.5deg)}}
@keyframes lvopen{to{opacity:0}}
.lvball>b{position:absolute;left:0;right:0;display:block;overflow:hidden}
.lvb-t{top:0;height:50%;border-radius:52px 52px 0 0;
  background:radial-gradient(circle at 32% 24%,#ff8a6b,var(--red) 46%,#8d1b04 100%);
  box-shadow:inset 0 -3px 0 rgba(0,0,0,.55);
  animation:lvtop .42s cubic-bezier(.3,.7,.3,1) 1.87s both}
.lvb-b{bottom:0;height:50%;border-radius:0 0 52px 52px;
  background:radial-gradient(circle at 32% 78%,#fff,#dfe3f2 52%,#9aa0bd 100%);
  box-shadow:inset 0 3px 0 rgba(0,0,0,.3);
  animation:lvbot .42s cubic-bezier(.3,.7,.3,1) 1.87s both}
/* They throw mostly SIDEWAYS now. Straight up ran the top half into the crop
   line at the port's mouth and sliced it in half mid-flight; there is plenty of
   room left and right, and none above. */
@keyframes lvtop{to{transform:translate(-62px,-44px) rotate(-42deg);opacity:0}}
@keyframes lvbot{to{transform:translate(56px,52px) rotate(28deg);opacity:0}}
.lvb-band{top:calc(50% - 5px);height:10px;background:#0c0d18;border-radius:0;
  box-shadow:0 0 0 1px rgba(0,0,0,.6)}
.lvb-btn{top:calc(50% - 15px);left:calc(50% - 15px);right:auto;width:30px;height:30px;
  border-radius:50%;background:radial-gradient(circle at 36% 32%,#fff,#c9cee2);
  border:4px solid #0c0d18;box-shadow:0 0 14px rgba(255,255,255,.55)}
.lvb-band,.lvb-btn{animation:lvopen .22s ease 1.87s both}

/* the flash when it cracks */
.lvflash{position:absolute;left:50%;top:50%;width:26px;height:26px;margin:-13px 0 0 -13px;
  border-radius:50%;background:#fff;pointer-events:none;opacity:0;
  box-shadow:0 0 60px 26px rgba(255,255,255,.95),0 0 120px 60px rgba(255,203,5,.75);
  animation:lvflash .5s ease-out 1.86s both}
@keyframes lvflash{0%{opacity:0;transform:scale(.2)}18%{opacity:1;transform:scale(1)}
  100%{opacity:0;transform:scale(3.4)}}
/* sparks thrown outward, each on its own angle */
.lvsparks{position:absolute;inset:0;pointer-events:none}
.lvsparks i{position:absolute;left:50%;top:50%;width:5px;height:5px;border-radius:50%;
  background:var(--gold);box-shadow:0 0 10px var(--gold);
  animation:lvspark .78s cubic-bezier(.15,.75,.35,1) 1.9s both}
@keyframes lvspark{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.4)}
  15%{opacity:1}
  100%{opacity:0;transform:translate(calc(-50% + var(--dx)),calc(-50% + var(--dy))) scale(.3)}}

/* the badge rises out of the open ball, spinning, and lands */
.lvprize{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  display:flex;flex-direction:column;align-items:center;gap:9px;
  animation:lvrise .72s cubic-bezier(.18,1.35,.4,1) 2.02s both}
@keyframes lvrise{
  0%{opacity:0;transform:translate(-50%,-24%) scale(.2) rotate(-190deg)}
  62%{opacity:1;transform:translate(-50%,-56%) scale(1.14) rotate(8deg)}
  100%{opacity:1;transform:translate(-50%,-50%) scale(1) rotate(0)}}
.lvprize .bsvg{width:96px;height:96px;filter:drop-shadow(0 12px 28px rgba(0,0,0,.75))}
.lvprize .lvnum{font-family:'Bebas Neue';font-size:19px;letter-spacing:.14em;color:var(--gold);
  text-shadow:0 0 16px rgba(255,203,5,.8)}
/* a slow halo behind the badge — light, not a sunburst */
.lvhalo{position:absolute;left:50%;top:50%;width:220px;height:220px;margin:-110px 0 0 -110px;
  border-radius:50%;pointer-events:none;
  background:radial-gradient(circle,rgba(255,203,5,.4),rgba(255,203,5,.09) 46%,transparent 68%);
  animation:lvhalo 1s ease-out 2s both}
@keyframes lvhalo{0%{opacity:0;transform:scale(.4)}40%{opacity:1;transform:scale(1.05)}
  100%{opacity:.72;transform:scale(1)}}

.lvup-t{margin-top:2px;font-family:'Bebas Neue';font-size:38px;letter-spacing:.05em;color:#fff;
  text-shadow:0 3px 22px rgba(0,0,0,.9);opacity:0;animation:lvfade .4s ease 2.42s forwards}
.lvup-s{font-size:12px;color:#bcc3ea;margin-top:1px;opacity:0;
  animation:lvfade .4s ease 2.52s forwards}
/* THE PAYOUT.
   Three goes at this, and the third one still had COINS in it. This game's
   money has never been coins — it is a wallet pill in the header reading
   $5,614.10, and every gain anywhere else in the app is GREEN: .walletdelta.up
   is #3ff08a, the sell buttons are green, sell prices are green. Gold is the XP
   colour. So a gold pile of coins was inventing a currency the game doesn't
   have, in the colour that means something else.

   Money looks like money looks everywhere else in here. Green figure, the
   wallet glyph from the header, the balance it landed in. No new nouns. */
/* PADDING, not a margin on the child. .lvpay has no padding or border, so a
   margin-top on .lvamt collapses out THROUGH the parent and shifts .lvpay
   itself — and the rule is position:absolute top:0 on .lvpay, so it shifts by
   exactly the same amount. The two move together and the gap never opens, at
   any value. Padding does not collapse, and top:0 resolves against the padding
   box, so the rule stays put while the figure drops away from it. */
.lvpay{position:relative;width:100%;max-width:250px;margin:24px auto 0;
  padding-top:34px;text-align:center}
/* a rule in the money colour, drawing itself open */
.lvpay::before{content:'';position:absolute;left:14%;right:14%;top:0;height:1px;
  background:linear-gradient(90deg,transparent,rgba(63,240,138,.55) 28%,
    rgba(214,255,232,.9) 50%,rgba(63,240,138,.55) 72%,transparent);
  box-shadow:0 0 15px rgba(47,213,115,.5);
  transform:scaleX(.15);opacity:0;
  animation:lvrule .5s cubic-bezier(.2,.9,.3,1) 2.45s forwards}
@keyframes lvrule{to{transform:scaleX(1);opacity:1}}

/* the figure lands rather than fades — it's the one number that matters.
   Bebas has a tall cap height and line-height .9 crops the box tight to it, so
   the glyph tops sat almost on the rule; the 34px glow was closing the rest of
   the gap. It needs more air than the type metrics suggest. */
.lvamt{margin-top:0;font-family:'Bebas Neue';font-size:48px;line-height:.9;
  color:#3ff08a;letter-spacing:.015em;font-variant-numeric:tabular-nums;
  text-shadow:0 0 26px rgba(47,213,115,.45),0 2px 0 rgba(0,0,0,.55);
  opacity:0;animation:lvamt .5s cubic-bezier(.2,1.35,.4,1) 2.55s both}
@keyframes lvamt{
  0%{opacity:0;transform:translateY(15px) scale(.88)}
  60%{opacity:1;transform:translateY(-4px) scale(1.05)}
  100%{opacity:1;transform:none}}

/* where it went, said with the header's own glyph instead of a sentence */
.lvwal{margin-top:8px;display:inline-flex;align-items:center;gap:8px;
  font-size:10px;font-weight:800;letter-spacing:.19em;text-transform:uppercase;
  color:#666f99;opacity:0;animation:lvfade .4s ease 3.02s forwards}
.lvwal svg{width:16px;height:16px;flex:none;opacity:.85}
.lvwal b{color:#c2c9f0;font-weight:800;letter-spacing:.05em;font-size:12.5px;
  font-variant-numeric:tabular-nums}

/* a perk is not money — thin caps, gold, no border */
.lvperk{margin-top:16px;display:flex;align-items:center;justify-content:center;gap:9px;
  font-size:9.5px;font-weight:800;letter-spacing:.26em;text-transform:uppercase;
  color:#9aa2cf;opacity:0;animation:lvfade .4s ease 3.24s forwards}
.lvperk .pk{width:5px;height:5px;flex:none;transform:rotate(45deg);background:var(--gold);
  box-shadow:0 0 9px rgba(255,203,5,.95)}
.lvperk b{color:var(--gold);font-weight:800;letter-spacing:.26em}

.lvup-tap{margin-top:17px;font-size:10px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--dim2);opacity:0;animation:lvfade .5s ease 3.62s forwards}
@keyframes lvfade{to{opacity:1}}
/* tapping through skips straight to the end rather than closing on a half-built
   screen — you'll see this a hundred times and you should be able to cut it */
#lvUpOverlay.skip *{animation-duration:.01s!important;animation-delay:0s!important}
#lvUpOverlay.skip .lvball,#lvUpOverlay.skip .lvflash,#lvUpOverlay.skip .lvsparks{display:none}

/* ============================================================
   7 · THE BADGE SHEET — tap an earned badge and hold it
   ------------------------------------------------------------
   Same makeTiltable engine the card reveal uses, so a badge behaves exactly the
   way a card does when you tap it: leans toward your finger, and the light
   moves with it. The engine hands us --mx/--my (where the pointer is) and --pd
   (how far from centre), and every effect here is driven off those three rather
   than off a canned animation — which is what makes it feel attached to your
   hand instead of played back at you.
   ============================================================ */
#badgeSheet{position:fixed;inset:0;z-index:175;display:none;
  align-items:center;justify-content:center;padding:26px 20px;
  background:radial-gradient(120% 90% at 50% 40%,rgba(18,14,34,.86),rgba(4,6,14,.95));
  backdrop-filter:blur(10px)}
#badgeSheet.show{display:flex;animation:ffade .18s ease}
.bsheet{position:relative;width:100%;max-width:340px;text-align:center}

/* the stage owns the perspective; the tilt element owns the transform */
.bs-stage{position:relative;width:100%;max-width:300px;margin:0 auto;
  perspective:900px;touch-action:pan-y;cursor:grab}
.bs-stage:active{cursor:grabbing}
.bs-tilt{position:relative;transform-style:preserve-3d;
  transition:transform .35s cubic-bezier(.2,.9,.3,1);
  animation:bsIn .5s cubic-bezier(.2,1.2,.4,1) both}
@keyframes bsIn{from{opacity:0;transform:scale(.72) rotateX(18deg)}to{opacity:1;transform:none}}
.bs-face{position:relative;aspect-ratio:1;border-radius:50%}
.bs-face .bsvg{width:100%;height:100%;
  filter:drop-shadow(0 22px 40px rgba(0,0,0,.75))}

/* the specular — a soft blob that sits under your finger */
.bs-face::after{content:'';position:absolute;inset:-4%;pointer-events:none;border-radius:50%;
  background:radial-gradient(closest-side circle at var(--mx,50%) var(--my,50%),
    rgba(255,255,255,.5),rgba(255,255,255,.14) 42%,transparent 68%);
  opacity:calc(.18 + var(--pd,0) * .55);mix-blend-mode:screen;transition:opacity .2s}
/* a faint prism edge that only shows once it's actually tipped — a rainbow
   sitting there at rest reads as decoration, one that arrives as you lean it
   reads as foil */
.bs-face::before{content:'';position:absolute;inset:-4%;pointer-events:none;border-radius:50%;
  background:linear-gradient(calc(90deg + var(--px,0) * 120deg),
    rgba(255,92,168,.35),rgba(125,232,255,.3) 38%,rgba(255,203,5,.32) 68%,rgba(176,108,255,.3));
  opacity:calc(var(--pd,0) * .32);mix-blend-mode:color-dodge;transition:opacity .2s}
/* the light pool moves opposite the lean, so the badge feels lifted off it */
.bs-pool{position:absolute;left:50%;bottom:-22px;width:62%;height:18px;
  transform:translateX(calc(-50% + var(--px,0) * -34px));
  border-radius:50%;background:radial-gradient(ellipse,rgba(0,0,0,.62),transparent 70%);
  filter:blur(7px);pointer-events:none;transition:transform .18s}

.bs-name{margin-top:34px;font-family:'Bebas Neue';font-size:36px;letter-spacing:.04em;
  color:#fff;line-height:1;text-shadow:0 3px 22px rgba(0,0,0,.85)}
.bs-kind{display:inline-block;margin-top:9px;padding:3px 13px 2px;transform:skewX(-11deg);
  font-family:'Bebas Neue';font-size:13px;letter-spacing:.17em;color:#2a1e02;
  background:linear-gradient(180deg,#ffe884,var(--yellow) 60%,#dda200);
  box-shadow:0 4px 12px -4px rgba(255,203,5,.9)}
.bs-kind span{display:inline-block;transform:skewX(11deg)}
.bs-earned{margin-top:17px;font-size:9.5px;font-weight:800;letter-spacing:.26em;
  text-transform:uppercase;color:#6d75a0}
.bs-earned b{color:#3ff08a;letter-spacing:.26em}
.bs-x{position:absolute;top:-14px;right:-4px;width:36px;height:36px;border-radius:50%;
  cursor:pointer;border:2px solid rgba(255,255,255,.2);background:rgba(8,10,22,.75);
  color:#e6eaff;display:grid;place-items:center;transition:.15s}
.bs-x svg{width:15px;height:15px}
.bs-x:hover{background:var(--red);border-color:var(--red2);transform:rotate(90deg)}
@media(max-width:400px){
  .bs-stage{max-width:250px}
  .bs-name{font-size:31px;margin-top:28px}
}
/* an earned badge in the case says it can be picked up */
.lvslot.got .lvwell{cursor:pointer}


/* ================= MACHINE RESTOCKED =================
   The second beat after a level-up that opens a ladder tier. Deliberately calm
   next to the level-up card it follows — that one is confetti and a shockwave,
   and following it with more of the same reads as noise. This is a list you are
   meant to READ, because which sets you just got is the only part you care
   about, so it is quiet, dark and legible. */
.lvunlock{position:relative;width:min(400px,92vw);max-height:88vh;overflow:auto;
  background:linear-gradient(180deg,#171b34,#0e1226);
  border:1px solid #2f3660;border-radius:18px;padding:20px 18px 16px;text-align:center;
  box-shadow:0 24px 60px -12px #000,0 0 0 1px #ffffff0a inset;
  animation:lvupop .34s cubic-bezier(.2,1.2,.35,1) both}
@keyframes lvupop{from{opacity:0;transform:translateY(14px) scale(.96)}to{opacity:1;transform:none}}
.lvu-kicker{font-size:9px;font-weight:800;letter-spacing:.26em;color:#3ff08a}
.lvu-ttl{font-family:'Bebas Neue';font-size:32px;letter-spacing:.03em;color:#fff;
  line-height:1.05;margin-top:6px;text-shadow:0 2px 14px #3ff08a3d}
.lvu-sub{font-size:11.5px;color:#8b93c4;margin-top:4px}
/* auto-fit, because a tier is anywhere from four sets to eleven and a fixed
   column count leaves either a ragged last row or a lot of empty card */
.lvu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(88px,1fr));
  gap:8px;margin:15px 0 4px}
.lvu-set{background:#0a0e20;border:1px solid #262c50;border-radius:10px;
  padding:9px 6px 7px;display:flex;flex-direction:column;align-items:center;gap:5px;
  min-height:64px;justify-content:center}
.lvu-set img{max-width:74%;max-height:26px;object-fit:contain}
.lvu-set em{font-style:normal;font-size:8.4px;font-weight:700;color:#9aa2cc;
  line-height:1.25;letter-spacing:.02em}
/* a set with no logo art still has to look intentional rather than broken */
.lvu-set.nologo em{font-size:10px;color:#dfe4ff}
.lvu-go{margin-top:14px;width:100%}
.lvunlock .lvup-tap{margin-top:10px;font-size:9.5px;letter-spacing:.18em;color:#5f668f}
@media(max-width:400px){
  .lvu-grid{grid-template-columns:repeat(auto-fit,minmax(76px,1fr));gap:6px}
  .lvu-ttl{font-size:27px}}
