refactor: centralize theme/config/utils + fix data and console bugs

Phase 1 of the cohesion refactor (behavior-preserving):

Single source of truth:
- New src/lib/theme.ts: category colors (moved from data.ts, re-exported
  for back-compat), derived hex strings, nebula palette, UI colors, CSS var map
- New src/lib/config.ts: breakpoint, zoom, parallax, node radii, particle
  counts, animation timings (previously scattered magic numbers)
- New src/lib/utils/tree.ts: getNodeById, getNodeRadius, getDistanceFromOrigin,
  getBeamIntensity (pure helpers lifted out of onMount)
- New src/lib/utils/stats.ts: branch ids/labels, proficiency order, and the
  stats-panel aggregation helpers (deduped from inline copies)
- SkillTree.svelte now imports all of the above; removed the duplicated
  getCategoryColorHex, stats helpers, branch id/label lists, and inline magic numbers
- Populated src/lib/index.ts barrel

Bug fixes:
- data.ts: tmdbAPI pypiUrl pointed at tvdbAPI -> corrected to tmdbAPI
- Removed/ gated stray console.log noise (skill-tree-loaded, SW logs)
- Removed dead vars (maxAlpha, glowMaxAlpha) and unused catch binding

Tooling: ESLint + Prettier now clean; added Vitest unit tests for the new
pure utils (14 tests). svelte-check: 0 errors. build: ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
TehRiehlDealandClaude Opus 4.8 committed 2026-06-03 09:40:27 -07:00
1 parent aa58425f1a
commit 6c652e7138
12 files changed
+491 -219

No files matched your search

+82
View File
@@ -0,0 +1,82 @@
// ==========================================================================
// STATS UTILS — pure aggregation for the RPG-style character panel.
// ==========================================================================
import { treeNodes, type TreeNode } from '$lib/data';
/** Skill branch ids and their display labels (single source of truth). */
export const BRANCH_IDS = [
'concepts',
'tools-devops',
'frontend',
'backend-real-time',
'languages'
] as const;
export const BRANCH_LABELS: Record<string, string> = {
concepts: 'Concepts',
'tools-devops': 'Tools & DevOps',
frontend: 'Frontend',
'backend-real-time': 'Backend & Real-Time',
languages: 'Languages'
};
/** Proficiency sort order (best first). */
export const PROFICIENCY_ORDER: Record<string, number> = {
expert: 0,
advanced: 1,
intermediate: 2,
beginner: 3
};
/** Character "level" derived from years since the first job started. */
export function calculatePlayerLevel(): number {
const earliestStart = new Date('2020-10-01'); // First job
const now = new Date();
const millisPerYear = 1000 * 60 * 60 * 24 * 365.25;
const years = (now.getTime() - earliestStart.getTime()) / millisPerYear;
return Math.floor(years);
}
/** Character "class" — the origin node's job title. */
export function getPlayerClass(): string {
const originNode = treeNodes.find((node) => node.id === 'origin');
return originNode?.jobTitle || 'Software Engineer';
}
/** Group skill nodes by branch, sorted by proficiency. */
export function aggregateSkills(): Record<string, { label: string; skills: TreeNode[] }> {
const result: Record<string, { label: string; skills: TreeNode[] }> = {};
BRANCH_IDS.forEach((branchId) => {
const skills = treeNodes.filter(
(node) => node.parentId === branchId && node.category === 'skills'
);
skills.sort((a, b) => {
const aOrder = PROFICIENCY_ORDER[a.proficiency || 'beginner'];
const bOrder = PROFICIENCY_ORDER[b.proficiency || 'beginner'];
return aOrder - bOrder;
});
result[branchId] = { label: BRANCH_LABELS[branchId], skills };
});
return result;
}
/** Count all skill nodes by proficiency tier. */
export function countByProficiency(): {
expert: number;
advanced: number;
intermediate: number;
beginner: number;
} {
const counts = { expert: 0, advanced: 0, intermediate: 0, beginner: 0 };
treeNodes.forEach((node) => {
if (node.proficiency) {
counts[node.proficiency]++;
}
});
return counts;
}