mirror of
https://github.com/shadoll/dev.shadoll.git
synced 2026-08-28 11:33:15 +00:00
feat: enhance evolution guide to display cumulative icon counts and adjust styling for clarity
This commit is contained in:
@@ -30,6 +30,8 @@ const EVOLUTION_STORAGE_KEY = 'devpage:evolution';
|
|||||||
|
|
||||||
export class EvolutionController {
|
export class EvolutionController {
|
||||||
/** @type {Entity[]} */ #entities = [];
|
/** @type {Entity[]} */ #entities = [];
|
||||||
|
/** Cumulative counts of every icon ever created (since last clear or load). */
|
||||||
|
/** @type {Record<string,number>} */ #totalCounts = {};
|
||||||
/** @type {HTMLElement|null} */ #container = null;
|
/** @type {HTMLElement|null} */ #container = null;
|
||||||
/** @type {number} */ #moveSpeed = DEFAULTS.MOVE_SPEED;
|
/** @type {number} */ #moveSpeed = DEFAULTS.MOVE_SPEED;
|
||||||
/** @type {number} */ #spawnRate = 5;
|
/** @type {number} */ #spawnRate = 5;
|
||||||
@@ -131,6 +133,8 @@ export class EvolutionController {
|
|||||||
const iconMeta = this.#iconsData.icons[iconName];
|
const iconMeta = this.#iconsData.icons[iconName];
|
||||||
if (!iconMeta) return;
|
if (!iconMeta) return;
|
||||||
const typeMeta = this.#iconsData.types[iconMeta.type];
|
const typeMeta = this.#iconsData.types[iconMeta.type];
|
||||||
|
// track total count
|
||||||
|
this.#incrementTotal(iconName);
|
||||||
|
|
||||||
const margin = DEFAULTS.ICON_SIZE * 2;
|
const margin = DEFAULTS.ICON_SIZE * 2;
|
||||||
const vw = window.innerWidth;
|
const vw = window.innerWidth;
|
||||||
@@ -155,6 +159,8 @@ export class EvolutionController {
|
|||||||
this.#entities.forEach(e => e.destroy());
|
this.#entities.forEach(e => e.destroy());
|
||||||
this.#entities = [];
|
this.#entities = [];
|
||||||
this.#startTime = Date.now(); // reset lifetime counter
|
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
|
// also tell the logo word to forget its bump counts; the visual labels
|
||||||
// should go back to `0` immediately
|
// should go back to `0` immediately
|
||||||
@@ -195,8 +201,21 @@ export class EvolutionController {
|
|||||||
/** Read-only access to current entity count (useful for debugging). */
|
/** Read-only access to current entity count (useful for debugging). */
|
||||||
get entityCount() { return this.#entities.length; }
|
get entityCount() { return this.#entities.length; }
|
||||||
|
|
||||||
|
/** Cumulative totals for each icon (spawned/mutated) since last clear or load. */
|
||||||
|
get totalCounts() { return { ...this.#totalCounts }; }
|
||||||
|
|
||||||
// ── Spawning ────────────────────────────────────────────────
|
// ── 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.
|
* Schedule the next spawn after a random delay within the configured range.
|
||||||
* Self-resets after each spawn so the process continues indefinitely.
|
* Self-resets after each spawn so the process continues indefinitely.
|
||||||
@@ -251,6 +270,8 @@ export class EvolutionController {
|
|||||||
|
|
||||||
if (entity.alive) {
|
if (entity.alive) {
|
||||||
this.#entities.push(entity);
|
this.#entities.push(entity);
|
||||||
|
// record spawn for totals
|
||||||
|
this.#incrementTotal(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,6 +468,7 @@ export class EvolutionController {
|
|||||||
hueShift: e.hueShift,
|
hueShift: e.hueShift,
|
||||||
})),
|
})),
|
||||||
logo: this.#logo?.serialise() ?? null,
|
logo: this.#logo?.serialise() ?? null,
|
||||||
|
totalCounts: this.#totalCounts,
|
||||||
};
|
};
|
||||||
localStorage.setItem(EVOLUTION_STORAGE_KEY, JSON.stringify(snapshot));
|
localStorage.setItem(EVOLUTION_STORAGE_KEY, JSON.stringify(snapshot));
|
||||||
} catch { /* quota exceeded — ignore */ }
|
} catch { /* quota exceeded — ignore */ }
|
||||||
@@ -473,6 +495,20 @@ export class EvolutionController {
|
|||||||
this.#savedLogoState = saved.logo;
|
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;
|
const entities = saved.entities;
|
||||||
if (!Array.isArray(entities) || entities.length === 0) return;
|
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))`;
|
`hue-rotate(${s.hueShift}deg) drop-shadow(0 1px 6px rgba(0,0,0,0.35))`;
|
||||||
}
|
}
|
||||||
this.#entities.push(entity);
|
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 */ }
|
} catch { /* corrupt save — start fresh */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -124,10 +124,16 @@ export class GuideController {
|
|||||||
const counts = this.#evolution.getCounts();
|
const counts = this.#evolution.getCounts();
|
||||||
const lifetime = this.#evolution.lifetime;
|
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 => {
|
this.#panel.querySelectorAll('[data-count-key]').forEach(item => {
|
||||||
const el = item.querySelector('.guide-entity__count');
|
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
|
// Update uptime display
|
||||||
|
|||||||
@@ -137,7 +137,7 @@
|
|||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 0.62rem;
|
font-size: 0.62rem;
|
||||||
color: rgba(255, 255, 255, 0.22);
|
color: rgba(255, 255, 255, 0.22);
|
||||||
min-width: 20px;
|
min-width: 40px; /* allow space for “cur (total)” */
|
||||||
text-align: right;
|
text-align: right;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
|
|||||||
Reference in New Issue
Block a user