Skip to content

Commit 6adc60c

Browse files
Bhuvanesh SBhuvanesh S
authored andcommitted
feat: add Contribution Velocity Predictor dashboard
Signed-off-by: Bhuvanesh S <YOUR_GITHUB_EMAIL>
1 parent 02320c0 commit 6adc60c

10 files changed

Lines changed: 1777 additions & 0 deletions
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<script lang="ts">
2+
import { onMount } from 'svelte';
3+
4+
// Generate some dummy data for the heatmap
5+
// 52 weeks * 7 days
6+
const weeks = 52;
7+
const daysPerWeek = 7;
8+
9+
let heatmapData = $state<{ intensity: number, date: Date }[][]>([]);
10+
11+
onMount(() => {
12+
const today = new Date();
13+
const generatedData = [];
14+
for (let w = 0; w < weeks; w++) {
15+
let weekData = [];
16+
for (let d = 0; d < daysPerWeek; d++) {
17+
const date = new Date(today);
18+
date.setDate(date.getDate() - ((weeks - 1 - w) * 7 + (6 - d)));
19+
const random = Math.random();
20+
let intensity = 0;
21+
if (random > 0.85) intensity = 4;
22+
else if (random > 0.7) intensity = 3;
23+
else if (random > 0.5) intensity = 2;
24+
else if (random > 0.3) intensity = 1;
25+
26+
weekData.push({ intensity, date });
27+
}
28+
generatedData.push(weekData);
29+
}
30+
heatmapData = generatedData;
31+
});
32+
33+
function getIntensityColor(intensity: number): string {
34+
switch(intensity) {
35+
case 1: return 'var(--color-level-1)';
36+
case 2: return 'var(--color-level-2)';
37+
case 3: return 'var(--color-level-3)';
38+
case 4: return 'var(--color-level-4)';
39+
default: return 'var(--color-level-0)';
40+
}
41+
}
42+
</script>
43+
44+
<div class="heatmap-container glass">
45+
<div class="heatmap-header">
46+
<h3>Realtime Contributor Activity</h3>
47+
<span class="pulse-indicator">
48+
<span class="pulse-dot"></span> Live
49+
</span>
50+
</div>
51+
52+
<div class="heatmap-scroll-wrapper">
53+
<div class="heatmap-grid">
54+
{#each heatmapData as week, wIndex}
55+
<div class="week">
56+
{#each week as day, dIndex}
57+
<div
58+
class="day"
59+
role="button"
60+
aria-label="Contribution activity on {day.date.toDateString()}"
61+
tabindex="0"
62+
style="background-color: {getIntensityColor(day.intensity)};"
63+
title="{day.date.toDateString()}: {day.intensity * 3} contributions"
64+
></div>
65+
{/each}
66+
</div>
67+
{/each}
68+
</div>
69+
</div>
70+
71+
<div class="heatmap-legend">
72+
<span class="legend-text">Less</span>
73+
<div class="day" role="button" aria-label="Contribution activity" style="background-color: var(--color-level-0);"></div>
74+
<div class="day" role="button" aria-label="Contribution activity" style="background-color: var(--color-level-1);"></div>
75+
<div class="day" role="button" aria-label="Contribution activity" style="background-color: var(--color-level-2);"></div>
76+
<div class="day" role="button" aria-label="Contribution activity" style="background-color: var(--color-level-3);"></div>
77+
<div class="day" role="button" aria-label="Contribution activity" style="background-color: var(--color-level-4);"></div>
78+
<span class="legend-text">More</span>
79+
</div>
80+
</div>
81+
82+
<style>
83+
.heatmap-container {
84+
--color-level-0: rgba(100, 116, 139, 0.1);
85+
--color-level-1: rgba(99, 102, 241, 0.4);
86+
--color-level-2: rgba(99, 102, 241, 0.6);
87+
--color-level-3: rgba(99, 102, 241, 0.8);
88+
--color-level-4: var(--primary);
89+
90+
padding: 1.5rem;
91+
border-radius: var(--radius-lg);
92+
display: flex;
93+
flex-direction: column;
94+
gap: 1.5rem;
95+
}
96+
97+
.heatmap-header {
98+
display: flex;
99+
justify-content: space-between;
100+
align-items: center;
101+
}
102+
103+
.heatmap-header h3 {
104+
font-size: 1.125rem;
105+
color: var(--text-primary);
106+
}
107+
108+
.pulse-indicator {
109+
display: flex;
110+
align-items: center;
111+
gap: 0.5rem;
112+
font-size: 0.875rem;
113+
color: var(--text-muted);
114+
font-weight: 500;
115+
}
116+
117+
.pulse-dot {
118+
width: 8px;
119+
height: 8px;
120+
background-color: #22c55e;
121+
border-radius: 50%;
122+
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7);
123+
animation: pulse 2s infinite;
124+
}
125+
126+
@keyframes pulse {
127+
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7); }
128+
70% { transform: scale(1); box-shadow: 0 0 0 6px rgba(34, 197, 94, 0); }
129+
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
130+
}
131+
132+
.heatmap-scroll-wrapper {
133+
overflow-x: auto;
134+
padding-bottom: 0.5rem;
135+
}
136+
137+
/* Custom scrollbar for heatmap */
138+
.heatmap-scroll-wrapper::-webkit-scrollbar {
139+
height: 6px;
140+
}
141+
.heatmap-scroll-wrapper::-webkit-scrollbar-track {
142+
background: var(--bg-secondary);
143+
border-radius: 999px;
144+
}
145+
.heatmap-scroll-wrapper::-webkit-scrollbar-thumb {
146+
background: rgba(99, 102, 241, 0.3);
147+
border-radius: 999px;
148+
}
149+
150+
.heatmap-grid {
151+
display: flex;
152+
gap: 4px;
153+
min-width: max-content;
154+
}
155+
156+
.week {
157+
display: flex;
158+
flex-direction: column;
159+
gap: 4px;
160+
}
161+
162+
.day {
163+
width: 14px;
164+
height: 14px;
165+
border-radius: 3px;
166+
border: 1px solid rgba(255, 255, 255, 0.05);
167+
transition: transform 0.2s ease, box-shadow 0.2s ease;
168+
cursor: pointer;
169+
}
170+
171+
.day:hover {
172+
transform: scale(1.2);
173+
box-shadow: 0 0 8px var(--primary-glow);
174+
z-index: 10;
175+
}
176+
177+
.heatmap-legend {
178+
display: flex;
179+
align-items: center;
180+
gap: 0.5rem;
181+
align-self: flex-end;
182+
font-size: 0.75rem;
183+
color: var(--text-muted);
184+
}
185+
186+
.legend-text {
187+
margin: 0 0.25rem;
188+
}
189+
</style>
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<script lang="ts">
2+
3+
let {
4+
title,
5+
value,
6+
trend,
7+
isPositive = true,
8+
icon = '📊'
9+
} = $props<{
10+
title: string;
11+
value: string;
12+
trend: string;
13+
isPositive?: boolean;
14+
icon?: string;
15+
}>();
16+
</script>
17+
18+
<div class="analytics-widget glass">
19+
<div class="widget-header">
20+
<div class="icon-wrapper">
21+
<span class="icon">{icon}</span>
22+
</div>
23+
<span class="trend {isPositive ? 'positive' : 'negative'}">
24+
{isPositive ? '' : ''} {trend}
25+
</span>
26+
</div>
27+
28+
<div class="widget-content">
29+
<h3 class="title">{title}</h3>
30+
<div class="value gradient-text">{value}</div>
31+
</div>
32+
</div>
33+
34+
<style>
35+
.analytics-widget {
36+
padding: 1.5rem;
37+
border-radius: var(--radius-lg);
38+
display: flex;
39+
flex-direction: column;
40+
gap: 1rem;
41+
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease;
42+
cursor: default;
43+
position: relative;
44+
overflow: hidden;
45+
}
46+
47+
.analytics-widget::before {
48+
content: '';
49+
position: absolute;
50+
top: 0;
51+
left: 0;
52+
right: 0;
53+
bottom: 0;
54+
background: radial-gradient(circle at top right, var(--primary-glow), transparent 70%);
55+
opacity: 0;
56+
transition: opacity 0.3s ease;
57+
pointer-events: none;
58+
}
59+
60+
.analytics-widget:hover {
61+
transform: translateY(-4px);
62+
box-shadow: var(--shadow-lg);
63+
}
64+
65+
.analytics-widget:hover::before {
66+
opacity: 1;
67+
}
68+
69+
.widget-header {
70+
display: flex;
71+
justify-content: space-between;
72+
align-items: center;
73+
}
74+
75+
.icon-wrapper {
76+
width: 42px;
77+
height: 42px;
78+
border-radius: 50%;
79+
background: var(--bg-secondary);
80+
display: flex;
81+
align-items: center;
82+
justify-content: center;
83+
border: 1px solid var(--border-glass);
84+
}
85+
86+
.icon {
87+
font-size: 1.25rem;
88+
}
89+
90+
.trend {
91+
font-size: 0.875rem;
92+
font-weight: 600;
93+
padding: 0.25rem 0.75rem;
94+
border-radius: 999px;
95+
}
96+
97+
.trend.positive {
98+
background: rgba(34, 197, 94, 0.15);
99+
color: #22c55e;
100+
}
101+
102+
.trend.negative {
103+
background: rgba(239, 68, 68, 0.15);
104+
color: #ef4444;
105+
}
106+
107+
.widget-content {
108+
display: flex;
109+
flex-direction: column;
110+
gap: 0.25rem;
111+
}
112+
113+
.title {
114+
font-size: 0.875rem;
115+
color: var(--text-muted);
116+
text-transform: uppercase;
117+
letter-spacing: 0.05em;
118+
font-family: 'Inter', sans-serif;
119+
}
120+
121+
.value {
122+
font-size: 2.25rem;
123+
font-weight: 800;
124+
font-family: 'Outfit', sans-serif;
125+
}
126+
</style>

0 commit comments

Comments
 (0)