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
+22
@@ -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
|
||||
|
||||
+43
-25
@@ -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
|
||||
<span id="plRow"><u id="plLv">LEVEL 1</u><s><i id="plFill"></i></s></span>
|
||||
<i id="plGoal"></i>
|
||||
</span>
|
||||
<em>›</em>
|
||||
<em>></em>
|
||||
</button>
|
||||
<button class="btn big" id="mStart">PLAY MATCH</button>
|
||||
<div class="two">
|
||||
@@ -605,15 +623,15 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
who-and-where with SERVE UP. Every step comes up pre-selected from last time, so a
|
||||
returning player is three taps from serving. -->
|
||||
<div id="stmode">
|
||||
<div class="hdr"><button class="chev" id="mBack">‹</button><h2>PICK A GAME</h2></div>
|
||||
<div class="hdr"><button class="chev" id="mBack"><</button><h2>PICK A GAME</h2></div>
|
||||
<div id="modeList"></div>
|
||||
</div>
|
||||
<div id="stsub">
|
||||
<div class="hdr"><button class="chev" id="subBack">‹</button><h2 id="subTitle">DIFFICULTY</h2></div>
|
||||
<div class="hdr"><button class="chev" id="subBack"><</button><h2 id="subTitle">DIFFICULTY</h2></div>
|
||||
<div id="modeSub"></div>
|
||||
</div>
|
||||
<div id="setup">
|
||||
<div class="hdr"><button class="chev" id="sBack">‹</button><h2>MATCH SETUP</h2></div>
|
||||
<div class="hdr"><button class="chev" id="sBack"><</button><h2>MATCH SETUP</h2></div>
|
||||
<!-- ONE PLAYER BLOCK. It was four stacked sections — two big portrait tiles, then the live
|
||||
player, then a kit row, then a skin row — which said the same thing twice (the tiles were
|
||||
a second, FRONT-FACING drawing of a man who does not react to any of the colours) and ran
|
||||
@@ -644,7 +662,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
keys), so the two screens cannot disagree about who you are. Rebuilt on every visit by
|
||||
main.buildPlayer, so the .on states track the setup screen's. -->
|
||||
<div id="player">
|
||||
<div class="hdr"><button class="chev" id="pBack">‹</button><h2>YOUR PLAYER</h2></div>
|
||||
<div class="hdr"><button class="chev" id="pBack"><</button><h2>YOUR PLAYER</h2></div>
|
||||
<h3>NAME<span>YOURS ON EVERY BOARD</span></h3>
|
||||
<div id="nameRow">
|
||||
<input id="pName" class="tin" maxlength="12" autocomplete="off" spellcheck="false">
|
||||
@@ -684,7 +702,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
main.buildCard from the profile + stats; the honours row shows every badge slot, earned
|
||||
bright and unearned dark. -->
|
||||
<div id="card">
|
||||
<div class="hdr"><button class="chev" id="cdBack">‹</button><h2 id="cdName">YOUR PLAYER</h2></div>
|
||||
<div class="hdr"><button class="chev" id="cdBack"><</button><h2 id="cdName">YOUR PLAYER</h2></div>
|
||||
<div id="cdTop">
|
||||
<canvas id="cdLook" width="96" height="224"></canvas>
|
||||
<div id="cdMeta">
|
||||
@@ -710,7 +728,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
(main.js hands the settled rungs to startMatch). The rows are built in main.js from
|
||||
config.CUP_ROUNDS, so a round added there needs no markup. -->
|
||||
<div id="bracket">
|
||||
<div class="hdr"><button class="chev" id="brBack">‹</button><h2>THE DRAW</h2></div>
|
||||
<div class="hdr"><button class="chev" id="brBack"><</button><h2>THE DRAW</h2></div>
|
||||
<h3>FOUR ROUNDS<span id="brState">DRAWING…</span></h3>
|
||||
<div id="cupRows"></div>
|
||||
<div class="foot">
|
||||
@@ -721,7 +739,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
<!-- THE TRAINING COURT's opponent picker: the whole ladder face-up, tap a body to drill it.
|
||||
Built in main.js from config's roster (matchFoe/LADDER), so a new rung needs no markup. -->
|
||||
<div id="train">
|
||||
<div class="hdr"><button class="chev" id="trBack">‹</button><h2>TRAINING</h2></div>
|
||||
<div class="hdr"><button class="chev" id="trBack"><</button><h2>TRAINING</h2></div>
|
||||
<h3>PICK YOUR OPPONENT<span>ONE GAME · GO AGAIN</span></h3>
|
||||
<div id="trainList"></div>
|
||||
</div>
|
||||
@@ -730,7 +748,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
a socket is open, so the room code and its SHARE button stand where the buttons were and
|
||||
the game stays parked behind this overlay until the room fills (main.js, THE LOBBY). -->
|
||||
<div id="vs">
|
||||
<div class="hdr"><button class="chev" id="vsBack">‹</button><h2>VS A FRIEND</h2></div>
|
||||
<div class="hdr"><button class="chev" id="vsBack"><</button><h2>VS A FRIEND</h2></div>
|
||||
<div id="vsPick">
|
||||
<h3>01 · YOUR NAME<span>ON THEIR SCOREBOARD</span></h3>
|
||||
<input id="vsName" class="tin" maxlength="12" placeholder="YOUR NAME" autocomplete="off"
|
||||
@@ -758,7 +776,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
</div>
|
||||
<!-- BODY COUNT. Every row in here is built by stats.js from the run records in localStorage. -->
|
||||
<div id="stats">
|
||||
<div class="hdr"><button class="chev" id="stBack">‹</button><h2 id="stTitle">BODY COUNT</h2></div>
|
||||
<div class="hdr"><button class="chev" id="stBack"><</button><h2 id="stTitle">BODY COUNT</h2></div>
|
||||
<!-- WHICH MODE, then which period. The three modes do not measure the same thing (see the
|
||||
record-shape note in stats.endRun), so this strip changes the whole screen under it —
|
||||
the title, the three figures, the roster's heading and the footer. -->
|
||||
@@ -783,7 +801,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
a ranking. Same tab strip as the stats screen's, and stats.drawRanks rewrites the column
|
||||
heading with the tab, because the number means something different on each one. -->
|
||||
<div id="ranks">
|
||||
<div class="hdr"><button class="chev" id="rkBack">‹</button><h2>WORLD BOARD</h2></div>
|
||||
<div class="hdr"><button class="chev" id="rkBack"><</button><h2>WORLD BOARD</h2></div>
|
||||
<div class="tabs" id="rkModes">
|
||||
<button class="on" data-m="g">SPLATTER</button>
|
||||
<button data-m="z">TENNIS</button>
|
||||
@@ -796,7 +814,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
at it, which is the whole point: it replaced a triple-tap on the in-play scoreboard that
|
||||
players kept tripping by accident. -->
|
||||
<div id="secret">
|
||||
<div class="hdr"><button class="chev" id="scBack">‹</button><h2>BACK ROOM</h2></div>
|
||||
<div class="hdr"><button class="chev" id="scBack"><</button><h2>BACK ROOM</h2></div>
|
||||
<div class="foot bare">
|
||||
<button class="btn big" id="scWeapons">WEAPON TEST</button>
|
||||
<button class="btn big" id="scBosses">BOSS TEST</button>
|
||||
@@ -808,7 +826,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
<!-- one tile per chapter boss, built from config.bossArt in main.js: adding a design to the
|
||||
roster puts it on this screen and never touches this markup -->
|
||||
<div id="bosses">
|
||||
<div class="hdr"><button class="chev" id="bsBack">‹</button><h2>BOSS TEST</h2></div>
|
||||
<div class="hdr"><button class="chev" id="bsBack"><</button><h2>BOSS TEST</h2></div>
|
||||
<h3>PICK A CHAPTER<span>FULL ARSENAL</span></h3>
|
||||
<div id="bossList"></div>
|
||||
</div>
|
||||
@@ -828,6 +846,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="src/main.js?v=316"></script>
|
||||
<script type="module" src="src/main.js?v=317"></script>
|
||||
</body>
|
||||
</html>
|
||||
+4
-1
@@ -844,7 +844,10 @@ export const MODES = [
|
||||
// (`mode`) or carries its own card and the MODES ids it fans out to (`kids`).
|
||||
export const MENU = [
|
||||
{ mode: 'classic', diffs: true },
|
||||
{ name: 'ZOMBIE TENNIS', sub: 'THE DEAD CLUB PLAYS YOU', art: 'mode_zombie', kids: ['zombie', 'cup', 'train'] },
|
||||
// the WAY-UP ribbon shows on THIS level too, so the click path to the promotion is marked
|
||||
// from the first screen of the picker (main.banner reads `tag`)
|
||||
{ name: 'ZOMBIE TENNIS', sub: 'THE DEAD CLUB PLAYS YOU', art: 'mode_zombie', kids: ['zombie', 'cup', 'train'],
|
||||
tag: 'THE WAY UP' },
|
||||
{ mode: 'gore' },
|
||||
// NETPLAY (prototype): a poster with no run mode of its own — its second step is HOST / JOIN,
|
||||
// and either chip starts classic tennis against the other phone (beats.startNetVs + src/net.js)
|
||||
|
||||
+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