The leagues, the polish round: the way-up ribbon, upright chevrons, the breathing badges, the card counts up
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
87ac3294bc
commit
fa6db7f77b
4 files changed
+113
-42
No files matched your search
+44
-16
@@ -506,7 +506,21 @@ document.getElementById('trBack').addEventListener('click', () => go('setup'));
|
||||
// honours row is a COLLECTION: every slot always visible, earned badges bright, the rest dark —
|
||||
// the empty slots are the incentive. The ledger reads the same stats module the BODY COUNT does.
|
||||
const fig = (label, val) => `<div class="fig"><b>${label}</b><i>${val}</i></div>`;
|
||||
// THE CARD COUNTS UP (Oliver): bars sweep in and every number climbs fast on entry. setTimeout,
|
||||
// not rAF — the menu parks the game loop (the bracket's own rule). Timers are cancelled on the
|
||||
// next build so a re-entry never races itself.
|
||||
let cardTimers = [];
|
||||
const countUp = (write, target, dur = 700) => {
|
||||
const t0 = performance.now();
|
||||
(function tick() {
|
||||
const p = Math.min(1, (performance.now() - t0) / dur);
|
||||
write(Math.round(target * (1 - Math.pow(1 - p, 3)))); // easeOutCubic: fast, then settles
|
||||
if (p < 1) cardTimers.push(setTimeout(tick, 30));
|
||||
})();
|
||||
};
|
||||
function buildCard() {
|
||||
for (const t of cardTimers) clearTimeout(t);
|
||||
cardTimers = [];
|
||||
const lg = leagueOf(profile.prof.lg), pr = profile.progress();
|
||||
document.getElementById('cdName').textContent = stats.savedName() || 'THE WALK-ON';
|
||||
document.getElementById('cdBadge').src = `assets/badge_l${lg.id}.png?v=${SPRV}`;
|
||||
@@ -515,14 +529,20 @@ function buildCard() {
|
||||
el.style.color = lg.color;
|
||||
document.getElementById('cdLevel').textContent =
|
||||
'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)
|
||||
: ' · 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)) + '%';
|
||||
// both bars sweep from empty to their mark (the 0.7s eased transition in the CSS)
|
||||
for (const [id, w] of [['cdFill', Math.round(pr.frac * 100)],
|
||||
['cdCareerFill', Math.min(100, Math.round(profile.prof.xp / profile.xpFor(profile.LEVEL_CAP) * 100))]]) {
|
||||
const bar = document.getElementById(id);
|
||||
bar.style.transition = 'none'; bar.style.width = '0%';
|
||||
void bar.offsetWidth; // commit the empty bar before the sweep
|
||||
bar.style.transition = '';
|
||||
cardTimers.push(setTimeout(() => { bar.style.width = w + '%'; }, 40));
|
||||
}
|
||||
const xpTail = pr.need
|
||||
? ' · ' + (pr.need - pr.into).toLocaleString('en-GB') + ' TO LEVEL ' + (pr.level + 1)
|
||||
: ' · THE CAP';
|
||||
const cdXp = document.getElementById('cdXp');
|
||||
countUp(v => cdXp.textContent = v.toLocaleString('en-GB') + ' XP' + xpTail, profile.prof.xp);
|
||||
const row = document.getElementById('cdBadges');
|
||||
row.innerHTML = '';
|
||||
for (const l of LEAGUES) {
|
||||
@@ -542,14 +562,22 @@ function buildCard() {
|
||||
const g = stats.read('all', undefined, stats.MODE_GORE),
|
||||
z = stats.read('all', undefined, stats.MODE_MATCH),
|
||||
c = stats.read('all', undefined, stats.MODE_CLASSIC);
|
||||
document.getElementById('cdFigs1').innerHTML =
|
||||
fig('KILLS', (g.kills + z.kills + c.kills).toLocaleString('en-GB'))
|
||||
+ fig('MULTI-KILLS', (g.multi + z.multi + c.multi).toLocaleString('en-GB'))
|
||||
+ fig('DEATHS', (g.deaths + z.deaths + c.deaths).toLocaleString('en-GB'));
|
||||
document.getElementById('cdFigs2').innerHTML =
|
||||
fig('BEST RUN', g.longest + ' G')
|
||||
+ fig('MOST FELLED', z.longest)
|
||||
+ fig('RUNS', (g.matches + z.matches + c.matches).toLocaleString('en-GB'));
|
||||
// the ledger climbs: rows are [box id, [label, target, format]...]; every value starts at 0 and
|
||||
// counts up through the same easing the bars sweep with
|
||||
for (const [boxId, rows] of [
|
||||
['cdFigs1', [['KILLS', g.kills + z.kills + c.kills],
|
||||
['MULTI-KILLS', g.multi + z.multi + c.multi],
|
||||
['DEATHS', g.deaths + z.deaths + c.deaths]]],
|
||||
['cdFigs2', [['BEST RUN', g.longest, v => v + ' G'],
|
||||
['MOST FELLED', z.longest],
|
||||
['RUNS', g.matches + z.matches + c.matches]]]]) {
|
||||
const box = document.getElementById(boxId);
|
||||
box.innerHTML = rows.map(([label]) => fig(label, '0')).join('');
|
||||
[...box.querySelectorAll('.fig i')].forEach((cell, i) => {
|
||||
const fmt = rows[i][2] || (v => v.toLocaleString('en-GB'));
|
||||
countUp(v => cell.textContent = fmt(v), rows[i][1]);
|
||||
});
|
||||
}
|
||||
refreshLook();
|
||||
}
|
||||
document.getElementById('plaque').addEventListener('click', () => { buildCard(); go('card'); });
|
||||
|
||||
Reference in new issue
Block a user