@boshu2/vibe-check 1.0.1 โ†’ 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/.agents/bundles/ml-learning-loop-complete-plan-2025-11-28.md +908 -0
  2. package/.agents/bundles/unified-vibe-system-plan-phase1-2025-11-28.md +962 -0
  3. package/.agents/bundles/unified-vibe-system-research-2025-11-28.md +1003 -0
  4. package/.agents/bundles/vibe-check-ecosystem-plan-2025-11-29.md +635 -0
  5. package/.agents/bundles/vibe-check-gamification-complete-2025-11-29.md +132 -0
  6. package/.agents/bundles/vibe-score-scientific-framework-2025-11-28.md +602 -0
  7. package/.vibe-check/calibration.json +38 -0
  8. package/.vibe-check/latest.json +114 -0
  9. package/CHANGELOG.md +46 -0
  10. package/CLAUDE.md +178 -0
  11. package/README.md +265 -63
  12. package/action.yml +270 -0
  13. package/dashboard/app.js +494 -0
  14. package/dashboard/index.html +235 -0
  15. package/dashboard/styles.css +647 -0
  16. package/dist/calibration/ece.d.ts +26 -0
  17. package/dist/calibration/ece.d.ts.map +1 -0
  18. package/dist/calibration/ece.js +93 -0
  19. package/dist/calibration/ece.js.map +1 -0
  20. package/dist/calibration/index.d.ts +3 -0
  21. package/dist/calibration/index.d.ts.map +1 -0
  22. package/dist/calibration/index.js +15 -0
  23. package/dist/calibration/index.js.map +1 -0
  24. package/dist/calibration/storage.d.ts +34 -0
  25. package/dist/calibration/storage.d.ts.map +1 -0
  26. package/dist/calibration/storage.js +188 -0
  27. package/dist/calibration/storage.js.map +1 -0
  28. package/dist/cli.js +30 -76
  29. package/dist/cli.js.map +1 -1
  30. package/dist/commands/analyze.d.ts +16 -0
  31. package/dist/commands/analyze.d.ts.map +1 -0
  32. package/dist/commands/analyze.js +256 -0
  33. package/dist/commands/analyze.js.map +1 -0
  34. package/dist/commands/index.d.ts +4 -0
  35. package/dist/commands/index.d.ts.map +1 -0
  36. package/dist/commands/index.js +11 -0
  37. package/dist/commands/index.js.map +1 -0
  38. package/dist/commands/level.d.ts +3 -0
  39. package/dist/commands/level.d.ts.map +1 -0
  40. package/dist/commands/level.js +277 -0
  41. package/dist/commands/level.js.map +1 -0
  42. package/dist/commands/profile.d.ts +4 -0
  43. package/dist/commands/profile.d.ts.map +1 -0
  44. package/dist/commands/profile.js +143 -0
  45. package/dist/commands/profile.js.map +1 -0
  46. package/dist/gamification/achievements.d.ts +15 -0
  47. package/dist/gamification/achievements.d.ts.map +1 -0
  48. package/dist/gamification/achievements.js +273 -0
  49. package/dist/gamification/achievements.js.map +1 -0
  50. package/dist/gamification/index.d.ts +8 -0
  51. package/dist/gamification/index.d.ts.map +1 -0
  52. package/dist/gamification/index.js +30 -0
  53. package/dist/gamification/index.js.map +1 -0
  54. package/dist/gamification/profile.d.ts +46 -0
  55. package/dist/gamification/profile.d.ts.map +1 -0
  56. package/dist/gamification/profile.js +272 -0
  57. package/dist/gamification/profile.js.map +1 -0
  58. package/dist/gamification/streaks.d.ts +26 -0
  59. package/dist/gamification/streaks.d.ts.map +1 -0
  60. package/dist/gamification/streaks.js +132 -0
  61. package/dist/gamification/streaks.js.map +1 -0
  62. package/dist/gamification/types.d.ts +111 -0
  63. package/dist/gamification/types.d.ts.map +1 -0
  64. package/dist/gamification/types.js +26 -0
  65. package/dist/gamification/types.js.map +1 -0
  66. package/dist/gamification/xp.d.ts +37 -0
  67. package/dist/gamification/xp.d.ts.map +1 -0
  68. package/dist/gamification/xp.js +115 -0
  69. package/dist/gamification/xp.js.map +1 -0
  70. package/dist/git.d.ts +11 -0
  71. package/dist/git.d.ts.map +1 -1
  72. package/dist/git.js +52 -0
  73. package/dist/git.js.map +1 -1
  74. package/dist/metrics/code-stability.d.ts +13 -0
  75. package/dist/metrics/code-stability.d.ts.map +1 -0
  76. package/dist/metrics/code-stability.js +74 -0
  77. package/dist/metrics/code-stability.js.map +1 -0
  78. package/dist/metrics/file-churn.d.ts +8 -0
  79. package/dist/metrics/file-churn.d.ts.map +1 -0
  80. package/dist/metrics/file-churn.js +75 -0
  81. package/dist/metrics/file-churn.js.map +1 -0
  82. package/dist/metrics/time-spiral.d.ts +8 -0
  83. package/dist/metrics/time-spiral.d.ts.map +1 -0
  84. package/dist/metrics/time-spiral.js +69 -0
  85. package/dist/metrics/time-spiral.js.map +1 -0
  86. package/dist/metrics/velocity-anomaly.d.ts +13 -0
  87. package/dist/metrics/velocity-anomaly.d.ts.map +1 -0
  88. package/dist/metrics/velocity-anomaly.js +67 -0
  89. package/dist/metrics/velocity-anomaly.js.map +1 -0
  90. package/dist/output/index.d.ts +6 -3
  91. package/dist/output/index.d.ts.map +1 -1
  92. package/dist/output/index.js +4 -3
  93. package/dist/output/index.js.map +1 -1
  94. package/dist/output/json.d.ts +2 -2
  95. package/dist/output/json.d.ts.map +1 -1
  96. package/dist/output/json.js +54 -0
  97. package/dist/output/json.js.map +1 -1
  98. package/dist/output/markdown.d.ts +2 -2
  99. package/dist/output/markdown.d.ts.map +1 -1
  100. package/dist/output/markdown.js +34 -1
  101. package/dist/output/markdown.js.map +1 -1
  102. package/dist/output/terminal.d.ts +6 -2
  103. package/dist/output/terminal.d.ts.map +1 -1
  104. package/dist/output/terminal.js +131 -3
  105. package/dist/output/terminal.js.map +1 -1
  106. package/dist/recommend/index.d.ts +3 -0
  107. package/dist/recommend/index.d.ts.map +1 -0
  108. package/dist/recommend/index.js +14 -0
  109. package/dist/recommend/index.js.map +1 -0
  110. package/dist/recommend/ordered-logistic.d.ts +49 -0
  111. package/dist/recommend/ordered-logistic.d.ts.map +1 -0
  112. package/dist/recommend/ordered-logistic.js +153 -0
  113. package/dist/recommend/ordered-logistic.js.map +1 -0
  114. package/dist/recommend/questions.d.ts +19 -0
  115. package/dist/recommend/questions.d.ts.map +1 -0
  116. package/dist/recommend/questions.js +73 -0
  117. package/dist/recommend/questions.js.map +1 -0
  118. package/dist/score/index.d.ts +21 -0
  119. package/dist/score/index.d.ts.map +1 -0
  120. package/dist/score/index.js +48 -0
  121. package/dist/score/index.js.map +1 -0
  122. package/dist/score/weights.d.ts +16 -0
  123. package/dist/score/weights.d.ts.map +1 -0
  124. package/dist/score/weights.js +28 -0
  125. package/dist/score/weights.js.map +1 -0
  126. package/dist/types.d.ts +83 -0
  127. package/dist/types.d.ts.map +1 -1
  128. package/package.json +10 -9
@@ -0,0 +1,494 @@
1
+ // Vibe-Check Dashboard Application
2
+ class VibeDashboard {
3
+ constructor() {
4
+ this.profile = null;
5
+ this.charts = {};
6
+ this.currentPage = 'dashboard';
7
+ }
8
+
9
+ async init() {
10
+ this.setupEventListeners();
11
+ await this.loadProfile();
12
+ this.renderDashboard();
13
+ this.initCharts();
14
+ }
15
+
16
+ setupEventListeners() {
17
+ // Navigation
18
+ document.querySelectorAll('.nav-item').forEach(item => {
19
+ item.addEventListener('click', (e) => {
20
+ const page = e.currentTarget.dataset.page;
21
+ this.navigateTo(page);
22
+ });
23
+ });
24
+
25
+ // Sidebar toggle
26
+ const sidebarToggle = document.getElementById('sidebarToggle');
27
+ if (sidebarToggle) {
28
+ sidebarToggle.addEventListener('click', () => this.toggleSidebar());
29
+ }
30
+
31
+ // Refresh button
32
+ const refreshBtn = document.getElementById('refreshBtn');
33
+ if (refreshBtn) {
34
+ refreshBtn.addEventListener('click', () => this.refresh());
35
+ }
36
+
37
+ // Chart range buttons
38
+ document.querySelectorAll('.chart-btn').forEach(btn => {
39
+ btn.addEventListener('click', (e) => {
40
+ const range = parseInt(e.target.dataset.range);
41
+ this.updateTrendChart(range);
42
+ document.querySelectorAll('.chart-btn').forEach(b => b.classList.remove('active'));
43
+ e.target.classList.add('active');
44
+ });
45
+ });
46
+
47
+ // Achievement modal close
48
+ const closeModal = document.getElementById('closeAchievementModal');
49
+ if (closeModal) {
50
+ closeModal.addEventListener('click', () => this.closeModal());
51
+ }
52
+
53
+ // Keyboard shortcuts
54
+ document.addEventListener('keydown', (e) => {
55
+ if (e.key === 'Escape') this.closeModal();
56
+ });
57
+ }
58
+
59
+ async loadProfile() {
60
+ // In a real implementation, this would fetch from an API or local file
61
+ // For now, we'll use mock data or localStorage
62
+ const stored = localStorage.getItem('vibe-check-profile');
63
+ if (stored) {
64
+ this.profile = JSON.parse(stored);
65
+ } else {
66
+ this.profile = this.getMockProfile();
67
+ }
68
+ }
69
+
70
+ getMockProfile() {
71
+ // Mock profile for demonstration
72
+ return {
73
+ version: '1.0.0',
74
+ xp: {
75
+ total: 456,
76
+ level: 3,
77
+ levelName: 'Practitioner',
78
+ currentLevelXP: 156,
79
+ nextLevelXP: 300,
80
+ },
81
+ streak: {
82
+ current: 5,
83
+ longest: 12,
84
+ weeklyProgress: 3,
85
+ weeklyGoal: 5,
86
+ },
87
+ achievements: [
88
+ { id: 'first_check', name: 'First Blood', icon: '๐Ÿฉธ', unlockedAt: '2025-11-20' },
89
+ { id: 'elite_vibes', name: 'Elite Vibes', icon: 'โœจ', unlockedAt: '2025-11-25' },
90
+ { id: 'zen_master', name: 'Zen Master', icon: '๐Ÿง˜', unlockedAt: '2025-11-28' },
91
+ ],
92
+ sessions: [
93
+ { date: '2025-11-29', vibeScore: 89, overall: 'ELITE', commits: 14, spirals: 0 },
94
+ { date: '2025-11-28', vibeScore: 85, overall: 'HIGH', commits: 23, spirals: 1 },
95
+ { date: '2025-11-27', vibeScore: 91, overall: 'ELITE', commits: 31, spirals: 0 },
96
+ { date: '2025-11-26', vibeScore: 78, overall: 'HIGH', commits: 18, spirals: 2 },
97
+ { date: '2025-11-25', vibeScore: 82, overall: 'HIGH', commits: 27, spirals: 1 },
98
+ { date: '2025-11-24', vibeScore: 70, overall: 'MEDIUM', commits: 12, spirals: 3 },
99
+ { date: '2025-11-23', vibeScore: 88, overall: 'ELITE', commits: 35, spirals: 0 },
100
+ ],
101
+ stats: {
102
+ totalSessions: 45,
103
+ totalCommitsAnalyzed: 847,
104
+ avgVibeScore: 82,
105
+ bestVibeScore: 95,
106
+ spiralsAvoided: 15,
107
+ },
108
+ };
109
+ }
110
+
111
+ navigateTo(page) {
112
+ this.currentPage = page;
113
+
114
+ // Update nav
115
+ document.querySelectorAll('.nav-item').forEach(item => {
116
+ item.classList.toggle('active', item.dataset.page === page);
117
+ });
118
+
119
+ // Update pages
120
+ document.querySelectorAll('.page').forEach(p => {
121
+ p.classList.toggle('active', p.id === `page-${page}`);
122
+ });
123
+
124
+ // Render page-specific content
125
+ switch (page) {
126
+ case 'achievements':
127
+ this.renderAchievements();
128
+ break;
129
+ case 'history':
130
+ this.renderHistory();
131
+ break;
132
+ case 'profile':
133
+ this.renderProfile();
134
+ break;
135
+ }
136
+ }
137
+
138
+ renderDashboard() {
139
+ this.updateProfileSummary();
140
+ this.updateStats();
141
+ this.renderRecentSessions();
142
+ }
143
+
144
+ updateProfileSummary() {
145
+ const { xp, streak } = this.profile;
146
+
147
+ // Level info
148
+ document.getElementById('levelIcon').textContent = this.getLevelIcon(xp.level);
149
+ document.getElementById('levelName').textContent = `Level ${xp.level} ${xp.levelName}`;
150
+ document.getElementById('xpText').textContent = `${xp.currentLevelXP}/${xp.nextLevelXP} XP`;
151
+
152
+ const progress = (xp.currentLevelXP / xp.nextLevelXP) * 100;
153
+ document.getElementById('xpFill').style.width = `${progress}%`;
154
+
155
+ // Streak
156
+ document.getElementById('streakText').textContent = `${streak.current} day streak`;
157
+ document.getElementById('streakDays').textContent = streak.current;
158
+ }
159
+
160
+ updateStats() {
161
+ const { sessions, stats } = this.profile;
162
+ const latest = sessions[0];
163
+
164
+ document.getElementById('currentScore').textContent = latest ? `${latest.vibeScore}%` : '--';
165
+ document.getElementById('avgScore').textContent = `${stats.avgVibeScore}%`;
166
+ document.getElementById('achievementCount').textContent =
167
+ `${this.profile.achievements.length}/24`;
168
+ }
169
+
170
+ renderRecentSessions() {
171
+ const container = document.getElementById('recentSessions');
172
+ const sessions = this.profile.sessions.slice(0, 5);
173
+
174
+ if (sessions.length === 0) {
175
+ container.innerHTML = `
176
+ <div class="empty-state">
177
+ <span class="empty-icon">๐Ÿ“ญ</span>
178
+ <p>No sessions yet. Run <code>vibe-check --score</code> to start!</p>
179
+ </div>
180
+ `;
181
+ return;
182
+ }
183
+
184
+ container.innerHTML = sessions.map(session => `
185
+ <div class="session-item">
186
+ <div class="session-date">${this.formatDate(session.date)}</div>
187
+ <div class="session-score">${session.vibeScore}%</div>
188
+ <span class="session-rating ${session.overall.toLowerCase()}">${session.overall}</span>
189
+ </div>
190
+ `).join('');
191
+ }
192
+
193
+ initCharts() {
194
+ this.initTrendChart();
195
+ this.initRadarChart();
196
+ this.initRatingsChart();
197
+ }
198
+
199
+ initTrendChart() {
200
+ const ctx = document.getElementById('trendCanvas');
201
+ if (!ctx) return;
202
+
203
+ const sessions = this.profile.sessions.slice().reverse();
204
+ const labels = sessions.map(s => this.formatDate(s.date));
205
+ const data = sessions.map(s => s.vibeScore);
206
+
207
+ this.charts.trend = new Chart(ctx, {
208
+ type: 'line',
209
+ data: {
210
+ labels,
211
+ datasets: [{
212
+ label: 'Vibe Score',
213
+ data,
214
+ borderColor: '#58a6ff',
215
+ backgroundColor: 'rgba(88, 166, 255, 0.1)',
216
+ fill: true,
217
+ tension: 0.4,
218
+ pointRadius: 4,
219
+ pointHoverRadius: 6,
220
+ }]
221
+ },
222
+ options: {
223
+ responsive: true,
224
+ maintainAspectRatio: false,
225
+ plugins: {
226
+ legend: { display: false },
227
+ },
228
+ scales: {
229
+ y: {
230
+ min: 0,
231
+ max: 100,
232
+ grid: { color: '#30363d' },
233
+ ticks: { color: '#7d8590' },
234
+ },
235
+ x: {
236
+ grid: { display: false },
237
+ ticks: { color: '#7d8590' },
238
+ }
239
+ }
240
+ }
241
+ });
242
+ }
243
+
244
+ initRadarChart() {
245
+ const ctx = document.getElementById('radarCanvas');
246
+ if (!ctx) return;
247
+
248
+ // Mock metrics data
249
+ this.charts.radar = new Chart(ctx, {
250
+ type: 'radar',
251
+ data: {
252
+ labels: ['Trust Pass', 'Velocity', 'Flow', 'Stability', 'No Spirals'],
253
+ datasets: [{
254
+ label: 'Current',
255
+ data: [92, 78, 85, 88, 95],
256
+ borderColor: '#58a6ff',
257
+ backgroundColor: 'rgba(88, 166, 255, 0.2)',
258
+ pointBackgroundColor: '#58a6ff',
259
+ }]
260
+ },
261
+ options: {
262
+ responsive: true,
263
+ maintainAspectRatio: false,
264
+ plugins: {
265
+ legend: { display: false },
266
+ },
267
+ scales: {
268
+ r: {
269
+ min: 0,
270
+ max: 100,
271
+ grid: { color: '#30363d' },
272
+ angleLines: { color: '#30363d' },
273
+ pointLabels: { color: '#7d8590' },
274
+ ticks: { display: false },
275
+ }
276
+ }
277
+ }
278
+ });
279
+ }
280
+
281
+ initRatingsChart() {
282
+ const ctx = document.getElementById('ratingsCanvas');
283
+ if (!ctx) return;
284
+
285
+ // Count ratings
286
+ const counts = { ELITE: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
287
+ this.profile.sessions.forEach(s => counts[s.overall]++);
288
+
289
+ this.charts.ratings = new Chart(ctx, {
290
+ type: 'doughnut',
291
+ data: {
292
+ labels: ['Elite', 'High', 'Medium', 'Low'],
293
+ datasets: [{
294
+ data: [counts.ELITE, counts.HIGH, counts.MEDIUM, counts.LOW],
295
+ backgroundColor: ['#a371f7', '#3fb950', '#d29922', '#f85149'],
296
+ borderWidth: 0,
297
+ }]
298
+ },
299
+ options: {
300
+ responsive: true,
301
+ maintainAspectRatio: false,
302
+ plugins: {
303
+ legend: {
304
+ position: 'bottom',
305
+ labels: { color: '#7d8590' },
306
+ },
307
+ },
308
+ }
309
+ });
310
+ }
311
+
312
+ updateTrendChart(days) {
313
+ const sessions = this.profile.sessions.slice(0, days).reverse();
314
+ const labels = sessions.map(s => this.formatDate(s.date));
315
+ const data = sessions.map(s => s.vibeScore);
316
+
317
+ this.charts.trend.data.labels = labels;
318
+ this.charts.trend.data.datasets[0].data = data;
319
+ this.charts.trend.update();
320
+ }
321
+
322
+ renderAchievements() {
323
+ const container = document.getElementById('achievementsGrid');
324
+ const unlocked = new Set(this.profile.achievements.map(a => a.id));
325
+
326
+ // All possible achievements
327
+ const allAchievements = [
328
+ { id: 'first_check', name: 'First Blood', icon: '๐Ÿฉธ', description: 'Run your first vibe-check' },
329
+ { id: 'week_warrior', name: 'Week Warrior', icon: 'โš”๏ธ', description: 'Maintain a 7-day streak' },
330
+ { id: 'fortnight_force', name: 'Fortnight Force', icon: '๐Ÿ›ก๏ธ', description: 'Maintain a 14-day streak' },
331
+ { id: 'monthly_master', name: 'Monthly Master', icon: '๐Ÿ‘‘', description: 'Maintain a 30-day streak' },
332
+ { id: 'elite_vibes', name: 'Elite Vibes', icon: 'โœจ', description: 'Achieve ELITE rating' },
333
+ { id: 'consistent_high', name: 'High Roller', icon: '๐ŸŽฐ', description: '5 consecutive HIGH+ sessions' },
334
+ { id: 'perfect_week', name: 'Perfect Week', icon: '๐Ÿ’Ž', description: '7 consecutive ELITE sessions' },
335
+ { id: 'score_90', name: 'Ninety Club', icon: '๐Ÿ…', description: 'Vibe Score โ‰ฅ 90%' },
336
+ { id: 'ten_sessions', name: 'Getting Started', icon: '๐Ÿ“Š', description: '10 vibe-check sessions' },
337
+ { id: 'fifty_sessions', name: 'Regular', icon: '๐Ÿ“ˆ', description: '50 vibe-check sessions' },
338
+ { id: 'hundred_sessions', name: 'Centurion', icon: '๐Ÿ’ฏ', description: '100 vibe-check sessions' },
339
+ { id: 'zen_master', name: 'Zen Master', icon: '๐Ÿง˜', description: '0 spirals in 50+ commit session' },
340
+ { id: 'trust_builder', name: 'Trust Builder', icon: '๐Ÿ—๏ธ', description: '10 sessions with 90%+ trust' },
341
+ { id: 'comeback_kid', name: 'Comeback Kid', icon: '๐Ÿ”„', description: 'LOW โ†’ ELITE in 7 days' },
342
+ { id: 'early_bird', name: 'Early Bird', icon: '๐ŸŒ…', description: 'Session before 7 AM' },
343
+ { id: 'night_owl', name: 'Night Owl', icon: '๐Ÿฆ‰', description: 'Session after midnight' },
344
+ { id: 'thousand_commits', name: 'Thousand Strong', icon: '๐ŸŽฏ', description: '1000 commits analyzed' },
345
+ // Hidden achievements shown as ???
346
+ { id: 'perfect_score', name: '???', icon: 'โ“', description: '???', hidden: true },
347
+ { id: 'spiral_survivor', name: '???', icon: 'โ“', description: '???', hidden: true },
348
+ ];
349
+
350
+ container.innerHTML = allAchievements.map(achievement => {
351
+ const isUnlocked = unlocked.has(achievement.id);
352
+ const unlockedAchievement = this.profile.achievements.find(a => a.id === achievement.id);
353
+
354
+ return `
355
+ <div class="achievement-card ${isUnlocked ? 'unlocked' : 'locked'}">
356
+ <div class="achievement-icon-wrapper">
357
+ ${isUnlocked || !achievement.hidden ? achievement.icon : '๐Ÿ”’'}
358
+ </div>
359
+ <div class="achievement-info">
360
+ <h4>${isUnlocked || !achievement.hidden ? achievement.name : '???'}</h4>
361
+ <p>${isUnlocked || !achievement.hidden ? achievement.description : 'Keep playing to unlock!'}</p>
362
+ ${isUnlocked ? `<div class="achievement-date">Unlocked ${this.formatDate(unlockedAchievement.unlockedAt)}</div>` : ''}
363
+ </div>
364
+ </div>
365
+ `;
366
+ }).join('');
367
+
368
+ document.getElementById('achievementProgress').textContent =
369
+ `${this.profile.achievements.length}/${allAchievements.length} unlocked`;
370
+ }
371
+
372
+ renderHistory() {
373
+ const container = document.getElementById('historyContainer');
374
+
375
+ container.innerHTML = `
376
+ <div class="sessions-list">
377
+ ${this.profile.sessions.map(session => `
378
+ <div class="session-item">
379
+ <div>
380
+ <div class="session-date">${this.formatDate(session.date)}</div>
381
+ <div style="font-size: 0.75rem; color: var(--text-muted)">
382
+ ${session.commits} commits ยท ${session.spirals} spirals
383
+ </div>
384
+ </div>
385
+ <div class="session-score">${session.vibeScore}%</div>
386
+ <span class="session-rating ${session.overall.toLowerCase()}">${session.overall}</span>
387
+ </div>
388
+ `).join('')}
389
+ </div>
390
+ `;
391
+ }
392
+
393
+ renderProfile() {
394
+ const container = document.getElementById('profileContainer');
395
+ const { stats, xp, streak } = this.profile;
396
+
397
+ container.innerHTML = `
398
+ <div class="stats-grid">
399
+ <div class="stat-card">
400
+ <div class="stat-icon">${this.getLevelIcon(xp.level)}</div>
401
+ <div class="stat-content">
402
+ <span class="stat-value">Level ${xp.level}</span>
403
+ <span class="stat-label">${xp.levelName}</span>
404
+ </div>
405
+ </div>
406
+ <div class="stat-card">
407
+ <div class="stat-icon">โญ</div>
408
+ <div class="stat-content">
409
+ <span class="stat-value">${xp.total}</span>
410
+ <span class="stat-label">Total XP</span>
411
+ </div>
412
+ </div>
413
+ <div class="stat-card">
414
+ <div class="stat-icon">๐Ÿ”ฅ</div>
415
+ <div class="stat-content">
416
+ <span class="stat-value">${streak.longest}</span>
417
+ <span class="stat-label">Longest Streak</span>
418
+ </div>
419
+ </div>
420
+ <div class="stat-card">
421
+ <div class="stat-icon">๐Ÿ†</div>
422
+ <div class="stat-content">
423
+ <span class="stat-value">${stats.bestVibeScore}%</span>
424
+ <span class="stat-label">Best Score</span>
425
+ </div>
426
+ </div>
427
+ </div>
428
+
429
+ <div class="recent-section" style="margin-top: var(--spacing-xl)">
430
+ <h3 style="margin-bottom: var(--spacing-lg)">Lifetime Stats</h3>
431
+ <div class="sessions-list">
432
+ <div class="session-item">
433
+ <span>Total Sessions</span>
434
+ <span class="stat-value">${stats.totalSessions}</span>
435
+ </div>
436
+ <div class="session-item">
437
+ <span>Commits Analyzed</span>
438
+ <span class="stat-value">${stats.totalCommitsAnalyzed.toLocaleString()}</span>
439
+ </div>
440
+ <div class="session-item">
441
+ <span>Average Score</span>
442
+ <span class="stat-value">${stats.avgVibeScore}%</span>
443
+ </div>
444
+ <div class="session-item">
445
+ <span>Zero-Spiral Sessions</span>
446
+ <span class="stat-value">${stats.spiralsAvoided}</span>
447
+ </div>
448
+ </div>
449
+ </div>
450
+ `;
451
+ }
452
+
453
+ toggleSidebar() {
454
+ document.getElementById('sidebar').classList.toggle('collapsed');
455
+ }
456
+
457
+ async refresh() {
458
+ await this.loadProfile();
459
+ this.renderDashboard();
460
+ this.showToast('Data refreshed!');
461
+ }
462
+
463
+ showToast(message) {
464
+ const toast = document.getElementById('toast');
465
+ document.getElementById('toastMessage').textContent = message;
466
+ toast.classList.add('show');
467
+ setTimeout(() => toast.classList.remove('show'), 3000);
468
+ }
469
+
470
+ closeModal() {
471
+ document.getElementById('achievementModal').classList.remove('show');
472
+ }
473
+
474
+ showAchievementModal(achievement) {
475
+ document.getElementById('modalAchievementIcon').textContent = achievement.icon;
476
+ document.getElementById('modalAchievementName').textContent = achievement.name;
477
+ document.getElementById('modalAchievementDesc').textContent = achievement.description;
478
+ document.getElementById('achievementModal').classList.add('show');
479
+ }
480
+
481
+ getLevelIcon(level) {
482
+ const icons = ['๐ŸŒฑ', '๐ŸŒฟ', '๐ŸŒณ', '๐ŸŒฒ', '๐ŸŽ‹', '๐Ÿ”๏ธ'];
483
+ return icons[level - 1] || '๐ŸŒฑ';
484
+ }
485
+
486
+ formatDate(dateStr) {
487
+ const date = new Date(dateStr);
488
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
489
+ }
490
+ }
491
+
492
+ // Initialize dashboard
493
+ const dashboard = new VibeDashboard();
494
+ document.addEventListener('DOMContentLoaded', () => dashboard.init());