repo-wrapped 0.0.2

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 (51) hide show
  1. package/README.md +94 -0
  2. package/dist/cli.js +24 -0
  3. package/dist/commands/generate.js +95 -0
  4. package/dist/commands/index.js +24 -0
  5. package/dist/constants/chronotypes.js +23 -0
  6. package/dist/constants/colors.js +18 -0
  7. package/dist/constants/index.js +18 -0
  8. package/dist/formatters/index.js +17 -0
  9. package/dist/formatters/timeFormatter.js +29 -0
  10. package/dist/generators/html/scripts/export.js +125 -0
  11. package/dist/generators/html/scripts/knowledge.js +120 -0
  12. package/dist/generators/html/scripts/modal.js +68 -0
  13. package/dist/generators/html/scripts/navigation.js +156 -0
  14. package/dist/generators/html/scripts/tabs.js +18 -0
  15. package/dist/generators/html/scripts/tooltip.js +21 -0
  16. package/dist/generators/html/styles/achievements.css +387 -0
  17. package/dist/generators/html/styles/base.css +818 -0
  18. package/dist/generators/html/styles/components.css +1391 -0
  19. package/dist/generators/html/styles/knowledge.css +221 -0
  20. package/dist/generators/html/templates/achievementsSection.js +156 -0
  21. package/dist/generators/html/templates/commitQualitySection.js +89 -0
  22. package/dist/generators/html/templates/contributionGraph.js +73 -0
  23. package/dist/generators/html/templates/impactSection.js +117 -0
  24. package/dist/generators/html/templates/knowledgeSection.js +226 -0
  25. package/dist/generators/html/templates/streakSection.js +42 -0
  26. package/dist/generators/html/templates/timePatternsSection.js +110 -0
  27. package/dist/generators/html/utils/colorUtils.js +21 -0
  28. package/dist/generators/html/utils/commitMapBuilder.js +24 -0
  29. package/dist/generators/html/utils/dateRangeCalculator.js +57 -0
  30. package/dist/generators/html/utils/developerStatsCalculator.js +29 -0
  31. package/dist/generators/html/utils/scriptLoader.js +16 -0
  32. package/dist/generators/html/utils/styleLoader.js +18 -0
  33. package/dist/generators/html/utils/weekGrouper.js +28 -0
  34. package/dist/index.js +77 -0
  35. package/dist/types/index.js +2 -0
  36. package/dist/utils/achievementDefinitions.js +433 -0
  37. package/dist/utils/achievementEngine.js +170 -0
  38. package/dist/utils/commitQualityAnalyzer.js +368 -0
  39. package/dist/utils/fileHotspotAnalyzer.js +270 -0
  40. package/dist/utils/gitParser.js +125 -0
  41. package/dist/utils/htmlGenerator.js +449 -0
  42. package/dist/utils/impactAnalyzer.js +248 -0
  43. package/dist/utils/knowledgeDistributionAnalyzer.js +374 -0
  44. package/dist/utils/matrixGenerator.js +350 -0
  45. package/dist/utils/slideGenerator.js +313 -0
  46. package/dist/utils/streakCalculator.js +135 -0
  47. package/dist/utils/timePatternAnalyzer.js +305 -0
  48. package/dist/utils/wrappedDisplay.js +115 -0
  49. package/dist/utils/wrappedGenerator.js +377 -0
  50. package/dist/utils/wrappedHtmlGenerator.js +552 -0
  51. package/package.json +55 -0
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.getRepositoryUrl = exports.getRepositoryName = exports.getCurrentGitUser = exports.parseGitCommits = exports.parseCommitData = exports.getCommitHistory = void 0;
27
+ const child_process_1 = require("child_process");
28
+ const path = __importStar(require("path"));
29
+ const GIT_LOG_FORMAT = '--pretty=format:"%H|%an|%aI|%s"';
30
+ const getCommitHistory = (repoPath) => {
31
+ try {
32
+ const output = (0, child_process_1.execSync)(`git log ${GIT_LOG_FORMAT}`, {
33
+ cwd: repoPath,
34
+ encoding: 'utf-8',
35
+ });
36
+ return output;
37
+ }
38
+ catch (error) {
39
+ throw new Error(`Error retrieving commit history: ${error.message}`);
40
+ }
41
+ };
42
+ exports.getCommitHistory = getCommitHistory;
43
+ const parseCommitData = (commitData) => {
44
+ if (!commitData || commitData.trim() === '') {
45
+ return [];
46
+ }
47
+ return commitData.split('\n')
48
+ .filter(line => line.trim())
49
+ .map(line => {
50
+ const parts = line.split('|');
51
+ if (parts.length < 4) {
52
+ return null;
53
+ }
54
+ const [hash, author, date, ...messageParts] = parts;
55
+ const message = messageParts.join('|'); // Rejoin in case message contains |
56
+ return { hash, author, date, message };
57
+ })
58
+ .filter((commit) => commit !== null &&
59
+ Boolean(commit.hash) &&
60
+ Boolean(commit.author) &&
61
+ Boolean(commit.date));
62
+ };
63
+ exports.parseCommitData = parseCommitData;
64
+ const parseGitCommits = (repoPath) => {
65
+ const commitHistory = (0, exports.getCommitHistory)(repoPath);
66
+ return (0, exports.parseCommitData)(commitHistory);
67
+ };
68
+ exports.parseGitCommits = parseGitCommits;
69
+ const getCurrentGitUser = (repoPath) => {
70
+ try {
71
+ const name = (0, child_process_1.execSync)('git config user.name', {
72
+ cwd: repoPath,
73
+ encoding: 'utf-8',
74
+ }).trim();
75
+ return name || 'Unknown';
76
+ }
77
+ catch (error) {
78
+ return 'Unknown';
79
+ }
80
+ };
81
+ exports.getCurrentGitUser = getCurrentGitUser;
82
+ const getRepositoryName = (repoPath) => {
83
+ try {
84
+ // Try to get from remote URL first
85
+ const remoteUrl = (0, child_process_1.execSync)('git config --get remote.origin.url', {
86
+ cwd: repoPath,
87
+ encoding: 'utf-8',
88
+ }).trim();
89
+ if (remoteUrl) {
90
+ // Extract repo name from URL (e.g., "owner/repo" or just "repo")
91
+ const match = remoteUrl.match(/([^\/]+\/[^\/]+?)(\.git)?$/) || remoteUrl.match(/([^\/]+?)(\.git)?$/);
92
+ if (match) {
93
+ return match[1].replace('.git', '');
94
+ }
95
+ }
96
+ }
97
+ catch (error) {
98
+ // If git remote fails, fall back to directory name
99
+ }
100
+ // Fallback to directory name
101
+ return path.basename(path.resolve(repoPath));
102
+ };
103
+ exports.getRepositoryName = getRepositoryName;
104
+ const getRepositoryUrl = (repoPath) => {
105
+ try {
106
+ const remoteUrl = (0, child_process_1.execSync)('git config --get remote.origin.url', {
107
+ cwd: repoPath,
108
+ encoding: 'utf-8',
109
+ }).trim();
110
+ if (remoteUrl) {
111
+ // Convert SSH URL to HTTPS for display
112
+ if (remoteUrl.startsWith('git@')) {
113
+ return remoteUrl
114
+ .replace(/^git@([^:]+):/, 'https://$1/')
115
+ .replace(/\.git$/, '');
116
+ }
117
+ return remoteUrl.replace(/\.git$/, '');
118
+ }
119
+ }
120
+ catch (error) {
121
+ // No remote URL available
122
+ }
123
+ return '';
124
+ };
125
+ exports.getRepositoryUrl = getRepositoryUrl;
@@ -0,0 +1,449 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateHTML = void 0;
4
+ const date_fns_1 = require("date-fns");
5
+ const achievementEngine_1 = require("./achievementEngine");
6
+ const commitQualityAnalyzer_1 = require("./commitQualityAnalyzer");
7
+ const fileHotspotAnalyzer_1 = require("./fileHotspotAnalyzer");
8
+ const impactAnalyzer_1 = require("./impactAnalyzer");
9
+ const knowledgeDistributionAnalyzer_1 = require("./knowledgeDistributionAnalyzer");
10
+ const streakCalculator_1 = require("./streakCalculator");
11
+ const timePatternAnalyzer_1 = require("./timePatternAnalyzer");
12
+ const styleLoader_1 = require("../generators/html/utils/styleLoader");
13
+ const scriptLoader_1 = require("../generators/html/utils/scriptLoader");
14
+ const contributionGraph_1 = require("../generators/html/templates/contributionGraph");
15
+ const streakSection_1 = require("../generators/html/templates/streakSection");
16
+ const timePatternsSection_1 = require("../generators/html/templates/timePatternsSection");
17
+ const commitQualitySection_1 = require("../generators/html/templates/commitQualitySection");
18
+ const achievementsSection_1 = require("../generators/html/templates/achievementsSection");
19
+ const impactSection_1 = require("../generators/html/templates/impactSection");
20
+ const knowledgeSection_1 = require("../generators/html/templates/knowledgeSection");
21
+ const commitMapBuilder_1 = require("../generators/html/utils/commitMapBuilder");
22
+ const developerStatsCalculator_1 = require("../generators/html/utils/developerStatsCalculator");
23
+ const dateRangeCalculator_1 = require("../generators/html/utils/dateRangeCalculator");
24
+ const weekGrouper_1 = require("../generators/html/utils/weekGrouper");
25
+ const colorUtils_1 = require("../generators/html/utils/colorUtils");
26
+ // Helper to format date range for display
27
+ function formatDateRange(start, end) {
28
+ return `${(0, date_fns_1.format)(start, 'MMM d, yyyy')} – ${(0, date_fns_1.format)(end, 'MMM d, yyyy')}`;
29
+ }
30
+ // Helper to build KPI header
31
+ function buildKPIHeader(totalCommits, qualityScore, currentStreak, activeDayPercentage) {
32
+ const healthScore = ((qualityScore / 10) * 0.4 + (activeDayPercentage / 100) * 0.3 + Math.min(currentStreak / 30, 1) * 0.3) * 10;
33
+ return `
34
+ <header class="kpi-header">
35
+ <div class="kpi-card">
36
+ <span class="kpi-label">Total Commits</span>
37
+ <span class="kpi-value">${totalCommits.toLocaleString()}</span>
38
+ </div>
39
+ <div class="kpi-card">
40
+ <span class="kpi-label">Health Score</span>
41
+ <span class="kpi-value">${healthScore.toFixed(1)}</span>
42
+ </div>
43
+ <div class="kpi-card">
44
+ <span class="kpi-label">Current Streak</span>
45
+ <span class="kpi-value">${currentStreak}d</span>
46
+ </div>
47
+ <div class="kpi-card">
48
+ <span class="kpi-label">Quality</span>
49
+ <span class="kpi-value">${qualityScore.toFixed(1)}</span>
50
+ </div>
51
+ <div class="kpi-card">
52
+ <span class="kpi-label">Active Days</span>
53
+ <span class="kpi-value">${activeDayPercentage.toFixed(0)}%</span>
54
+ </div>
55
+ </header>
56
+ `;
57
+ }
58
+ // Helper to wrap content in collapsible section
59
+ function wrapInSection(id, title, content, icon = '') {
60
+ return `
61
+ <section class="dashboard-section" id="${id}">
62
+ <header class="section-header" role="button" aria-expanded="true">
63
+ <h2>${icon ? icon + ' ' : ''}${title}</h2>
64
+ <span class="collapse-icon">▼</span>
65
+ </header>
66
+ <div class="section-content">
67
+ ${content}
68
+ </div>
69
+ </section>
70
+ `;
71
+ }
72
+ function generateHTML(commits, year, monthsToShow, repoPath, repoName, repoUrl, skipBodyCheck = false, allTime = false, deepAnalysis = false) {
73
+ // Get current git user
74
+ const { getCurrentGitUser } = require('./gitParser');
75
+ const currentUser = getCurrentGitUser(repoPath);
76
+ // Calculate date ranges
77
+ const { startDate, endDate, weekStartDate, weekEndDate } = allTime
78
+ ? (0, dateRangeCalculator_1.calculateDateRangesFromCommits)(commits)
79
+ : (0, dateRangeCalculator_1.calculateDateRanges)(year, monthsToShow);
80
+ // Create commit maps for overall and personal commits
81
+ const { commitMap, commitDetailsMap } = (0, commitMapBuilder_1.createCommitMaps)(commits, startDate, endDate);
82
+ const personalCommits = commits.filter(c => c.author === currentUser);
83
+ const { commitMap: personalCommitMap, commitDetailsMap: personalCommitDetailsMap } = (0, commitMapBuilder_1.createCommitMaps)(personalCommits, startDate, endDate);
84
+ // Calculate per-developer statistics
85
+ const developerStats = (0, developerStatsCalculator_1.calculateDeveloperStats)(commits, startDate, endDate);
86
+ // Calculate streak data (overall and personal)
87
+ const streakData = (0, streakCalculator_1.calculateStreaks)(commits, startDate, endDate);
88
+ const personalStreakData = (0, streakCalculator_1.calculateStreaks)(personalCommits, startDate, endDate);
89
+ // Analyze time patterns (overall and personal)
90
+ const timePattern = (0, timePatternAnalyzer_1.analyzeTimePatterns)(commits, startDate, endDate);
91
+ const personalTimePattern = (0, timePatternAnalyzer_1.analyzeTimePatterns)(personalCommits, startDate, endDate);
92
+ // Analyze commit quality (overall and personal)
93
+ const commitQuality = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(commits, { skipBodyCheck });
94
+ const personalCommitQuality = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(personalCommits, { skipBodyCheck });
95
+ // Analyze file hotspots
96
+ const fileHotspots = (0, fileHotspotAnalyzer_1.analyzeFileHotspots)(repoPath, startDate, endDate);
97
+ // Analyze impact and knowledge distribution (enterprise insights)
98
+ const impactAnalysis = (0, impactAnalyzer_1.analyzeImpact)(commits);
99
+ const knowledgeDistribution = (0, knowledgeDistributionAnalyzer_1.analyzeKnowledgeDistribution)(commits, repoPath, deepAnalysis);
100
+ const totalCommits = Array.from(commitMap.values()).reduce((sum, count) => sum + count, 0);
101
+ const totalPersonalCommits = Array.from(personalCommitMap.values()).reduce((sum, count) => sum + count, 0);
102
+ // Prepare analysis data for achievements (overall and personal)
103
+ const analysisData = {
104
+ commits,
105
+ totalCommits,
106
+ streakData,
107
+ timePattern,
108
+ commitQuality,
109
+ fileHotspots,
110
+ dateRange: { start: startDate, end: endDate }
111
+ };
112
+ const personalAnalysisData = {
113
+ commits: personalCommits,
114
+ totalCommits: totalPersonalCommits,
115
+ streakData: personalStreakData,
116
+ timePattern: personalTimePattern,
117
+ commitQuality: personalCommitQuality,
118
+ fileHotspots,
119
+ dateRange: { start: startDate, end: endDate }
120
+ };
121
+ // Check achievements
122
+ const achievementProgress = (0, achievementEngine_1.checkAchievements)(analysisData);
123
+ const personalAchievementProgress = (0, achievementEngine_1.checkAchievements)(personalAnalysisData);
124
+ return generateHTMLContent(commitMap, commitDetailsMap, developerStats, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, personalCommitMap, personalCommitDetailsMap, personalStreakData, personalTimePattern, personalCommitQuality, personalAchievementProgress, weekStartDate, weekEndDate, startDate, endDate, repoName, repoUrl, currentUser, skipBodyCheck, impactAnalysis, knowledgeDistribution);
125
+ }
126
+ exports.generateHTML = generateHTML;
127
+ function generatePersonalContent(personalCommitMap, personalCommitDetailsMap, streakData, timePattern, commitQuality, achievementProgress, weekStartDate, weekEndDate, dataStartDate, dataEndDate, currentUser, totalCommits, skipBodyCheck = false) {
128
+ const weeks = (0, weekGrouper_1.groupDaysIntoWeeks)(weekStartDate, weekEndDate);
129
+ const dayLabels = (0, weekGrouper_1.getDayLabels)();
130
+ // Build KPI header for personal stats
131
+ const kpiHeader = buildKPIHeader(totalCommits, commitQuality.overallScore, streakData.currentStreak.days, streakData.activeDayPercentage);
132
+ // Generate sections using template builders
133
+ const contributionGraphHtml = (0, contributionGraph_1.buildContributionGraph)({
134
+ commitMap: personalCommitMap,
135
+ commitDetailsMap: personalCommitDetailsMap,
136
+ weeks,
137
+ dayLabels,
138
+ dataStartDate,
139
+ dataEndDate,
140
+ totalCommits,
141
+ title: 'Your contributions',
142
+ getColorFn: colorUtils_1.getColor
143
+ });
144
+ const streakSectionHtml = (0, streakSection_1.buildStreakSection)(streakData);
145
+ const timePatternsHtml = (0, timePatternsSection_1.buildTimePatternsSection)(timePattern);
146
+ const commitQualityHtml = (0, commitQualitySection_1.buildCommitQualitySection)(commitQuality, totalCommits, skipBodyCheck);
147
+ const achievementsHtml = (0, achievementsSection_1.buildAchievementsSection)(achievementProgress);
148
+ // Combine all sections with collapsible wrappers
149
+ return `
150
+ ${kpiHeader}
151
+
152
+ ${wrapInSection('personal-overview', 'Contribution Overview', contributionGraphHtml)}
153
+ ${wrapInSection('personal-streaks', 'Streak Analysis', streakSectionHtml)}
154
+ ${wrapInSection('personal-patterns', 'Time Patterns', timePatternsHtml)}
155
+ ${wrapInSection('personal-quality', 'Commit Quality', commitQualityHtml)}
156
+ ${wrapInSection('personal-achievements', 'Achievements', achievementsHtml)}
157
+ `;
158
+ }
159
+ function generateHTMLContent(commitMap, commitDetailsMap, developerStats, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, personalCommitMap, personalCommitDetailsMap, personalStreakData, personalTimePattern, personalCommitQuality, personalAchievementProgress, weekStartDate, weekEndDate, dataStartDate, dataEndDate, repoName, repoUrl, currentUser, skipBodyCheck = false, impactAnalysis, knowledgeDistribution) {
160
+ const totalPersonalCommits = Array.from(personalCommitMap.values()).reduce((sum, count) => sum + count, 0);
161
+ const weeks = (0, weekGrouper_1.groupDaysIntoWeeks)(weekStartDate, weekEndDate);
162
+ // Build month labels and track month boundaries
163
+ let monthLabels = '';
164
+ let currentMonth = '';
165
+ const monthBoundaries = new Set();
166
+ weeks.forEach((week, weekIndex) => {
167
+ const firstDayOfWeek = week[0];
168
+ const monthName = (0, date_fns_1.format)(firstDayOfWeek, 'MMM');
169
+ if (monthName !== currentMonth) {
170
+ currentMonth = monthName;
171
+ if (weekIndex > 0) {
172
+ monthBoundaries.add(weekIndex);
173
+ }
174
+ }
175
+ });
176
+ // Build week columns
177
+ let weekColumns = '';
178
+ const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
179
+ weeks.forEach((week, weekIndex) => {
180
+ let weekColumn = `<div class="graph-column">`;
181
+ week.forEach((day, dayOfWeek) => {
182
+ const dateKey = (0, date_fns_1.format)(day, 'yyyy-MM-dd');
183
+ const count = commitMap.get(dateKey) || 0;
184
+ const isInRange = day >= dataStartDate && day <= dataEndDate;
185
+ const color = isInRange ? (0, colorUtils_1.getColor)(count) : 'transparent';
186
+ const dateStr = (0, date_fns_1.format)(day, 'MMM d, yyyy');
187
+ const emptyClass = count === 0 ? ' empty' : '';
188
+ const clickable = count > 0 ? ' clickable' : '';
189
+ const detailsData = count > 0 ? `data-details='${JSON.stringify(commitDetailsMap.get(dateKey) || [])}'` : '';
190
+ weekColumn += `<div class="day${emptyClass}${clickable}" style="background-color: ${color};" data-count="${count}" data-date="${dateStr}" ${detailsData}></div>`;
191
+ });
192
+ weekColumn += '</div>';
193
+ weekColumns += weekColumn;
194
+ });
195
+ // Build day labels column
196
+ let dayLabelsHtml = '<div class="day-labels">';
197
+ dayLabels.forEach((label, index) => {
198
+ const displayLabel = [1, 3, 5].includes(index) ? label : '';
199
+ dayLabelsHtml += `<div class="day-label">${displayLabel}</div>`;
200
+ });
201
+ dayLabelsHtml += '</div>';
202
+ // Build month labels header
203
+ let monthLabelsHtml = '';
204
+ currentMonth = '';
205
+ weeks.forEach((week, weekIndex) => {
206
+ const firstDayOfWeek = week[0];
207
+ const monthName = (0, date_fns_1.format)(firstDayOfWeek, 'MMM');
208
+ if (monthName !== currentMonth) {
209
+ monthLabelsHtml += `<div class="month-label">${monthName}</div>`;
210
+ currentMonth = monthName;
211
+ }
212
+ else {
213
+ monthLabelsHtml += `<div class="month-label"></div>`;
214
+ }
215
+ });
216
+ const totalCommits = Array.from(commitMap.values()).reduce((sum, count) => sum + count, 0);
217
+ // Sort developers by commit count
218
+ const sortedDevelopers = Array.from(developerStats.entries())
219
+ .sort((a, b) => b[1].commits - a[1].commits);
220
+ // Generate developer analysis HTML
221
+ const developerAnalysisHtml = sortedDevelopers.map(([author, stats]) => {
222
+ const percentage = ((stats.commits / totalCommits) * 100).toFixed(1);
223
+ const dateRange = stats.firstCommit.toDateString() === stats.lastCommit.toDateString()
224
+ ? (0, date_fns_1.format)(stats.firstCommit, 'MMM d, yyyy')
225
+ : `${(0, date_fns_1.format)(stats.firstCommit, 'MMM d, yyyy')} - ${(0, date_fns_1.format)(stats.lastCommit, 'MMM d, yyyy')}`;
226
+ return `
227
+ <div class="developer-row">
228
+ <div class="developer-info">
229
+ <div class="developer-name">${author}</div>
230
+ <div class="developer-date-range">${dateRange}</div>
231
+ </div>
232
+ <div class="developer-stats">
233
+ <div class="developer-bar-container">
234
+ <div class="developer-bar" style="width: ${percentage}%"></div>
235
+ </div>
236
+ <div class="developer-count">${stats.commits} commits (${percentage}%)</div>
237
+ </div>
238
+ </div>
239
+ `;
240
+ }).join('');
241
+ // Build contribution graph HTML for Overall tab
242
+ const contributionGraphHtml = `
243
+ <div class="graph-container">
244
+ <div class="months">
245
+ ${monthLabelsHtml}
246
+ </div>
247
+
248
+ <div class="graph">
249
+ ${dayLabelsHtml}
250
+ ${weekColumns}
251
+ </div>
252
+
253
+ <div class="legend">
254
+ <span>Less</span>
255
+ <span class="legend-box" style="background-color: rgba(235, 237, 240, 0.1); border: 1px solid #21262d;"></span>
256
+ <span class="legend-box" style="background-color: #9be9a8;"></span>
257
+ <span class="legend-box" style="background-color: #40c463;"></span>
258
+ <span class="legend-box" style="background-color: #30a14e;"></span>
259
+ <span class="legend-box" style="background-color: #216e39;"></span>
260
+ <span>More</span>
261
+ </div>
262
+ </div>
263
+ `;
264
+ const developerSectionHtml = `
265
+ <div class="developer-analysis">
266
+ ${developerAnalysisHtml}
267
+ </div>
268
+ `;
269
+ // Build KPI header for Overall tab
270
+ const overallKpiHeader = buildKPIHeader(totalCommits, commitQuality.overallScore, streakData.currentStreak.days, streakData.activeDayPercentage);
271
+ return `<!DOCTYPE html>
272
+ <html lang="en">
273
+ <head>
274
+ <meta charset="UTF-8">
275
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
276
+ <title>Git Wrapped - ${repoName}</title>
277
+ <style>
278
+ ${(0, styleLoader_1.loadStyles)()}
279
+ </style>
280
+ </head>
281
+ <body>
282
+ <!-- Mobile Navigation Toggle -->
283
+ <button class="mobile-nav-toggle" aria-label="Toggle navigation">
284
+ <span class="hamburger-icon"></span>
285
+ </button>
286
+
287
+ <div class="dashboard-layout">
288
+ <!-- Sidebar Navigation -->
289
+ <nav class="sidebar" role="navigation" aria-label="Main navigation">
290
+ <div class="sidebar-header">
291
+ <h2>Git Wrapped</h2>
292
+ <span class="version">v1.0</span>
293
+ </div>
294
+
295
+ <div class="nav-section">
296
+ <div class="nav-section-title">Views</div>
297
+ <a href="#" class="nav-item active" data-tab="overall">
298
+ <span class="nav-icon">📊</span>
299
+ <span class="nav-label">Team Overview</span>
300
+ </a>
301
+ <a href="#" class="nav-item" data-tab="personal">
302
+ <span class="nav-icon">👤</span>
303
+ <span class="nav-label">Personal Stats</span>
304
+ </a>
305
+ </div>
306
+
307
+ <div class="nav-section">
308
+ <div class="nav-section-title">Quick Jump</div>
309
+ <a href="#contributions" class="nav-item nav-item-small">
310
+ <span class="nav-label">Contributions</span>
311
+ </a>
312
+ <a href="#personal-streaks" class="nav-item nav-item-small" data-tab="personal">
313
+ <span class="nav-label">Streaks</span>
314
+ </a>
315
+ <a href="#personal-patterns" class="nav-item nav-item-small" data-tab="personal">
316
+ <span class="nav-label">Patterns</span>
317
+ </a>
318
+ <a href="#personal-quality" class="nav-item nav-item-small" data-tab="personal">
319
+ <span class="nav-label">Quality</span>
320
+ </a>
321
+ <a href="#personal-achievements" class="nav-item nav-item-small" data-tab="personal">
322
+ <span class="nav-label">Achievements</span>
323
+ </a>
324
+ </div>
325
+
326
+ <div class="sidebar-footer">
327
+ <div class="repo-info">
328
+ <span class="repo-name">${repoName}</span>
329
+ ${repoUrl ? `<a href="${repoUrl}" target="_blank" class="repo-link">View Repository →</a>` : ''}
330
+ </div>
331
+ </div>
332
+ </nav>
333
+
334
+ <!-- Main Content Area -->
335
+ <main class="main-content">
336
+ <div class="container">
337
+ <header class="dashboard-header">
338
+ <div class="header-left">
339
+ <h1 class="repo-name">${repoName}</h1>
340
+ </div>
341
+ <div class="header-right">
342
+ <div class="export-controls">
343
+ <button class="btn btn-secondary btn-sm" data-export="json">Export JSON</button>
344
+ <button class="btn btn-secondary btn-sm" data-export="csv">Export CSV</button>
345
+ </div>
346
+ </div>
347
+ </header>
348
+
349
+ <div class="filter-bar">
350
+ <div class="filter-group">
351
+ <label class="filter-label">Period</label>
352
+ <span class="filter-value">${formatDateRange(dataStartDate, dataEndDate)}</span>
353
+ </div>
354
+ <div class="filter-group">
355
+ <label class="filter-label">Author</label>
356
+ <span class="filter-value">${currentUser !== 'Unknown' ? currentUser : 'All Contributors'}</span>
357
+ </div>
358
+ </div>
359
+
360
+ <!-- Tab Navigation (kept for backwards compatibility) -->
361
+ <div class="tab-nav">
362
+ <button class="tab-button active" data-tab="overall">📊 Team Overview</button>
363
+ <button class="tab-button" data-tab="personal">👤 Personal</button>
364
+ </div>
365
+
366
+ <!-- Overall Tab Content -->
367
+ <div class="tab-content active" id="overall-content">
368
+ ${overallKpiHeader}
369
+
370
+ ${wrapInSection('contributions', 'Contribution Activity', contributionGraphHtml)}
371
+ ${wrapInSection('team-developers', 'Developer Contributions', developerSectionHtml)}
372
+ ${impactAnalysis ? wrapInSection('impact', 'Developer Impact', (0, impactSection_1.buildImpactSection)(impactAnalysis)) : ''}
373
+ ${knowledgeDistribution ? wrapInSection('knowledge', 'Knowledge Distribution', (0, knowledgeSection_1.buildKnowledgeSection)(knowledgeDistribution)) : ''}
374
+ </div>
375
+ <!-- End Overall Tab Content -->
376
+
377
+ <!-- Personal Tab Content -->
378
+ <div class="tab-content" id="personal-content">
379
+ ${generatePersonalContent(personalCommitMap, personalCommitDetailsMap, personalStreakData, personalTimePattern, personalCommitQuality, personalAchievementProgress, weekStartDate, weekEndDate, dataStartDate, dataEndDate, currentUser, totalPersonalCommits, skipBodyCheck)}
380
+ </div>
381
+ <!-- End Personal Tab Content -->
382
+
383
+ </div>
384
+ </main>
385
+ </div>
386
+
387
+ <div class="tooltip" id="tooltip"></div>
388
+
389
+ <div class="modal" id="modal">
390
+ <div class="modal-content">
391
+ <div class="modal-header">
392
+ <div class="modal-title" id="modal-title"></div>
393
+ <button class="modal-close" id="modal-close">&times;</button>
394
+ </div>
395
+ <div class="commit-list" id="commit-list"></div>
396
+ </div>
397
+ </div>
398
+
399
+ <script>
400
+ // Embed data for export functionality
401
+ window.__GITWRAPPED_DATA__ = ${JSON.stringify({
402
+ repository: repoName,
403
+ period: {
404
+ start: dataStartDate.toISOString(),
405
+ end: dataEndDate.toISOString()
406
+ },
407
+ author: currentUser,
408
+ summary: {
409
+ totalCommits: totalCommits,
410
+ totalContributors: developerStats.size,
411
+ activeDays: commitMap.size
412
+ },
413
+ streaks: {
414
+ currentStreak: streakData.currentStreak.days,
415
+ longestStreak: streakData.longestStreak.days,
416
+ totalActiveDays: streakData.totalActiveDays,
417
+ activeDayPercentage: streakData.activeDayPercentage
418
+ },
419
+ quality: {
420
+ overallScore: commitQuality.overallScore,
421
+ conventionalAdherence: commitQuality.conventionalCommits.adherence,
422
+ avgSubjectLength: commitQuality.subjectQuality.avgLength
423
+ },
424
+ timePattern: {
425
+ chronotype: timePattern.chronotype,
426
+ peakHour: timePattern.peakHour.hour,
427
+ consistency: timePattern.consistency?.score || 0
428
+ },
429
+ commits: Array.from(commitDetailsMap.values()).flat().map((c) => ({
430
+ date: c.date,
431
+ author: c.author,
432
+ message: c.message.split('\\n')[0],
433
+ hash: c.hash
434
+ })),
435
+ developerStats: Array.from(developerStats.entries()).map(([name, stats]) => ({
436
+ name,
437
+ commits: stats.commits,
438
+ firstCommit: stats.firstCommit.toISOString(),
439
+ lastCommit: stats.lastCommit.toISOString()
440
+ }))
441
+ })};
442
+ </script>
443
+
444
+ <script>
445
+ ${(0, scriptLoader_1.loadScripts)()}
446
+ </script>
447
+ </body>
448
+ </html>`;
449
+ }