The climb: the arcade runs the whole earned ladder, the tempo follows the territory, the court is yours

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-29 16:24:43 +02:00
1 parent 041827c4dc
commit 689550d2d5
7 files changed
+104 -52

No files matched your search

+40 -26
View File
@@ -303,9 +303,24 @@ swatches('kitRow', 'kitName', KITS, 'kit', () => pickedKit, id => pickedKit = id
swatches('skinRow', 'skinName', SKINS, 'skin', () => pickedSkin, id => pickedSkin = id);
refreshLook();
// THE LEAGUE PANEL (the leagues): the setup's league-mode face — your league writ large and the
// band's roster with your all-time felled counts (stats' per-foe tallies, key 'L'+index). The
// identity block this replaces still shows for every non-league mode; the card owns it anyway.
// THE CLIMB PANEL: the arcade's setup face — the marathon's length (grown by every promotion)
// and your best run. Reuses the league panel's furniture; the foes row stays empty (up to 18
// tiles is a wall, and the training picker already shows the roster face-up).
function buildClimbPanel() {
const lg = leagueOf(profile.prof.lg);
document.getElementById('lgBadge').src = `assets/badge_l${lg.id}.png?v=${SPRV}`;
const name = document.getElementById('lgName');
name.textContent = 'THE CLIMB';
name.style.color = '';
const best = stats.read('all', undefined, stats.MODE_MATCH).longest;
document.getElementById('lgSub').textContent =
`THE CLUB FROM THE BOTTOM — ${lg.band[1]} STAND` + (best ? ` · BEST RUN: ${best} FELLED` : '');
document.getElementById('lgFoes').innerHTML = '';
}
// THE LEAGUE PANEL (the leagues): the cup's setup face — your league writ large and the band's
// roster with your all-time felled counts (stats' per-foe tallies, key 'L'+index). The identity
// block these panels replace still shows for every other mode; the card owns it anyway.
function buildLeaguePanel() {
const lg = leagueOf(profile.prof.lg);
const badge = document.getElementById('lgBadge');
@@ -336,27 +351,26 @@ function buildLeaguePanel() {
// On the LEAGUE MODES the player block steps aside for the league panel (Oliver: the identity is
// on the card now — the setup should show the league and who of it you have already beaten).
function syncSetup() {
const leagueMode = pickedMode === 'zombie' || pickedMode === 'cup';
document.getElementById('who').style.display = leagueMode ? 'none' : 'flex';
document.getElementById('lgPanel').style.display = leagueMode ? 'block' : 'none';
document.getElementById('s01').textContent = leagueMode ? '01 · YOUR LEAGUE' : '01 · PLAYER';
if (leagueMode) buildLeaguePanel();
const cupMode = pickedMode === 'cup', climbMode = pickedMode === 'zombie';
document.getElementById('who').style.display = cupMode || climbMode ? 'none' : 'flex';
document.getElementById('lgPanel').style.display = cupMode || climbMode ? 'block' : 'none';
document.getElementById('s01').textContent =
cupMode ? '01 · YOUR LEAGUE' : climbMode ? '01 · THE CLIMB' : '01 · PLAYER';
if (cupMode) buildLeaguePanel(); else if (climbMode) buildClimbPanel();
if (!courtUnlocked(picked)) picked = leagueOf(profile.prof.lg).court;
// ...and the COURT section stops pretending to be a choice on the league modes (Oliver: a
// league HAS its court — start() has forced it all along): only the league's own row shows,
// ringed, as the venue. Every other mode gets the full pickable list. This runs AFTER the
// generic repaint below would, so it has the last word on .on/.lock.
// the COURT section: the CUP is the league fixture — one ringed row, the home venue, no choice
// (Oliver: a league HAS its court). The climb and every other mode keep the full pickable list.
const homeCourt = leagueOf(profile.prof.lg).court;
document.getElementById('s02').innerHTML = leagueMode
document.getElementById('s02').innerHTML = cupMode
? '02 · HOME COURT<span>SET BY YOUR LEAGUE</span>' : '02 · COURT';
courtList.classList.toggle('fixed', leagueMode);
courtList.classList.toggle('fixed', cupMode);
for (const el of courtList.children) {
const c = COURTS.find(x => x.id === el.dataset.id), l = courtLeague(el.dataset.id);
const open = courtUnlocked(el.dataset.id);
el.classList.toggle('lock', !open && !leagueMode);
el.classList.toggle('on', leagueMode ? el.dataset.id === homeCourt
: open && el.dataset.id === picked);
el.style.display = leagueMode && el.dataset.id !== homeCourt ? 'none' : '';
el.classList.toggle('lock', !open && !cupMode);
el.classList.toggle('on', cupMode ? el.dataset.id === homeCourt
: open && el.dataset.id === picked);
el.style.display = cupMode && el.dataset.id !== homeCourt ? 'none' : '';
// the sub line carries the lock and names the price — the tile stays visible, as a goal should
el.querySelector('i').textContent = open ? c.sub : 'LOCKED — REACH ' + l.name;
}
@@ -440,7 +454,7 @@ for (const c of COURTS) {
// Tapping a tile SELECTS it and nothing more. It used to start the match on the spot, which gave
// you no way to look at the three of them and no way back out of the screen.
b.addEventListener('click', () => {
if (pickedMode === 'zombie' || pickedMode === 'cup') return; // the league picks the venue
if (pickedMode === 'cup') return; // the cup is the league fixture — the league picks
if (!courtUnlocked(c.id)) return; // locked: the row itself says which league opens it
picked = c.id;
for (const el of courtList.children) el.classList.toggle('on', el === b);
@@ -541,7 +555,7 @@ function buildTrain() {
const top = Math.min(LADDER, leagueOf(profile.prof.lg).band[1]);
for (let n = 1; n <= top; n++) {
const f = matchFoe(n), b = document.createElement('button');
b.innerHTML = `<img src="${foeArt(f)}" alt=""><b>${f.name}</b><i>RUNG ${n}</i>`;
b.innerHTML = `<img src="${foeArt(f)}" alt=""><b>${f.name}</b><i>No. ${n}</i>`;
b.addEventListener('click', () => { trainRung = n; start('train'); });
trainList.appendChild(b);
}
@@ -669,8 +683,8 @@ const boardFor = m => m === 'zombie' || m === 'cup' || m === 'train' ? stats.MOD
function summary() {
const t = TOURS.find(x => x.id === pickedTour) || TOURS[0];
const lg = leagueOf(profile.prof.lg);
const leagueMode = pickedMode === 'zombie' || pickedMode === 'cup';
const c = COURTS.find(x => x.id === (leagueMode ? lg.court : picked)) || COURTS[0];
const cupMode = pickedMode === 'cup';
const c = COURTS.find(x => x.id === (cupMode ? lg.court : picked)) || COURTS[0];
const m = MODES.find(x => x.id === pickedMode) || MODES[0];
// the mark to beat is the PICKED MODE's own, because the three of them do not measure the same
// thing — see the record-shape note in stats.endRun. The cup banks through endRun as MODE_MATCH
@@ -681,7 +695,7 @@ function summary() {
const name = m.id === 'classic'
? m.name + ' · ' + (DIFFS.find(d => d.id === pickedDiff) || DIFFS[1]).name : m.name;
document.getElementById('sSum').innerHTML =
`<span>${name} · ${t.name.replace(' SINGLES', '')} · ${c.name}${leagueMode
`<span>${name} · ${t.name.replace(' SINGLES', '')} · ${c.name}${cupMode
? ' · LEAGUE ' + lg.id : ''}</span>`
// training banks nothing, so there is no mark to beat and the footer must not invent one
+ `<span>${m.id === 'train' ? 'NOTHING AT STAKE — JUST REPS'
@@ -718,9 +732,9 @@ function start(modeId = pickedMode) {
if (!menuUp()) return;
selectTour(pickedTour);
selectKit(pickedKit); selectSkin(pickedSkin);
// THE LEAGUES: a league match is played on the league's own court — the pick stays yours in
// classic, gore, netplay and training (among the unlocked courts).
selectCourt(modeId === 'zombie' || modeId === 'cup' ? leagueOf(profile.prof.lg).court : picked);
// THE LEAGUES: the CUP is the league fixture and plays the league's own court; every other
// mode — the climb included, it is an exhibition — plays your pick (among the unlocked).
selectCourt(modeId === 'cup' ? leagueOf(profile.prof.lg).court : picked);
menuDuck(true); // the theme belongs to the menus — hand the court over to the ambience
intro.hidden = true; ambient(); voice('quiet_please'); // low atmosphere bed starts with the first gesture
// ...and the mode the setup screen picked. THREE LINES, because that is genuinely all the modes