diff --git a/PROJECT.md b/PROJECT.md
index a028221..355c2a6 100644
--- a/PROJECT.md
+++ b/PROJECT.md
@@ -491,6 +491,28 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
`RETURNED`, `DOUBLE KILL`) is a footnote to something you already watched happen. Sharing the loud
FACE with the milestone cards is right; sharing their SIZE made every footnote read as a milestone.
+## Session log: THE LEAGUES — the polish round: the ribbon, the glow, the count-up (2026-08-29, same day, STAGING ONLY)
+
+Oliver's retest ("schon viel besser") with five polish items, all built:
+
+- **Every chevron is UPRIGHT now.** Bangers is drawn slanted, so `‹`/`›` read as italic on every
+ screen (his "an vielen Stellen"). All `.chev` buttons and the plaque's arrow use Silkscreen's
+ straight ASCII `<`/`>` instead — the announcement face keeps its job, arrows stop leaning.
+- **The plaque wears rounded corners (8px) and real padding** (12/14 against 8/10).
+- **THE WAY-UP RIBBON**: the tag became a banderole — red field, thick white Silkscreen with a
+ light shadow, rotated -45° over the poster's top-left corner, ends clipped by the poster's own
+ `overflow: hidden` (that clip is what sells the wrap). And it shows on BOTH picker levels: the
+ top-level ZOMBIE TENNIS poster carries it too (`tag` on the MENU row), so the click path to the
+ promotion is marked from the first screen.
+- **Poster captions grew**: titles 12 → 15 bold, sub lines 9 → 10 and a brighter grey — "viel
+ Text in sehr kleiner Schrift" was true of every banner.
+- **The card performs its entrance**: earned badges and the current crest BREATHE a subtle gold
+ halo (`badgeGlow` keyframes, CSS-only — the parked menu loop is irrelevant to CSS animations;
+ the dark slots stay dead, which is their point), both bars sweep from empty (0.7s eased
+ transition, forced-reflow reset), and every number counts up fast — the XP line and all six
+ ledger figures through one `countUp` easer (setTimeout, the bracket's own no-rAF rule; timers
+ cancelled on rebuild so re-entry never races itself).
+
## Session log: THE LEAGUES — the plaque round: the meta speaks (2026-08-29, same day, STAGING ONLY)
Oliver's first screenshot review, four items, all built. The theme: the meta layer existed but
diff --git a/index.html b/index.html
index 3f094d3..2acacb1 100644
--- a/index.html
+++ b/index.html
@@ -209,8 +209,10 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
background: var(--solid);
padding-top: max(10px, env(safe-area-inset-top)); }
.hdr h2 { padding-left: 2px; }
+ /* UPRIGHT, not Bangers: the announcement face is drawn slanted, and a slanted arrow read as
+ italic on every screen. Silkscreen's ASCII < > are straight pixel arrows. */
.chev { width: 34px; height: 34px; flex: none; background: none; border: 2px solid var(--line);
- color: var(--acid); font: 400 20px Bangers, cursive; line-height: 1; cursor: pointer;
+ color: var(--acid); font: 700 14px Silkscreen, monospace; line-height: 1; cursor: pointer;
-webkit-tap-highlight-color: transparent; }
/* the BODY COUNT headline row: three boxed figures sharing one frame */
.figs { display: flex; border: 2px solid var(--line); background: var(--panel); }
@@ -291,6 +293,13 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
#cdBadges { display: flex; gap: 10px; align-items: flex-end; }
#cdBadges img { height: 46px; image-rendering: pixelated; }
#cdBadges img.dark { filter: grayscale(1) brightness(0.3); }
+ /* earned badges BREATHE — a subtle gold halo, CSS-only (the menu parks the game loop, CSS
+ animations do not care). The dark slots stay dead, which is the point of them. */
+ #cdBadges img:not(.dark), #cdBadge {
+ animation: badgeGlow 2.6s ease-in-out infinite; }
+ @keyframes badgeGlow {
+ 0%, 100% { filter: drop-shadow(0 0 2px rgba(232, 178, 58, 0.25)); }
+ 50% { filter: drop-shadow(0 0 7px rgba(232, 178, 58, 0.75)); } }
.rkbadge { height: 15px; margin-right: 6px; image-rendering: pixelated; flex: none; }
/* THE LEVEL STRIP: the one meta number over a thin bar, riding the splash foot. The fill colour
is the acid accent for now; the leagues will hand it the skill colour in phase 3. */
@@ -301,8 +310,9 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
level+league over the bar, and the standing GOAL LINE ("win the X cup to advance"), with a
chevron that says it opens. The splash stays two buttons + one plaque; the badge COLLECTION
stays on the card, one (now discoverable) tap away. */
- #plaque { display: flex; align-items: center; gap: 10px; width: 100%; margin: 0 0 10px;
- padding: 8px 10px; background: var(--solid); border: 2px solid var(--line);
+ #plaque { display: flex; align-items: center; gap: 12px; width: 100%; margin: 0 0 10px;
+ padding: 12px 14px; background: var(--solid); border: 2px solid var(--line);
+ border-radius: 8px;
cursor: pointer; text-align: left; -webkit-tap-highlight-color: transparent; }
#plBadge { height: 34px; flex: none; image-rendering: pixelated; }
#plBody { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
@@ -313,20 +323,27 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
letter-spacing: 1px; white-space: nowrap; }
#plGoal { font: 400 8px Silkscreen, monospace; font-style: normal; color: var(--acid);
letter-spacing: 1px; }
- #plaque em { flex: none; font: 400 22px Bangers, cursive; font-style: normal; color: var(--acid); }
+ #plaque em { flex: none; font: 700 16px Silkscreen, monospace; font-style: normal; color: var(--acid); }
#plRow s, #cdBar s, #cdCareer s { flex: 1; height: 6px; background: rgba(255,255,255,0.10);
border: 1px solid var(--line); text-decoration: none; display: block; }
#cdBar s { height: 8px; }
#cdCareer { display: flex; align-items: center; gap: 8px; }
#cdCareer u { font: 700 8px Silkscreen, monospace; color: var(--dim); text-decoration: none;
letter-spacing: 1px; }
- /* the mode picker's WAY-UP tag: the standing answer to "how do I advance", worn by the cup */
- .way { position: absolute; top: 8px; left: 8px; padding: 3px 6px; font: 700 8px Silkscreen, monospace;
- font-style: normal; color: var(--ink); background: var(--acid); letter-spacing: 1px;
- text-decoration: none; }
+ /* the WAY-UP RIBBON: the standing answer to "how do I advance", worn as a banderole wrapped
+ over the poster's corner (Oliver: bigger, red, thick white type, light shadow). The poster
+ clips its ends diagonally (overflow hidden + the -45deg rotation), which is what sells the
+ wrap. */
+ .way { position: absolute; top: 20px; left: -42px; width: 150px; transform: rotate(-45deg);
+ padding: 6px 0; font: 700 11px Silkscreen, monospace; font-style: normal;
+ text-align: center; color: #fff; background: var(--red);
+ border-top: 1px solid rgba(255,255,255,0.35); border-bottom: 1px solid #69201c;
+ text-shadow: 1px 1px 0 rgba(0,0,0,0.55); box-shadow: 0 2px 6px rgba(0,0,0,0.4);
+ letter-spacing: 1px; text-decoration: none; }
#plRow s i, #cdBar s i, #cdCareer s i { display: block; height: 100%; width: 0%;
background: linear-gradient(90deg, #3f9560, var(--acid) 55%, #e8b23a);
transition: width 0.4s ease; }
+ #cdBar s i, #cdCareer s i { transition: width 0.7s cubic-bezier(0.2, 0.7, 0.3, 1); }
/* KIT and SKIN: a row of colour squares, under a label that names whatever is selected. That is
the whole UI — the pick has no meters and no art to show, and a labelled tile per colour would
be five times the screen for a choice you make by looking at the colour. */
@@ -391,7 +408,8 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
a live answer to the header's question; tapping answers and advances. The only memory shown is
the LAST tag — an indicator, not a selection. A border that means "SERVE UP will use this"
exists solely on the final screen, where a confirm button actually follows. */
- #modeList button.mode, #modeSub button.mode { display: flex; align-items: flex-end; width: 100%; min-height: 96px;
+ #modeList button.mode, #modeSub button.mode { overflow: hidden; /* clips the WAY-UP ribbon's ends */
+ display: flex; align-items: flex-end; width: 100%; min-height: 96px;
margin: 0 0 8px; padding: 7px 9px; background-size: cover; background-position: center bottom;
image-rendering: pixelated; border: 2px solid var(--line); color: inherit; font: inherit;
text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent;
@@ -425,8 +443,8 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
#courtList img { width: 92px; height: 66px; display: block; image-rendering: pixelated; flex: none;
object-fit: cover; }
#courtList .cb, #modeList .cb, #modeSub .cb { flex: 1; min-width: 0; }
- #courtList b, #modeList b, #modeSub b { display: block; font: 400 12px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; }
- #courtList i, #modeList i, #modeSub i { display: block; margin: 3px 0 6px; font: 400 9px Silkscreen, monospace; color: var(--dim); font-style: normal; }
+ #courtList b, #modeList b, #modeSub b { display: block; font: 700 15px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; }
+ #courtList i, #modeList i, #modeSub i { display: block; margin: 4px 0 6px; font: 400 10px Silkscreen, monospace; color: #c9c4b8; font-style: normal; }
/* The two meters read straight off the court's own bounce numbers (see main.js), so they cannot
drift away from what the ball actually does on it. */
.meter { display: flex; align-items: center; gap: 6px; margin-top: 4px;
@@ -587,7 +605,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
LEVEL 1
- ›
+ >