From e0dba18315b08ce42ad3534bc1bf54fd4618ef66 Mon Sep 17 00:00:00 2001 From: sHa Date: Sat, 21 Feb 2026 23:46:44 +0200 Subject: [PATCH] feat: enhance evolution guide to display cumulative icon counts and adjust styling for clarity --- src/js/evolution.js | 46 ++++++++++++++++++++++++++++++++++++++++++++ src/js/guide.js | 10 ++++++++-- src/styles/guide.css | 2 +- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/js/evolution.js b/src/js/evolution.js index 9259cf1..1b64918 100644 --- a/src/js/evolution.js +++ b/src/js/evolution.js @@ -30,6 +30,8 @@ const EVOLUTION_STORAGE_KEY = 'devpage:evolution'; export class EvolutionController { /** @type {Entity[]} */ #entities = []; + /** Cumulative counts of every icon ever created (since last clear or load). */ + /** @type {Record} */ #totalCounts = {}; /** @type {HTMLElement|null} */ #container = null; /** @type {number} */ #moveSpeed = DEFAULTS.MOVE_SPEED; /** @type {number} */ #spawnRate = 5; @@ -131,6 +133,8 @@ export class EvolutionController { const iconMeta = this.#iconsData.icons[iconName]; if (!iconMeta) return; const typeMeta = this.#iconsData.types[iconMeta.type]; + // track total count + this.#incrementTotal(iconName); const margin = DEFAULTS.ICON_SIZE * 2; const vw = window.innerWidth; @@ -155,6 +159,8 @@ export class EvolutionController { this.#entities.forEach(e => e.destroy()); this.#entities = []; this.#startTime = Date.now(); // reset lifetime counter + // also clear total counts + this.#totalCounts = {}; // also tell the logo word to forget its bump counts; the visual labels // should go back to `0` immediately @@ -195,8 +201,21 @@ export class EvolutionController { /** Read-only access to current entity count (useful for debugging). */ get entityCount() { return this.#entities.length; } + /** Cumulative totals for each icon (spawned/mutated) since last clear or load. */ + get totalCounts() { return { ...this.#totalCounts }; } + // ── Spawning ──────────────────────────────────────────────── + /** + * Private helper: increment the all‑time count for an icon. + * + * @param {string} name + */ + #incrementTotal(name) { + if (!name) return; + this.#totalCounts[name] = (this.#totalCounts[name] || 0) + 1; + } + /** * Schedule the next spawn after a random delay within the configured range. * Self-resets after each spawn so the process continues indefinitely. @@ -251,6 +270,8 @@ export class EvolutionController { if (entity.alive) { this.#entities.push(entity); + // record spawn for totals + this.#incrementTotal(name); } } @@ -447,6 +468,7 @@ export class EvolutionController { hueShift: e.hueShift, })), logo: this.#logo?.serialise() ?? null, + totalCounts: this.#totalCounts, }; localStorage.setItem(EVOLUTION_STORAGE_KEY, JSON.stringify(snapshot)); } catch { /* quota exceeded — ignore */ } @@ -473,6 +495,20 @@ export class EvolutionController { this.#savedLogoState = saved.logo; } + // determine whether we'll need to rebuild totals from the entity list + let buildTotalsFromEntities = false; + if ( + saved.totalCounts && + typeof saved.totalCounts === 'object' && + Object.keys(saved.totalCounts).length > 0 + ) { + // existing totals present + this.#totalCounts = { ...saved.totalCounts }; + } else { + // no meaningful totals stored; rebuild from entity list below + buildTotalsFromEntities = true; + } + const entities = saved.entities; if (!Array.isArray(entities) || entities.length === 0) return; @@ -504,8 +540,18 @@ export class EvolutionController { `hue-rotate(${s.hueShift}deg) drop-shadow(0 1px 6px rgba(0,0,0,0.35))`; } this.#entities.push(entity); + // if the snapshot didn't already include totals, count this entity now + if (buildTotalsFromEntities) { + this.#incrementTotal(s.name); + } } })); + + // make sure totals are at least as large as the current restored population + const restored = this.getCounts(); + for (const [key, num] of Object.entries(restored)) { + this.#totalCounts[key] = Math.max(this.#totalCounts[key] || 0, num); + } } catch { /* corrupt save — start fresh */ } } diff --git a/src/js/guide.js b/src/js/guide.js index 36e6a00..e1489cd 100644 --- a/src/js/guide.js +++ b/src/js/guide.js @@ -124,10 +124,16 @@ export class GuideController { const counts = this.#evolution.getCounts(); const lifetime = this.#evolution.lifetime; - // Update per-entity count badges + // Update per-entity count badges (current + all‑time total in parentheses) + const totals = this.#evolution.totalCounts || {}; this.#panel.querySelectorAll('[data-count-key]').forEach(item => { const el = item.querySelector('.guide-entity__count'); - if (el) el.textContent = String(counts[item.dataset.countKey] ?? 0); + if (el) { + const key = item.dataset.countKey; + const cur = counts[key] ?? 0; + const tot = totals[key] ?? 0; + el.textContent = tot > 0 ? `${cur} (${tot})` : String(cur); + } }); // Update uptime display diff --git a/src/styles/guide.css b/src/styles/guide.css index 0bac353..cfeacbf 100644 --- a/src/styles/guide.css +++ b/src/styles/guide.css @@ -137,7 +137,7 @@ font-family: var(--font-mono); font-size: 0.62rem; color: rgba(255, 255, 255, 0.22); - min-width: 20px; + min-width: 40px; /* allow space for “cur (total)” */ text-align: right; flex-shrink: 0; font-variant-numeric: tabular-nums;