The leagues, the plaque round: the meta speaks — the player plaque, the goal line, the way-up tag, the centred ceremony
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
787877bce6
commit
87ac3294bc
7 files changed
+124
-25
No files matched your search
+10
-3
@@ -666,7 +666,10 @@ function matchOpponent() {
|
||||
const cup = G.match.cup;
|
||||
showModal({ title: zb.foe.name, color: UI.hot, icon: 'z_' + zb.fam + 'head', iconH: 64,
|
||||
body: [zb.foe.line].concat(cup ? [cup.rounds[cup.round].name + (cup.rounds[cup.round].need > 1
|
||||
? ' · FIRST TO ' + cup.rounds[cup.round].need + ' GAMES' : '')] : []) });
|
||||
? ' · FIRST TO ' + cup.rounds[cup.round].need + ' GAMES' : '')]
|
||||
// the top of the band walking BACK on is the arcade's natural "what now?" moment — the
|
||||
// intro answers it in place (THE LEAGUES: the cup is the only promotion)
|
||||
: G.match.again ? ['NOBODY STRONGER IS LEFT', 'THE CUP IS THE WAY UP'] : []) });
|
||||
// ...and a fresh body has read NOTHING yet: the habit tracker starts clean (see THE READ, config)
|
||||
G.match.laneRun = 0; G.match.lastLane = undefined; G.match.surprised = G.match.expected = false;
|
||||
// a FLAT pool per body: with regicide gone the body is pure scoreboard (matchPoint takes a
|
||||
@@ -793,8 +796,12 @@ function matchGameWon(zb, banked = false) {
|
||||
if (!G.net && !d.train) {
|
||||
d.felled = (d.felled || 0) + 1; // bodies actually felled (the board's number)
|
||||
// ...and the ladder climbs only inside the league's band: past its top rung the league has
|
||||
// nobody stronger, so the top body walks back on (THE LEAGUES — the cup is the way up)
|
||||
d.opp = Math.min(d.opp + 1, leagueOf(G.league).band[1]);
|
||||
// nobody stronger, so the top body walks back on (THE LEAGUES — the cup is the way up).
|
||||
// `d.again` marks exactly that moment — the felled body WAS the top — so its walk-on modal
|
||||
// can answer the "what now?" the repeat would otherwise pose (see matchOpponent).
|
||||
const top = leagueOf(G.league).band[1];
|
||||
d.again = d.opp >= top;
|
||||
d.opp = Math.min(d.opp + 1, top);
|
||||
}
|
||||
// ALTERNATING SERVICE, as the sport has it. The match calls advancePoint directly, which banks
|
||||
// the game but never touches the server — that flip lives in tennis's own pointTo, which the
|
||||
|
||||
+4
-1
@@ -826,7 +826,10 @@ export const COURTS = [
|
||||
export const MODES = [
|
||||
{ id: 'classic', name: 'CLASSIC TENNIS', sub: 'REAL TENNIS · BEST OF THREE SETS', art: 'mode_classic' },
|
||||
{ id: 'zombie', name: 'ARCADE', sub: 'ONE GAME PER OPPONENT · BLOOD ONLY', art: 'mode_arcade' },
|
||||
{ id: 'cup', name: 'THE TOURNAMENT', sub: 'FOUR ROUNDS · THE CHAMPION WAITS', art: 'mode_cup' },
|
||||
// `tag` is the poster's corner chip (main.banner): the cup is THE WAY UP — winning your
|
||||
// league's tournament is the one route to promotion, and the picker should say so standing.
|
||||
{ id: 'cup', name: 'THE TOURNAMENT', sub: 'YOUR LEAGUE\'S ROUNDS · WIN IT TO RISE', art: 'mode_cup',
|
||||
tag: 'THE WAY UP' },
|
||||
// TRAINING (2026-08-28): pick any rung, play it one game at a time, nothing is banked. The same
|
||||
// match machinery with the ladder parked — see startMatch's train parameter.
|
||||
{ id: 'train', name: 'TRAINING', sub: 'PICK A BODY · ONE GAME · GO AGAIN', art: 'mode_train' },
|
||||
|
||||
+24
-9
@@ -107,7 +107,9 @@ const banner = (card, cls, last) => {
|
||||
b.style.backgroundImage =
|
||||
`linear-gradient(180deg, rgba(0,0,0,0) 34%, rgba(0,0,0,0.78)), url(assets/${card.art}.png?v=${SPRV})`;
|
||||
b.innerHTML = `<span class="cb"><b>${card.name}</b><i>${card.sub}</i></span>`
|
||||
+ (last ? '<u class="last">LAST</u>' : '');
|
||||
+ (last ? '<u class="last">LAST</u>' : '')
|
||||
// the standing answer to "how do I advance", worn by the poster it is true of (config MODES)
|
||||
+ (card.tag ? `<u class="way">${card.tag}</u>` : '');
|
||||
return b;
|
||||
};
|
||||
const pickMode = id => { pickedMode = id; try { localStorage.setItem('mode', id); } catch {} };
|
||||
@@ -512,10 +514,15 @@ function buildCard() {
|
||||
el.textContent = `LEAGUE ${lg.id} OF ${LEAGUES.length} — ${lg.name}`;
|
||||
el.style.color = lg.color;
|
||||
document.getElementById('cdLevel').textContent =
|
||||
'LEVEL ' + pr.level + (pr.need ? '' : ' · THE CAP');
|
||||
'LEVEL ' + pr.level + ' OF ' + profile.LEVEL_CAP;
|
||||
document.getElementById('cdFill').style.width = Math.round(pr.frac * 100) + '%';
|
||||
document.getElementById('cdXp').textContent = profile.prof.xp.toLocaleString('en-GB') + ' XP'
|
||||
+ (pr.need ? ' · ' + (pr.need - pr.into).toLocaleString('en-GB') + ' TO LEVEL ' + (pr.level + 1) : '');
|
||||
+ (pr.need ? ' · ' + (pr.need - pr.into).toLocaleString('en-GB') + ' TO LEVEL ' + (pr.level + 1)
|
||||
: ' · THE CAP');
|
||||
// the CAREER bar is the long form the splash deliberately does not carry: whole-curve progress,
|
||||
// honest about how far level 50 is
|
||||
document.getElementById('cdCareerFill').style.width =
|
||||
Math.min(100, Math.round(profile.prof.xp / profile.xpFor(profile.LEVEL_CAP) * 100)) + '%';
|
||||
const row = document.getElementById('cdBadges');
|
||||
row.innerHTML = '';
|
||||
for (const l of LEAGUES) {
|
||||
@@ -545,7 +552,7 @@ function buildCard() {
|
||||
+ fig('RUNS', (g.matches + z.matches + c.matches).toLocaleString('en-GB'));
|
||||
refreshLook();
|
||||
}
|
||||
document.getElementById('xpRow').addEventListener('click', () => { buildCard(); go('card'); });
|
||||
document.getElementById('plaque').addEventListener('click', () => { buildCard(); go('card'); });
|
||||
document.getElementById('cdBack').addEventListener('click', () => go('splash'));
|
||||
let playerReturn = null; // EDIT PLAYER borrows the identity screen and comes back to the card
|
||||
document.getElementById('cdEdit').addEventListener('click', () => {
|
||||
@@ -613,11 +620,19 @@ function summary() {
|
||||
// has just ended and the count has moved.
|
||||
function refreshSplash() {
|
||||
document.getElementById('mKills').textContent = stats.total().kills.toLocaleString('en-GB') + ' KILLS';
|
||||
// the level strip — the meta clock, re-read whenever the splash comes back because a run has
|
||||
// just moved it (same rule as the kill count above)
|
||||
const pr = profile.progress();
|
||||
document.getElementById('xpLv').textContent = 'LEVEL ' + pr.level;
|
||||
document.getElementById('xpFill').style.width = Math.round(pr.frac * 100) + '%';
|
||||
// THE PLAQUE — the meta identity, re-read whenever the splash comes back because a run has just
|
||||
// moved it (same rule as the kill count above). The goal line is the standing answer to "what
|
||||
// do I do to advance": win the CURRENT league's cup, become champion, or rest on the crown.
|
||||
const pr = profile.progress(), lg = leagueOf(profile.prof.lg);
|
||||
document.getElementById('plBadge').src = `assets/badge_l${lg.id}.png?v=${SPRV}`;
|
||||
document.getElementById('plName').textContent = stats.savedName() || 'YOUR PLAYER';
|
||||
document.getElementById('plLv').textContent = 'LEVEL ' + pr.level + ' · LEAGUE ' + lg.id;
|
||||
document.getElementById('plFill').style.width = Math.round(pr.frac * 100) + '%';
|
||||
document.getElementById('plGoal').textContent = profile.prof.ch
|
||||
? 'CHAMPION OF THE DEAD CLUB — DEFEND THE NAME'
|
||||
: profile.prof.lg >= LEAGUES.length
|
||||
? 'WIN THE ' + lg.name.replace('THE ', '') + ' CUP — BECOME CHAMPION'
|
||||
: 'WIN THE ' + lg.name.replace('THE ', '') + ' CUP TO ADVANCE';
|
||||
summary();
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -2919,10 +2919,11 @@ export function render() {
|
||||
if (xd.xp > 0) {
|
||||
const pr = progress();
|
||||
ctx.font = F(8);
|
||||
// +24/+34: clear of the NEW BEST line above, which is B(20) drawn top-baseline at +2
|
||||
shadowText('+' + xd.xp + ' XP' + (xd.to > xd.from ? ' · LEVEL ' + xd.from + ' → ' + xd.to
|
||||
: ' · LEVEL ' + pr.level),
|
||||
W / 2, H / 2 + 18, xd.to > xd.from ? UI.pick : UI.cream);
|
||||
const bw = 120, bx = Math.round(W / 2 - bw / 2), by = Math.round(H / 2 + 30);
|
||||
W / 2, H / 2 + 24, xd.to > xd.from ? UI.pick : UI.cream);
|
||||
const bw = 120, bx = Math.round(W / 2 - bw / 2), by = Math.round(H / 2 + 36);
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.14)'; ctx.fillRect(bx, by, bw, 4);
|
||||
// the fill is a gradient (Oliver's call), green into gold — same ramp as the splash strip
|
||||
const gr = ctx.createLinearGradient(bx, 0, bx + bw, 0);
|
||||
@@ -2932,7 +2933,7 @@ export function render() {
|
||||
// gated well past the animation so a panic tap can't skip it (main.js gates the restart the same)
|
||||
if (dead ? G.eatenT > EATEN_RESTART : G.done.t > DONE_RESTART) {
|
||||
ctx.font = F(8);
|
||||
shadowText(IS_TOUCH ? 'TAP TO PLAY AGAIN' : 'PRESS ANY KEY TO PLAY AGAIN', W / 2, H / 2 + 44, UI.warm);
|
||||
shadowText(IS_TOUCH ? 'TAP TO PLAY AGAIN' : 'PRESS ANY KEY TO PLAY AGAIN', W / 2, H / 2 + 50, UI.warm);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user