claude-roi 0.2.3 → 0.3.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.
- package/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/package.json +1 -1
- package/skills-lock.json +10 -0
- package/src/claude-parser.js +2 -2
- package/src/dashboard.html +827 -188
- package/src/index.js +43 -40
- package/src/metrics.js +10 -2
- package/src/server.js +18 -1
package/src/dashboard.html
CHANGED
|
@@ -4,59 +4,214 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Codelens AI — Agent Productivity Dashboard</title>
|
|
7
|
-
<link href="https://fonts.googleapis.com/css2?family=
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&family=JetBrains+Mono:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
8
8
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
|
|
9
|
+
<script>
|
|
10
|
+
(function() {
|
|
11
|
+
var pref = localStorage.getItem('codelens-theme') || 'system';
|
|
12
|
+
var resolved = pref === 'system'
|
|
13
|
+
? (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark')
|
|
14
|
+
: pref;
|
|
15
|
+
if (resolved === 'light') document.documentElement.setAttribute('data-theme', 'light');
|
|
16
|
+
})();
|
|
17
|
+
</script>
|
|
9
18
|
<style>
|
|
10
19
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
11
20
|
|
|
21
|
+
@property --grade-deg {
|
|
22
|
+
syntax: '<angle>';
|
|
23
|
+
initial-value: 0deg;
|
|
24
|
+
inherits: false;
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
:root {
|
|
13
|
-
--bg-primary: #
|
|
14
|
-
--bg-secondary: #
|
|
15
|
-
--bg-card: #
|
|
16
|
-
--bg-hover: #
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
-
--text-
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
--accent-
|
|
24
|
-
--accent-
|
|
25
|
-
--accent-
|
|
26
|
-
--accent-
|
|
27
|
-
--
|
|
28
|
-
--
|
|
29
|
-
--grade-
|
|
28
|
+
--bg-primary: #0a0e17;
|
|
29
|
+
--bg-secondary: #111827;
|
|
30
|
+
--bg-card: #151d2b;
|
|
31
|
+
--bg-hover: #1e2a3a;
|
|
32
|
+
--bg-elevated: #1a2332;
|
|
33
|
+
--border: #1f2d3d;
|
|
34
|
+
--border-subtle: #162030;
|
|
35
|
+
--text-primary: #f0f4f8;
|
|
36
|
+
--text-secondary: #94a3b8;
|
|
37
|
+
--text-muted: #64748b;
|
|
38
|
+
--accent-green: #22d3a8;
|
|
39
|
+
--accent-red: #ef4444;
|
|
40
|
+
--accent-orange: #f59e0b;
|
|
41
|
+
--accent-blue: #3b82f6;
|
|
42
|
+
--accent-purple: #a855f7;
|
|
43
|
+
--accent-cyan: #06b6d4;
|
|
44
|
+
--grade-a: #22d3a8;
|
|
45
|
+
--grade-b: #3b82f6;
|
|
46
|
+
--grade-c: #f59e0b;
|
|
30
47
|
--grade-d: #f0883e;
|
|
31
|
-
--grade-f: #
|
|
48
|
+
--grade-f: #ef4444;
|
|
49
|
+
--glow-green: rgba(34, 211, 168, 0.15);
|
|
50
|
+
--glow-blue: rgba(59, 130, 246, 0.15);
|
|
51
|
+
--glow-purple: rgba(168, 85, 247, 0.15);
|
|
52
|
+
--glow-orange: rgba(245, 158, 11, 0.15);
|
|
53
|
+
--glow-red: rgba(239, 68, 68, 0.15);
|
|
32
54
|
--radius: 12px;
|
|
33
55
|
--radius-sm: 8px;
|
|
56
|
+
--font-display: 'JetBrains Mono', 'SF Mono', monospace;
|
|
57
|
+
--font-body: 'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
58
|
+
|
|
59
|
+
/* Glassmorphism & overlay tokens */
|
|
60
|
+
--glass-bg-from: rgba(21, 29, 43, 0.8);
|
|
61
|
+
--glass-bg-to: rgba(15, 23, 35, 0.6);
|
|
62
|
+
--glass-border: rgba(255, 255, 255, 0.05);
|
|
63
|
+
--glass-border-hover: rgba(255, 255, 255, 0.1);
|
|
64
|
+
--overlay-subtle: rgba(255, 255, 255, 0.01);
|
|
65
|
+
--overlay-light: rgba(255, 255, 255, 0.02);
|
|
66
|
+
--overlay-soft: rgba(255, 255, 255, 0.03);
|
|
67
|
+
--overlay-medium: rgba(255, 255, 255, 0.04);
|
|
68
|
+
--overlay-strong: rgba(255, 255, 255, 0.06);
|
|
69
|
+
--overlay-intense: rgba(255, 255, 255, 0.08);
|
|
70
|
+
--shadow-card: rgba(0, 0, 0, 0.3);
|
|
71
|
+
--shadow-card-hover: rgba(0, 0, 0, 0.4);
|
|
72
|
+
--shadow-medium: rgba(0, 0, 0, 0.2);
|
|
73
|
+
--shadow-elevated: rgba(0, 0, 0, 0.25);
|
|
74
|
+
--tooltip-bg: rgba(10, 14, 23, 0.95);
|
|
75
|
+
--tooltip-border: rgba(255, 255, 255, 0.06);
|
|
76
|
+
--noise-opacity: 0.015;
|
|
77
|
+
--funnel-text: rgba(255, 255, 255, 0.9);
|
|
78
|
+
|
|
79
|
+
/* Grade background tokens */
|
|
80
|
+
--grade-a-bg: rgba(34, 211, 168, 0.13);
|
|
81
|
+
--grade-b-bg: rgba(59, 130, 246, 0.13);
|
|
82
|
+
--grade-c-bg: rgba(245, 158, 11, 0.13);
|
|
83
|
+
--grade-d-bg: rgba(240, 136, 62, 0.13);
|
|
84
|
+
--grade-f-bg: rgba(239, 68, 68, 0.13);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* ── Light Theme ─────────────────────────────── */
|
|
88
|
+
[data-theme="light"] {
|
|
89
|
+
--bg-primary: #f8fafc;
|
|
90
|
+
--bg-secondary: #f1f5f9;
|
|
91
|
+
--bg-card: #ffffff;
|
|
92
|
+
--bg-hover: #e2e8f0;
|
|
93
|
+
--bg-elevated: #f1f5f9;
|
|
94
|
+
--border: #cbd5e1;
|
|
95
|
+
--border-subtle: #e2e8f0;
|
|
96
|
+
--text-primary: #0f172a;
|
|
97
|
+
--text-secondary: #475569;
|
|
98
|
+
--text-muted: #64748b;
|
|
99
|
+
--accent-green: #0d9488;
|
|
100
|
+
--accent-red: #dc2626;
|
|
101
|
+
--accent-orange: #d97706;
|
|
102
|
+
--accent-blue: #2563eb;
|
|
103
|
+
--accent-purple: #7c3aed;
|
|
104
|
+
--accent-cyan: #0891b2;
|
|
105
|
+
--grade-a: #0d9488;
|
|
106
|
+
--grade-b: #2563eb;
|
|
107
|
+
--grade-c: #d97706;
|
|
108
|
+
--grade-d: #ea580c;
|
|
109
|
+
--grade-f: #dc2626;
|
|
110
|
+
--glow-green: rgba(13, 148, 136, 0.1);
|
|
111
|
+
--glow-blue: rgba(37, 99, 235, 0.1);
|
|
112
|
+
--glow-purple: rgba(124, 58, 237, 0.1);
|
|
113
|
+
--glow-orange: rgba(217, 119, 6, 0.1);
|
|
114
|
+
--glow-red: rgba(220, 38, 38, 0.1);
|
|
115
|
+
--glass-bg-from: rgba(255, 255, 255, 0.7);
|
|
116
|
+
--glass-bg-to: rgba(241, 245, 249, 0.5);
|
|
117
|
+
--glass-border: rgba(0, 0, 0, 0.06);
|
|
118
|
+
--glass-border-hover: rgba(0, 0, 0, 0.12);
|
|
119
|
+
--overlay-subtle: rgba(0, 0, 0, 0.01);
|
|
120
|
+
--overlay-light: rgba(0, 0, 0, 0.02);
|
|
121
|
+
--overlay-soft: rgba(0, 0, 0, 0.03);
|
|
122
|
+
--overlay-medium: rgba(0, 0, 0, 0.04);
|
|
123
|
+
--overlay-strong: rgba(0, 0, 0, 0.06);
|
|
124
|
+
--overlay-intense: rgba(0, 0, 0, 0.08);
|
|
125
|
+
--shadow-card: rgba(0, 0, 0, 0.06);
|
|
126
|
+
--shadow-card-hover: rgba(0, 0, 0, 0.1);
|
|
127
|
+
--shadow-medium: rgba(0, 0, 0, 0.08);
|
|
128
|
+
--shadow-elevated: rgba(0, 0, 0, 0.12);
|
|
129
|
+
--tooltip-bg: rgba(15, 23, 42, 0.92);
|
|
130
|
+
--tooltip-border: rgba(255, 255, 255, 0.1);
|
|
131
|
+
--noise-opacity: 0.008;
|
|
132
|
+
--funnel-text: rgba(0, 0, 0, 0.8);
|
|
133
|
+
--grade-a-bg: rgba(13, 148, 136, 0.1);
|
|
134
|
+
--grade-b-bg: rgba(37, 99, 235, 0.1);
|
|
135
|
+
--grade-c-bg: rgba(217, 119, 6, 0.1);
|
|
136
|
+
--grade-d-bg: rgba(234, 88, 12, 0.1);
|
|
137
|
+
--grade-f-bg: rgba(220, 38, 38, 0.1);
|
|
34
138
|
}
|
|
35
139
|
|
|
36
140
|
body {
|
|
37
|
-
font-family:
|
|
38
|
-
background:
|
|
141
|
+
font-family: var(--font-body);
|
|
142
|
+
background:
|
|
143
|
+
radial-gradient(ellipse 80% 50% at 50% -20%, rgba(59, 130, 246, 0.03), transparent),
|
|
144
|
+
radial-gradient(ellipse 60% 40% at 100% 50%, rgba(168, 85, 247, 0.02), transparent),
|
|
145
|
+
radial-gradient(ellipse 50% 30% at 0% 80%, rgba(34, 211, 168, 0.02), transparent),
|
|
146
|
+
var(--bg-primary);
|
|
39
147
|
color: var(--text-primary);
|
|
40
148
|
line-height: 1.6;
|
|
41
149
|
min-height: 100vh;
|
|
42
150
|
}
|
|
151
|
+
body::after {
|
|
152
|
+
content: '';
|
|
153
|
+
position: fixed;
|
|
154
|
+
inset: 0;
|
|
155
|
+
pointer-events: none;
|
|
156
|
+
z-index: 9999;
|
|
157
|
+
opacity: var(--noise-opacity);
|
|
158
|
+
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
|
159
|
+
background-repeat: repeat;
|
|
160
|
+
background-size: 256px 256px;
|
|
161
|
+
}
|
|
43
162
|
|
|
44
|
-
.container { max-width:
|
|
163
|
+
.container { max-width: 1320px; margin: 0 auto; padding: 32px; position: relative; z-index: 1; }
|
|
45
164
|
|
|
46
165
|
/* Header */
|
|
47
166
|
header {
|
|
48
167
|
text-align: center;
|
|
49
|
-
padding:
|
|
50
|
-
border-bottom:
|
|
51
|
-
margin-bottom:
|
|
168
|
+
padding: 56px 0 40px;
|
|
169
|
+
border-bottom: none;
|
|
170
|
+
margin-bottom: 40px;
|
|
171
|
+
position: relative;
|
|
172
|
+
overflow: hidden;
|
|
173
|
+
}
|
|
174
|
+
header::before {
|
|
175
|
+
content: '';
|
|
176
|
+
position: absolute;
|
|
177
|
+
inset: 0;
|
|
178
|
+
background:
|
|
179
|
+
radial-gradient(ellipse 60% 50% at 20% 50%, rgba(59, 130, 246, 0.08), transparent),
|
|
180
|
+
radial-gradient(ellipse 50% 60% at 80% 30%, rgba(168, 85, 247, 0.06), transparent),
|
|
181
|
+
radial-gradient(ellipse 40% 40% at 50% 80%, rgba(34, 211, 168, 0.04), transparent);
|
|
182
|
+
z-index: 0;
|
|
183
|
+
animation: headerShift 20s ease-in-out infinite alternate;
|
|
184
|
+
}
|
|
185
|
+
@keyframes headerShift {
|
|
186
|
+
0% { opacity: 0.7; transform: scale(1); }
|
|
187
|
+
100% { opacity: 1; transform: scale(1.05); }
|
|
188
|
+
}
|
|
189
|
+
header > * { position: relative; z-index: 1; }
|
|
190
|
+
header::after {
|
|
191
|
+
content: '';
|
|
192
|
+
position: absolute;
|
|
193
|
+
bottom: 0;
|
|
194
|
+
left: 10%;
|
|
195
|
+
right: 10%;
|
|
196
|
+
height: 1px;
|
|
197
|
+
background: linear-gradient(90deg, transparent, var(--border), transparent);
|
|
198
|
+
z-index: 1;
|
|
52
199
|
}
|
|
53
200
|
header h1 {
|
|
54
|
-
font-
|
|
55
|
-
font-
|
|
56
|
-
|
|
201
|
+
font-family: var(--font-display);
|
|
202
|
+
font-size: 2.8rem;
|
|
203
|
+
font-weight: 800;
|
|
204
|
+
letter-spacing: -0.02em;
|
|
205
|
+
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple), var(--accent-cyan));
|
|
206
|
+
background-size: 200% 200%;
|
|
57
207
|
-webkit-background-clip: text;
|
|
58
208
|
-webkit-text-fill-color: transparent;
|
|
59
|
-
|
|
209
|
+
animation: gradientMove 8s ease-in-out infinite;
|
|
210
|
+
margin-bottom: 12px;
|
|
211
|
+
}
|
|
212
|
+
@keyframes gradientMove {
|
|
213
|
+
0%, 100% { background-position: 0% 50%; }
|
|
214
|
+
50% { background-position: 100% 50%; }
|
|
60
215
|
}
|
|
61
216
|
header .tagline {
|
|
62
217
|
color: var(--text-secondary);
|
|
@@ -73,21 +228,40 @@
|
|
|
73
228
|
color: var(--text-muted);
|
|
74
229
|
}
|
|
75
230
|
.meta-info .badge {
|
|
76
|
-
background: var(--
|
|
77
|
-
|
|
231
|
+
background: var(--overlay-medium);
|
|
232
|
+
backdrop-filter: blur(12px);
|
|
233
|
+
border: 1px solid var(--overlay-strong);
|
|
78
234
|
border-radius: 20px;
|
|
79
|
-
padding:
|
|
235
|
+
padding: 5px 14px;
|
|
236
|
+
transition: background 0.3s;
|
|
237
|
+
}
|
|
238
|
+
.meta-info .badge:hover {
|
|
239
|
+
background: var(--overlay-intense);
|
|
80
240
|
}
|
|
81
241
|
|
|
82
242
|
/* Shared section heading style */
|
|
83
243
|
.stats-section {
|
|
84
|
-
margin-bottom:
|
|
244
|
+
margin-bottom: 48px;
|
|
85
245
|
}
|
|
86
246
|
.stats-section h2 {
|
|
87
|
-
font-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
247
|
+
font-family: var(--font-display);
|
|
248
|
+
font-size: 0.85rem;
|
|
249
|
+
margin-bottom: 16px;
|
|
250
|
+
color: var(--text-muted);
|
|
251
|
+
font-weight: 600;
|
|
252
|
+
text-transform: uppercase;
|
|
253
|
+
letter-spacing: 0.12em;
|
|
254
|
+
display: flex;
|
|
255
|
+
align-items: center;
|
|
256
|
+
gap: 8px;
|
|
257
|
+
}
|
|
258
|
+
.stats-section h2::before {
|
|
259
|
+
content: '';
|
|
260
|
+
display: inline-block;
|
|
261
|
+
width: 3px;
|
|
262
|
+
height: 16px;
|
|
263
|
+
background: var(--accent-blue);
|
|
264
|
+
border-radius: 2px;
|
|
91
265
|
}
|
|
92
266
|
|
|
93
267
|
/* Hero Stats — 3+1 layout */
|
|
@@ -103,33 +277,56 @@
|
|
|
103
277
|
gap: 12px;
|
|
104
278
|
}
|
|
105
279
|
.stat-card {
|
|
106
|
-
background: var(--bg-
|
|
107
|
-
|
|
280
|
+
background: linear-gradient(145deg, var(--glass-bg-from), var(--glass-bg-to));
|
|
281
|
+
backdrop-filter: blur(16px);
|
|
282
|
+
border: 1px solid var(--glass-border);
|
|
108
283
|
border-radius: var(--radius);
|
|
109
284
|
padding: 20px 24px;
|
|
110
285
|
display: flex;
|
|
111
286
|
align-items: center;
|
|
112
287
|
gap: 16px;
|
|
113
|
-
transition: transform 0.
|
|
288
|
+
transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
|
289
|
+
border-color 0.3s,
|
|
290
|
+
box-shadow 0.3s;
|
|
114
291
|
position: relative;
|
|
115
292
|
overflow: visible;
|
|
116
293
|
text-align: left;
|
|
117
294
|
}
|
|
118
295
|
.stat-card:hover {
|
|
119
|
-
transform: translateY(-
|
|
120
|
-
border-color: var(--
|
|
296
|
+
transform: translateY(-3px);
|
|
297
|
+
border-color: var(--glass-border-hover);
|
|
298
|
+
box-shadow: 0 8px 32px var(--shadow-card),
|
|
299
|
+
0 0 0 1px var(--glass-border);
|
|
121
300
|
}
|
|
122
301
|
.stat-card.glow::before {
|
|
123
302
|
content: '';
|
|
124
303
|
position: absolute;
|
|
125
304
|
top: 0; left: 0; right: 0;
|
|
126
|
-
height:
|
|
305
|
+
height: 2px;
|
|
127
306
|
border-radius: var(--radius) var(--radius) 0 0;
|
|
128
307
|
}
|
|
308
|
+
.stat-card.glow::after {
|
|
309
|
+
content: '';
|
|
310
|
+
position: absolute;
|
|
311
|
+
inset: -1px;
|
|
312
|
+
border-radius: var(--radius);
|
|
313
|
+
opacity: 0;
|
|
314
|
+
transition: opacity 0.3s;
|
|
315
|
+
pointer-events: none;
|
|
316
|
+
z-index: -1;
|
|
317
|
+
}
|
|
129
318
|
.stat-card.glow.cost-card::before { background: var(--accent-orange); }
|
|
319
|
+
.stat-card.glow.cost-card::after { box-shadow: 0 4px 24px var(--glow-orange); }
|
|
320
|
+
.stat-card.glow.cost-card:hover::after { opacity: 1; }
|
|
130
321
|
.stat-card.glow.commits-card::before { background: var(--accent-green); }
|
|
322
|
+
.stat-card.glow.commits-card::after { box-shadow: 0 4px 24px var(--glow-green); }
|
|
323
|
+
.stat-card.glow.commits-card:hover::after { opacity: 1; }
|
|
131
324
|
.stat-card.glow.avg-card::before { background: var(--accent-blue); }
|
|
325
|
+
.stat-card.glow.avg-card::after { box-shadow: 0 4px 24px var(--glow-blue); }
|
|
326
|
+
.stat-card.glow.avg-card:hover::after { opacity: 1; }
|
|
132
327
|
.stat-card.glow.grade-card::before { background: linear-gradient(90deg, var(--accent-blue), var(--accent-green)); }
|
|
328
|
+
.stat-card.glow.grade-card::after { box-shadow: 0 4px 24px var(--glow-blue); }
|
|
329
|
+
.stat-card.glow.grade-card:hover::after { opacity: 1; }
|
|
133
330
|
.stat-card .legend-dot {
|
|
134
331
|
width: 10px;
|
|
135
332
|
height: 10px;
|
|
@@ -145,13 +342,14 @@
|
|
|
145
342
|
text-align: right;
|
|
146
343
|
}
|
|
147
344
|
.stat-card .label {
|
|
148
|
-
font-size: 0.
|
|
345
|
+
font-size: 0.75rem;
|
|
149
346
|
color: var(--text-secondary);
|
|
150
347
|
text-transform: uppercase;
|
|
151
|
-
letter-spacing: 0.
|
|
348
|
+
letter-spacing: 0.08em;
|
|
152
349
|
margin-bottom: 2px;
|
|
153
350
|
}
|
|
154
351
|
.stat-card .value {
|
|
352
|
+
font-family: var(--font-display);
|
|
155
353
|
font-size: 1.8rem;
|
|
156
354
|
font-weight: 700;
|
|
157
355
|
white-space: nowrap;
|
|
@@ -169,6 +367,7 @@
|
|
|
169
367
|
padding: 24px;
|
|
170
368
|
}
|
|
171
369
|
.stat-card.grade .value {
|
|
370
|
+
font-family: var(--font-display);
|
|
172
371
|
font-size: 3.5rem;
|
|
173
372
|
line-height: 1;
|
|
174
373
|
font-weight: 800;
|
|
@@ -197,10 +396,10 @@
|
|
|
197
396
|
.hero-legend, .cost-legend, .token-legend {
|
|
198
397
|
display: flex;
|
|
199
398
|
gap: 24px;
|
|
200
|
-
margin-top:
|
|
201
|
-
padding:
|
|
202
|
-
background: var(--
|
|
203
|
-
border: 1px solid var(--
|
|
399
|
+
margin-top: 16px;
|
|
400
|
+
padding: 12px 20px;
|
|
401
|
+
background: var(--overlay-light);
|
|
402
|
+
border: 1px solid var(--overlay-medium);
|
|
204
403
|
border-radius: var(--radius-sm);
|
|
205
404
|
}
|
|
206
405
|
.hero-legend span, .cost-legend span, .token-legend span {
|
|
@@ -220,13 +419,16 @@
|
|
|
220
419
|
|
|
221
420
|
/* Insights */
|
|
222
421
|
.insights {
|
|
223
|
-
margin-bottom:
|
|
422
|
+
margin-bottom: 48px;
|
|
224
423
|
}
|
|
225
424
|
.insights h2 {
|
|
226
|
-
font-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
425
|
+
font-family: var(--font-display);
|
|
426
|
+
font-size: 0.85rem;
|
|
427
|
+
margin-bottom: 16px;
|
|
428
|
+
color: var(--text-muted);
|
|
429
|
+
font-weight: 600;
|
|
430
|
+
text-transform: uppercase;
|
|
431
|
+
letter-spacing: 0.12em;
|
|
230
432
|
}
|
|
231
433
|
.insight-list {
|
|
232
434
|
display: grid;
|
|
@@ -236,11 +438,17 @@
|
|
|
236
438
|
display: flex;
|
|
237
439
|
align-items: flex-start;
|
|
238
440
|
gap: 10px;
|
|
239
|
-
padding:
|
|
240
|
-
background: var(--
|
|
241
|
-
border: 1px solid var(--
|
|
441
|
+
padding: 14px 18px;
|
|
442
|
+
background: var(--overlay-light);
|
|
443
|
+
border: 1px solid var(--overlay-medium);
|
|
242
444
|
border-radius: var(--radius-sm);
|
|
243
|
-
font-size: 0.
|
|
445
|
+
font-size: 0.88rem;
|
|
446
|
+
transition: background 0.2s, border-color 0.2s, transform 0.2s;
|
|
447
|
+
}
|
|
448
|
+
.insight:hover {
|
|
449
|
+
background: var(--overlay-medium);
|
|
450
|
+
border-color: var(--overlay-intense);
|
|
451
|
+
transform: translateX(4px);
|
|
244
452
|
}
|
|
245
453
|
.insight .icon { font-size: 1.1rem; flex-shrink: 0; }
|
|
246
454
|
.insight.warning .icon { color: var(--accent-orange); }
|
|
@@ -252,23 +460,32 @@
|
|
|
252
460
|
.charts-grid {
|
|
253
461
|
display: grid;
|
|
254
462
|
grid-template-columns: 1fr 1fr;
|
|
255
|
-
gap:
|
|
256
|
-
margin-bottom:
|
|
463
|
+
gap: 20px;
|
|
464
|
+
margin-bottom: 48px;
|
|
257
465
|
}
|
|
258
466
|
.chart-card {
|
|
259
|
-
background: var(--bg-
|
|
260
|
-
|
|
467
|
+
background: linear-gradient(145deg, var(--glass-bg-from), var(--glass-bg-to));
|
|
468
|
+
backdrop-filter: blur(16px);
|
|
469
|
+
border: 1px solid var(--glass-border);
|
|
261
470
|
border-radius: var(--radius);
|
|
262
|
-
padding:
|
|
471
|
+
padding: 24px;
|
|
472
|
+
transition: border-color 0.3s, box-shadow 0.3s;
|
|
473
|
+
}
|
|
474
|
+
.chart-card:hover {
|
|
475
|
+
border-color: var(--overlay-intense);
|
|
476
|
+
box-shadow: 0 4px 20px var(--shadow-medium);
|
|
263
477
|
}
|
|
264
478
|
.chart-card.full-width {
|
|
265
479
|
grid-column: 1 / -1;
|
|
266
480
|
}
|
|
267
481
|
.chart-card h3 {
|
|
268
|
-
font-
|
|
269
|
-
|
|
482
|
+
font-family: var(--font-display);
|
|
483
|
+
font-size: 0.8rem;
|
|
484
|
+
color: var(--text-muted);
|
|
270
485
|
margin-bottom: 16px;
|
|
271
|
-
font-weight:
|
|
486
|
+
font-weight: 600;
|
|
487
|
+
text-transform: uppercase;
|
|
488
|
+
letter-spacing: 0.08em;
|
|
272
489
|
}
|
|
273
490
|
.chart-header {
|
|
274
491
|
display: flex;
|
|
@@ -278,16 +495,22 @@
|
|
|
278
495
|
}
|
|
279
496
|
.chart-header h3 { margin-bottom: 0; }
|
|
280
497
|
.scale-toggle {
|
|
281
|
-
padding:
|
|
282
|
-
border: 1px solid var(--
|
|
283
|
-
background: var(--
|
|
498
|
+
padding: 5px 12px;
|
|
499
|
+
border: 1px solid var(--overlay-intense);
|
|
500
|
+
background: var(--overlay-soft);
|
|
284
501
|
color: var(--text-secondary);
|
|
285
502
|
border-radius: 6px;
|
|
286
503
|
cursor: pointer;
|
|
287
|
-
font-family:
|
|
288
|
-
font-size: 0.
|
|
504
|
+
font-family: var(--font-display);
|
|
505
|
+
font-size: 0.7rem;
|
|
506
|
+
letter-spacing: 0.05em;
|
|
507
|
+
transition: all 0.2s;
|
|
508
|
+
}
|
|
509
|
+
.scale-toggle:hover {
|
|
510
|
+
color: var(--text-primary);
|
|
511
|
+
border-color: var(--accent-blue);
|
|
512
|
+
background: var(--glow-blue);
|
|
289
513
|
}
|
|
290
|
-
.scale-toggle:hover { color: var(--text-primary); border-color: var(--accent-blue); }
|
|
291
514
|
.chart-container {
|
|
292
515
|
position: relative;
|
|
293
516
|
width: 100%;
|
|
@@ -296,19 +519,23 @@
|
|
|
296
519
|
|
|
297
520
|
/* Survival bar */
|
|
298
521
|
.survival-section {
|
|
299
|
-
margin-bottom:
|
|
522
|
+
margin-bottom: 48px;
|
|
300
523
|
}
|
|
301
524
|
.survival-card {
|
|
302
|
-
background: var(--bg-
|
|
303
|
-
|
|
525
|
+
background: linear-gradient(145deg, var(--glass-bg-from), var(--glass-bg-to));
|
|
526
|
+
backdrop-filter: blur(16px);
|
|
527
|
+
border: 1px solid var(--glass-border);
|
|
304
528
|
border-radius: var(--radius);
|
|
305
529
|
padding: 24px;
|
|
306
530
|
}
|
|
307
531
|
.survival-card h3 {
|
|
308
|
-
font-
|
|
309
|
-
|
|
532
|
+
font-family: var(--font-display);
|
|
533
|
+
font-size: 0.8rem;
|
|
534
|
+
color: var(--text-muted);
|
|
310
535
|
margin-bottom: 16px;
|
|
311
|
-
font-weight:
|
|
536
|
+
font-weight: 600;
|
|
537
|
+
text-transform: uppercase;
|
|
538
|
+
letter-spacing: 0.08em;
|
|
312
539
|
display: flex;
|
|
313
540
|
align-items: center;
|
|
314
541
|
gap: 6px;
|
|
@@ -320,7 +547,7 @@
|
|
|
320
547
|
width: 16px;
|
|
321
548
|
height: 16px;
|
|
322
549
|
border-radius: 50%;
|
|
323
|
-
background: var(--
|
|
550
|
+
background: var(--glass-border);
|
|
324
551
|
color: var(--text-muted);
|
|
325
552
|
font-size: 0.6rem;
|
|
326
553
|
font-style: normal;
|
|
@@ -329,28 +556,39 @@
|
|
|
329
556
|
position: relative;
|
|
330
557
|
vertical-align: middle;
|
|
331
558
|
flex-shrink: 0;
|
|
559
|
+
transition: background 0.2s, color 0.2s;
|
|
560
|
+
}
|
|
561
|
+
.info-tip:hover {
|
|
562
|
+
background: var(--glow-blue);
|
|
563
|
+
color: var(--accent-blue);
|
|
564
|
+
}
|
|
565
|
+
@keyframes tooltipFade {
|
|
566
|
+
from { opacity: 0; transform: translateX(-50%) translateY(4px); }
|
|
567
|
+
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
332
568
|
}
|
|
333
569
|
.info-tip:hover::after {
|
|
334
570
|
content: attr(data-tip);
|
|
335
571
|
position: absolute;
|
|
336
|
-
bottom: calc(100% +
|
|
572
|
+
bottom: calc(100% + 10px);
|
|
337
573
|
left: 50%;
|
|
338
|
-
transform: translateX(-50%);
|
|
339
|
-
background: var(--bg
|
|
574
|
+
transform: translateX(-50%) translateY(0);
|
|
575
|
+
background: var(--tooltip-bg);
|
|
576
|
+
backdrop-filter: blur(16px);
|
|
340
577
|
color: var(--text-secondary);
|
|
341
|
-
padding:
|
|
342
|
-
border-radius:
|
|
578
|
+
padding: 10px 14px;
|
|
579
|
+
border-radius: 8px;
|
|
343
580
|
font-size: 0.75rem;
|
|
344
581
|
font-weight: 400;
|
|
345
582
|
width: 280px;
|
|
346
|
-
line-height: 1.
|
|
347
|
-
border: 1px solid var(--border);
|
|
583
|
+
line-height: 1.5;
|
|
584
|
+
border: 1px solid var(--tooltip-border);
|
|
348
585
|
z-index: 100;
|
|
349
586
|
pointer-events: none;
|
|
350
|
-
box-shadow: 0
|
|
587
|
+
box-shadow: 0 8px 32px var(--shadow-card-hover);
|
|
351
588
|
white-space: normal;
|
|
352
589
|
text-transform: none;
|
|
353
590
|
letter-spacing: normal;
|
|
591
|
+
animation: tooltipFade 0.2s ease forwards;
|
|
354
592
|
}
|
|
355
593
|
/* Table header tooltips: show below the icon instead of above */
|
|
356
594
|
thead .info-tip:hover::after {
|
|
@@ -359,12 +597,13 @@
|
|
|
359
597
|
}
|
|
360
598
|
/* Make table header tooltip icons more visible */
|
|
361
599
|
thead .info-tip {
|
|
362
|
-
background:
|
|
600
|
+
background: var(--glass-border-hover);
|
|
363
601
|
color: var(--text-secondary);
|
|
364
602
|
}
|
|
365
603
|
.survival-bar {
|
|
366
604
|
height: 24px;
|
|
367
|
-
background: var(--
|
|
605
|
+
background: var(--overlay-medium);
|
|
606
|
+
border: 1px solid var(--overlay-strong);
|
|
368
607
|
border-radius: 12px;
|
|
369
608
|
overflow: hidden;
|
|
370
609
|
margin-bottom: 12px;
|
|
@@ -398,12 +637,18 @@
|
|
|
398
637
|
}
|
|
399
638
|
.heatmap-cell {
|
|
400
639
|
aspect-ratio: 1;
|
|
401
|
-
border-radius:
|
|
640
|
+
border-radius: 4px;
|
|
402
641
|
min-height: 14px;
|
|
403
|
-
transition: transform 0.
|
|
642
|
+
transition: transform 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
|
643
|
+
box-shadow 0.2s;
|
|
404
644
|
cursor: default;
|
|
405
645
|
}
|
|
406
|
-
.heatmap-cell:hover {
|
|
646
|
+
.heatmap-cell:hover {
|
|
647
|
+
transform: scale(1.4);
|
|
648
|
+
box-shadow: 0 0 8px rgba(34, 211, 168, 0.3);
|
|
649
|
+
z-index: 10;
|
|
650
|
+
position: relative;
|
|
651
|
+
}
|
|
407
652
|
.heatmap-hour-labels {
|
|
408
653
|
display: grid;
|
|
409
654
|
grid-template-columns: 40px repeat(24, 1fr);
|
|
@@ -418,17 +663,21 @@
|
|
|
418
663
|
|
|
419
664
|
/* Sessions table */
|
|
420
665
|
.sessions-section {
|
|
421
|
-
margin-bottom:
|
|
666
|
+
margin-bottom: 48px;
|
|
422
667
|
}
|
|
423
668
|
.sessions-section h2 {
|
|
424
|
-
font-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
669
|
+
font-family: var(--font-display);
|
|
670
|
+
font-size: 0.85rem;
|
|
671
|
+
margin-bottom: 16px;
|
|
672
|
+
color: var(--text-muted);
|
|
673
|
+
font-weight: 600;
|
|
674
|
+
text-transform: uppercase;
|
|
675
|
+
letter-spacing: 0.12em;
|
|
428
676
|
}
|
|
429
677
|
.sessions-table-wrap {
|
|
430
|
-
background: var(--bg-
|
|
431
|
-
|
|
678
|
+
background: linear-gradient(145deg, var(--glass-bg-from), var(--glass-bg-to));
|
|
679
|
+
backdrop-filter: blur(16px);
|
|
680
|
+
border: 1px solid var(--glass-border);
|
|
432
681
|
border-radius: var(--radius);
|
|
433
682
|
overflow-x: auto;
|
|
434
683
|
overflow-y: visible;
|
|
@@ -439,43 +688,64 @@
|
|
|
439
688
|
font-size: 0.85rem;
|
|
440
689
|
}
|
|
441
690
|
thead th {
|
|
442
|
-
padding:
|
|
691
|
+
padding: 14px 14px;
|
|
443
692
|
text-align: left;
|
|
444
|
-
color: var(--text-
|
|
693
|
+
color: var(--text-muted);
|
|
694
|
+
font-family: var(--font-display);
|
|
445
695
|
font-weight: 500;
|
|
446
|
-
|
|
696
|
+
font-size: 0.75rem;
|
|
697
|
+
text-transform: uppercase;
|
|
698
|
+
letter-spacing: 0.08em;
|
|
699
|
+
border-bottom: 1px solid var(--overlay-strong);
|
|
447
700
|
cursor: pointer;
|
|
448
701
|
user-select: none;
|
|
449
702
|
white-space: nowrap;
|
|
703
|
+
transition: color 0.2s;
|
|
704
|
+
position: relative;
|
|
450
705
|
}
|
|
451
706
|
thead th:hover { color: var(--text-primary); }
|
|
452
707
|
thead th.sorted { color: var(--accent-blue); }
|
|
708
|
+
thead th.sorted::after {
|
|
709
|
+
content: '';
|
|
710
|
+
position: absolute;
|
|
711
|
+
bottom: 0;
|
|
712
|
+
left: 0;
|
|
713
|
+
right: 0;
|
|
714
|
+
height: 2px;
|
|
715
|
+
background: var(--accent-blue);
|
|
716
|
+
}
|
|
453
717
|
tbody tr {
|
|
454
|
-
border-bottom: 1px solid var(--
|
|
455
|
-
transition: background 0.
|
|
718
|
+
border-bottom: 1px solid var(--overlay-soft);
|
|
719
|
+
transition: background 0.2s;
|
|
456
720
|
}
|
|
457
|
-
tbody tr:hover { background: var(--
|
|
721
|
+
tbody tr:hover { background: var(--overlay-soft); }
|
|
458
722
|
tbody tr:last-child { border-bottom: none; }
|
|
459
723
|
tbody td {
|
|
460
|
-
padding:
|
|
724
|
+
padding: 12px 14px;
|
|
461
725
|
white-space: nowrap;
|
|
726
|
+
font-size: 0.85rem;
|
|
462
727
|
}
|
|
463
728
|
.grade-badge {
|
|
464
729
|
display: inline-flex;
|
|
465
730
|
align-items: center;
|
|
466
731
|
justify-content: center;
|
|
467
|
-
width:
|
|
468
|
-
height:
|
|
469
|
-
border-radius:
|
|
732
|
+
width: 30px;
|
|
733
|
+
height: 30px;
|
|
734
|
+
border-radius: 8px;
|
|
735
|
+
font-family: var(--font-display);
|
|
470
736
|
font-weight: 700;
|
|
471
|
-
font-size: 0.
|
|
737
|
+
font-size: 0.8rem;
|
|
738
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
739
|
+
}
|
|
740
|
+
tbody tr:hover .grade-badge {
|
|
741
|
+
transform: scale(1.1);
|
|
472
742
|
}
|
|
473
|
-
tr.orphaned { background: rgba(
|
|
743
|
+
tr.orphaned { background: rgba(245, 158, 11, 0.06); }
|
|
474
744
|
.expand-row {
|
|
475
745
|
display: none;
|
|
476
|
-
background: var(--
|
|
746
|
+
background: var(--overlay-subtle);
|
|
477
747
|
}
|
|
478
|
-
.expand-row.open { display: table-row; }
|
|
748
|
+
.expand-row.open { display: table-row; animation: fadeSlideUp 0.3s ease forwards; }
|
|
479
749
|
.expand-row td {
|
|
480
750
|
white-space: normal;
|
|
481
751
|
max-width: 1px;
|
|
@@ -522,14 +792,20 @@
|
|
|
522
792
|
border-top: 1px solid var(--border);
|
|
523
793
|
}
|
|
524
794
|
.pagination button {
|
|
525
|
-
padding:
|
|
526
|
-
border: 1px solid var(--
|
|
527
|
-
background: var(--
|
|
795
|
+
padding: 8px 18px;
|
|
796
|
+
border: 1px solid var(--overlay-intense);
|
|
797
|
+
background: var(--overlay-soft);
|
|
528
798
|
color: var(--text-primary);
|
|
529
|
-
border-radius:
|
|
799
|
+
border-radius: 8px;
|
|
530
800
|
cursor: pointer;
|
|
531
|
-
font-family:
|
|
532
|
-
font-size: 0.
|
|
801
|
+
font-family: var(--font-body);
|
|
802
|
+
font-size: 0.82rem;
|
|
803
|
+
transition: all 0.2s;
|
|
804
|
+
}
|
|
805
|
+
.pagination button:hover:not(:disabled) {
|
|
806
|
+
background: var(--overlay-intense);
|
|
807
|
+
border-color: var(--accent-blue);
|
|
808
|
+
box-shadow: 0 0 12px var(--glow-blue);
|
|
533
809
|
}
|
|
534
810
|
.pagination button:disabled { opacity: 0.4; cursor: default; }
|
|
535
811
|
.pagination span { font-size: 0.8rem; color: var(--text-muted); }
|
|
@@ -537,13 +813,23 @@
|
|
|
537
813
|
/* Footer */
|
|
538
814
|
footer {
|
|
539
815
|
text-align: center;
|
|
540
|
-
padding:
|
|
541
|
-
border-top:
|
|
816
|
+
padding: 40px 0;
|
|
817
|
+
border-top: none;
|
|
542
818
|
color: var(--text-muted);
|
|
543
819
|
font-size: 0.8rem;
|
|
820
|
+
position: relative;
|
|
544
821
|
}
|
|
545
|
-
footer
|
|
546
|
-
|
|
822
|
+
footer::before {
|
|
823
|
+
content: '';
|
|
824
|
+
position: absolute;
|
|
825
|
+
top: 0;
|
|
826
|
+
left: 10%;
|
|
827
|
+
right: 10%;
|
|
828
|
+
height: 1px;
|
|
829
|
+
background: linear-gradient(90deg, transparent, var(--border), transparent);
|
|
830
|
+
}
|
|
831
|
+
footer a { color: var(--accent-blue); text-decoration: none; transition: color 0.2s; }
|
|
832
|
+
footer a:hover { color: var(--accent-cyan); text-decoration: underline; }
|
|
547
833
|
|
|
548
834
|
/* Loading */
|
|
549
835
|
.loading {
|
|
@@ -571,31 +857,35 @@
|
|
|
571
857
|
gap: 16px;
|
|
572
858
|
}
|
|
573
859
|
.token-stat-card {
|
|
574
|
-
background: var(--bg-
|
|
575
|
-
|
|
860
|
+
background: linear-gradient(145deg, var(--glass-bg-from), var(--glass-bg-to));
|
|
861
|
+
backdrop-filter: blur(16px);
|
|
862
|
+
border: 1px solid var(--glass-border);
|
|
576
863
|
border-radius: var(--radius);
|
|
577
864
|
padding: 20px 24px;
|
|
578
865
|
text-align: center;
|
|
579
|
-
transition: transform 0.
|
|
580
|
-
|
|
866
|
+
transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
|
867
|
+
box-shadow 0.3s;
|
|
868
|
+
border-top: 2px solid var(--border);
|
|
581
869
|
display: flex;
|
|
582
870
|
flex-direction: column;
|
|
583
871
|
}
|
|
584
872
|
.token-stat-card:hover {
|
|
585
|
-
transform: translateY(-
|
|
873
|
+
transform: translateY(-3px);
|
|
874
|
+
box-shadow: 0 8px 32px var(--shadow-elevated);
|
|
586
875
|
}
|
|
587
876
|
.token-stat-card.burned { border-top-color: var(--accent-orange); }
|
|
588
877
|
.token-stat-card.wasted { border-top-color: var(--accent-red); }
|
|
589
878
|
.token-stat-card.efficiency { border-top-color: var(--accent-green); }
|
|
590
879
|
.token-stat-card.per-commit { border-top-color: var(--accent-cyan); }
|
|
591
880
|
.token-stat-card .label {
|
|
592
|
-
font-size: 0.
|
|
881
|
+
font-size: 0.75rem;
|
|
593
882
|
color: var(--text-secondary);
|
|
594
883
|
text-transform: uppercase;
|
|
595
|
-
letter-spacing: 0.
|
|
884
|
+
letter-spacing: 0.08em;
|
|
596
885
|
margin-bottom: 8px;
|
|
597
886
|
}
|
|
598
887
|
.token-stat-card .value {
|
|
888
|
+
font-family: var(--font-display);
|
|
599
889
|
font-size: 2rem;
|
|
600
890
|
font-weight: 700;
|
|
601
891
|
}
|
|
@@ -705,8 +995,9 @@
|
|
|
705
995
|
display: grid;
|
|
706
996
|
grid-template-columns: repeat(4, 1fr);
|
|
707
997
|
gap: 0;
|
|
708
|
-
background: var(--bg-
|
|
709
|
-
|
|
998
|
+
background: linear-gradient(145deg, var(--glass-bg-from), var(--glass-bg-to));
|
|
999
|
+
backdrop-filter: blur(16px);
|
|
1000
|
+
border: 1px solid var(--glass-border);
|
|
710
1001
|
border-radius: var(--radius);
|
|
711
1002
|
overflow: hidden;
|
|
712
1003
|
position: relative;
|
|
@@ -731,7 +1022,7 @@
|
|
|
731
1022
|
position: relative;
|
|
732
1023
|
}
|
|
733
1024
|
.period-card + .period-card {
|
|
734
|
-
border-left: 1px solid var(--border);
|
|
1025
|
+
border-left: 1px solid var(--glass-border);
|
|
735
1026
|
}
|
|
736
1027
|
.period-card:hover {
|
|
737
1028
|
background: var(--bg-hover);
|
|
@@ -746,7 +1037,7 @@
|
|
|
746
1037
|
position: relative;
|
|
747
1038
|
z-index: 2;
|
|
748
1039
|
}
|
|
749
|
-
.period-card.today::before { background: var(--accent-green); box-shadow: 0 0 8px rgba(
|
|
1040
|
+
.period-card.today::before { background: var(--accent-green); box-shadow: 0 0 8px rgba(34, 211, 168, 0.4); }
|
|
750
1041
|
.period-card.week::before { background: var(--accent-blue); }
|
|
751
1042
|
.period-card.month::before { background: var(--accent-purple); }
|
|
752
1043
|
.period-card.all-time::before { background: var(--accent-orange); }
|
|
@@ -762,6 +1053,7 @@
|
|
|
762
1053
|
margin-bottom: 10px;
|
|
763
1054
|
}
|
|
764
1055
|
.period-card .period-cost {
|
|
1056
|
+
font-family: var(--font-display);
|
|
765
1057
|
font-size: 1.6rem;
|
|
766
1058
|
font-weight: 700;
|
|
767
1059
|
margin-bottom: 8px;
|
|
@@ -783,19 +1075,23 @@
|
|
|
783
1075
|
}
|
|
784
1076
|
/* Token Flow Funnel */
|
|
785
1077
|
.token-funnel {
|
|
786
|
-
margin-bottom:
|
|
1078
|
+
margin-bottom: 48px;
|
|
787
1079
|
}
|
|
788
1080
|
.token-funnel-card {
|
|
789
|
-
background: var(--bg-
|
|
790
|
-
|
|
1081
|
+
background: linear-gradient(145deg, var(--glass-bg-from), var(--glass-bg-to));
|
|
1082
|
+
backdrop-filter: blur(16px);
|
|
1083
|
+
border: 1px solid var(--glass-border);
|
|
791
1084
|
border-radius: var(--radius);
|
|
792
1085
|
padding: 24px;
|
|
793
1086
|
}
|
|
794
1087
|
.token-funnel-card h3 {
|
|
795
|
-
font-
|
|
796
|
-
|
|
1088
|
+
font-family: var(--font-display);
|
|
1089
|
+
font-size: 0.8rem;
|
|
1090
|
+
color: var(--text-muted);
|
|
797
1091
|
margin-bottom: 20px;
|
|
798
|
-
font-weight:
|
|
1092
|
+
font-weight: 600;
|
|
1093
|
+
text-transform: uppercase;
|
|
1094
|
+
letter-spacing: 0.08em;
|
|
799
1095
|
}
|
|
800
1096
|
.funnel-row {
|
|
801
1097
|
margin-bottom: 16px;
|
|
@@ -817,16 +1113,20 @@
|
|
|
817
1113
|
}
|
|
818
1114
|
.funnel-bar .segment {
|
|
819
1115
|
height: 100%;
|
|
820
|
-
transition: width 0.8s ease;
|
|
1116
|
+
transition: width 0.8s ease, filter 0.2s, transform 0.2s;
|
|
821
1117
|
display: flex;
|
|
822
1118
|
align-items: center;
|
|
823
1119
|
justify-content: center;
|
|
824
1120
|
font-size: 0.7rem;
|
|
825
1121
|
font-weight: 500;
|
|
826
|
-
color:
|
|
1122
|
+
color: var(--funnel-text);
|
|
827
1123
|
overflow: hidden;
|
|
828
1124
|
white-space: nowrap;
|
|
829
1125
|
}
|
|
1126
|
+
.funnel-bar .segment:hover {
|
|
1127
|
+
filter: brightness(1.2);
|
|
1128
|
+
transform: scaleY(1.1);
|
|
1129
|
+
}
|
|
830
1130
|
.funnel-bar .segment:first-child { border-radius: 8px 0 0 8px; }
|
|
831
1131
|
.funnel-bar .segment:last-child { border-radius: 0 8px 8px 0; }
|
|
832
1132
|
.funnel-legend {
|
|
@@ -853,6 +1153,152 @@
|
|
|
853
1153
|
animation: pulse-red 2s ease-in-out infinite;
|
|
854
1154
|
}
|
|
855
1155
|
|
|
1156
|
+
/* Staggered reveal animation */
|
|
1157
|
+
@keyframes fadeSlideUp {
|
|
1158
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
1159
|
+
to { opacity: 1; transform: translateY(0); }
|
|
1160
|
+
}
|
|
1161
|
+
.reveal {
|
|
1162
|
+
opacity: 0;
|
|
1163
|
+
animation: fadeSlideUp 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
|
|
1164
|
+
}
|
|
1165
|
+
.reveal-delay-1 { animation-delay: 0.05s; }
|
|
1166
|
+
.reveal-delay-2 { animation-delay: 0.1s; }
|
|
1167
|
+
.reveal-delay-3 { animation-delay: 0.15s; }
|
|
1168
|
+
.reveal-delay-4 { animation-delay: 0.2s; }
|
|
1169
|
+
.reveal-delay-5 { animation-delay: 0.25s; }
|
|
1170
|
+
.reveal-delay-6 { animation-delay: 0.3s; }
|
|
1171
|
+
.reveal-delay-7 { animation-delay: 0.35s; }
|
|
1172
|
+
.reveal-delay-8 { animation-delay: 0.4s; }
|
|
1173
|
+
.reveal-delay-9 { animation-delay: 0.45s; }
|
|
1174
|
+
.reveal-delay-10 { animation-delay: 0.5s; }
|
|
1175
|
+
|
|
1176
|
+
/* Scroll-triggered reveal */
|
|
1177
|
+
.scroll-reveal {
|
|
1178
|
+
opacity: 0;
|
|
1179
|
+
transform: translateY(16px);
|
|
1180
|
+
transition: opacity 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
|
1181
|
+
transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
1182
|
+
}
|
|
1183
|
+
.scroll-reveal.revealed {
|
|
1184
|
+
opacity: 1;
|
|
1185
|
+
transform: translateY(0);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/* Grade circle animation */
|
|
1189
|
+
@keyframes gradeReveal {
|
|
1190
|
+
from { --grade-deg: 0deg; }
|
|
1191
|
+
}
|
|
1192
|
+
.grade-circle {
|
|
1193
|
+
animation: gradeReveal 1.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/* ── Header Actions (top-right buttons) ─────── */
|
|
1197
|
+
.header-actions {
|
|
1198
|
+
position: absolute;
|
|
1199
|
+
top: 16px;
|
|
1200
|
+
right: 16px;
|
|
1201
|
+
z-index: 10;
|
|
1202
|
+
display: flex;
|
|
1203
|
+
align-items: center;
|
|
1204
|
+
gap: 8px;
|
|
1205
|
+
}
|
|
1206
|
+
.header-action-btn {
|
|
1207
|
+
width: 42px;
|
|
1208
|
+
height: 42px;
|
|
1209
|
+
border-radius: 50%;
|
|
1210
|
+
border: 1px solid var(--glass-border);
|
|
1211
|
+
background: var(--overlay-medium);
|
|
1212
|
+
backdrop-filter: blur(12px);
|
|
1213
|
+
color: var(--text-secondary);
|
|
1214
|
+
cursor: pointer;
|
|
1215
|
+
display: flex;
|
|
1216
|
+
align-items: center;
|
|
1217
|
+
justify-content: center;
|
|
1218
|
+
transition: background 0.3s, border-color 0.3s, color 0.3s, transform 0.2s;
|
|
1219
|
+
}
|
|
1220
|
+
.header-action-btn:hover {
|
|
1221
|
+
background: var(--overlay-intense);
|
|
1222
|
+
border-color: var(--glass-border-hover);
|
|
1223
|
+
color: var(--text-primary);
|
|
1224
|
+
transform: scale(1.1);
|
|
1225
|
+
}
|
|
1226
|
+
.header-action-btn svg {
|
|
1227
|
+
width: 18px;
|
|
1228
|
+
height: 18px;
|
|
1229
|
+
}
|
|
1230
|
+
.refresh-btn.refreshing svg {
|
|
1231
|
+
animation: spin 0.8s linear infinite;
|
|
1232
|
+
}
|
|
1233
|
+
.theme-switcher {
|
|
1234
|
+
display: flex;
|
|
1235
|
+
align-items: center;
|
|
1236
|
+
border-radius: 22px;
|
|
1237
|
+
border: 1px solid var(--glass-border);
|
|
1238
|
+
background: var(--overlay-medium);
|
|
1239
|
+
backdrop-filter: blur(12px);
|
|
1240
|
+
padding: 3px;
|
|
1241
|
+
gap: 2px;
|
|
1242
|
+
}
|
|
1243
|
+
.theme-switcher button {
|
|
1244
|
+
width: 34px;
|
|
1245
|
+
height: 34px;
|
|
1246
|
+
border-radius: 50%;
|
|
1247
|
+
border: none;
|
|
1248
|
+
background: transparent;
|
|
1249
|
+
color: var(--text-muted);
|
|
1250
|
+
cursor: pointer;
|
|
1251
|
+
display: flex;
|
|
1252
|
+
align-items: center;
|
|
1253
|
+
justify-content: center;
|
|
1254
|
+
transition: background 0.25s, color 0.25s, transform 0.15s;
|
|
1255
|
+
}
|
|
1256
|
+
.theme-switcher button:hover {
|
|
1257
|
+
color: var(--text-primary);
|
|
1258
|
+
transform: scale(1.08);
|
|
1259
|
+
}
|
|
1260
|
+
.theme-switcher button.active {
|
|
1261
|
+
background: var(--overlay-intense);
|
|
1262
|
+
color: var(--text-primary);
|
|
1263
|
+
box-shadow: 0 1px 4px var(--shadow-card);
|
|
1264
|
+
}
|
|
1265
|
+
.theme-switcher button svg {
|
|
1266
|
+
width: 16px;
|
|
1267
|
+
height: 16px;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
/* ── Light Theme Overrides ───────────────────── */
|
|
1271
|
+
[data-theme="light"] body {
|
|
1272
|
+
background:
|
|
1273
|
+
radial-gradient(ellipse 80% 50% at 50% -20%, rgba(37, 99, 235, 0.05), transparent),
|
|
1274
|
+
radial-gradient(ellipse 60% 40% at 100% 50%, rgba(124, 58, 237, 0.04), transparent),
|
|
1275
|
+
radial-gradient(ellipse 50% 30% at 0% 80%, rgba(13, 148, 136, 0.04), transparent),
|
|
1276
|
+
var(--bg-primary);
|
|
1277
|
+
}
|
|
1278
|
+
[data-theme="light"] header::before {
|
|
1279
|
+
background:
|
|
1280
|
+
radial-gradient(ellipse 60% 50% at 20% 50%, rgba(37, 99, 235, 0.07), transparent),
|
|
1281
|
+
radial-gradient(ellipse 50% 60% at 80% 30%, rgba(124, 58, 237, 0.05), transparent),
|
|
1282
|
+
radial-gradient(ellipse 40% 40% at 50% 80%, rgba(13, 148, 136, 0.04), transparent);
|
|
1283
|
+
}
|
|
1284
|
+
[data-theme="light"] .info-tip:hover::after {
|
|
1285
|
+
color: #e2e8f0;
|
|
1286
|
+
}
|
|
1287
|
+
[data-theme="light"] tr.orphaned {
|
|
1288
|
+
background: rgba(217, 119, 6, 0.06);
|
|
1289
|
+
}
|
|
1290
|
+
[data-theme="light"] .main-badge {
|
|
1291
|
+
background: rgba(13, 148, 136, 0.12);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/* Smooth theme transition */
|
|
1295
|
+
html.theme-transition,
|
|
1296
|
+
html.theme-transition *,
|
|
1297
|
+
html.theme-transition *::before,
|
|
1298
|
+
html.theme-transition *::after {
|
|
1299
|
+
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease, background 0.3s ease !important;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
856
1302
|
/* Responsive */
|
|
857
1303
|
@media (max-width: 900px) {
|
|
858
1304
|
.hero-stats { grid-template-columns: 1fr; }
|
|
@@ -861,7 +1307,7 @@
|
|
|
861
1307
|
.period-stats { grid-template-columns: repeat(2, 1fr); }
|
|
862
1308
|
.period-stats::before { display: none; }
|
|
863
1309
|
.period-card:nth-child(3) { border-left: none; }
|
|
864
|
-
.period-card:nth-child(3), .period-card:nth-child(4) { border-top: 1px solid var(--border); }
|
|
1310
|
+
.period-card:nth-child(3), .period-card:nth-child(4) { border-top: 1px solid var(--glass-border); }
|
|
865
1311
|
.charts-grid { grid-template-columns: 1fr; }
|
|
866
1312
|
}
|
|
867
1313
|
@media (max-width: 600px) {
|
|
@@ -877,6 +1323,30 @@
|
|
|
877
1323
|
<body>
|
|
878
1324
|
<div class="container">
|
|
879
1325
|
<header>
|
|
1326
|
+
<div class="header-actions">
|
|
1327
|
+
<button class="header-action-btn refresh-btn" id="refresh-btn" aria-label="Refresh data" title="Refresh dashboard data">
|
|
1328
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1329
|
+
<polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
|
|
1330
|
+
</svg>
|
|
1331
|
+
</button>
|
|
1332
|
+
<div class="theme-switcher" id="theme-switcher">
|
|
1333
|
+
<button data-theme-pref="system" title="System theme" aria-label="Use system theme">
|
|
1334
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1335
|
+
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>
|
|
1336
|
+
</svg>
|
|
1337
|
+
</button>
|
|
1338
|
+
<button data-theme-pref="dark" title="Dark theme" aria-label="Dark theme">
|
|
1339
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1340
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
|
1341
|
+
</svg>
|
|
1342
|
+
</button>
|
|
1343
|
+
<button data-theme-pref="light" title="Light theme" aria-label="Light theme">
|
|
1344
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1345
|
+
<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
|
1346
|
+
</svg>
|
|
1347
|
+
</button>
|
|
1348
|
+
</div>
|
|
1349
|
+
</div>
|
|
880
1350
|
<h1>Codelens AI</h1>
|
|
881
1351
|
<p class="tagline">Correlates your AI coding agent's token spend with actual git output — see what shipped, what churned, and what it cost.</p>
|
|
882
1352
|
<div class="meta-info">
|
|
@@ -900,10 +1370,106 @@
|
|
|
900
1370
|
</div>
|
|
901
1371
|
|
|
902
1372
|
<script>
|
|
903
|
-
const
|
|
1373
|
+
const GRADE_VAR = { A: 'var(--grade-a)', B: 'var(--grade-b)', C: 'var(--grade-c)', D: 'var(--grade-d)', F: 'var(--grade-f)' };
|
|
1374
|
+
const GRADE_BG_VAR = { A: 'var(--grade-a-bg)', B: 'var(--grade-b-bg)', C: 'var(--grade-c-bg)', D: 'var(--grade-d-bg)', F: 'var(--grade-f-bg)' };
|
|
904
1375
|
const INSIGHT_ICONS = { warning: '!', success: '+', info: 'i', tip: '*' };
|
|
905
1376
|
const DAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
906
1377
|
let DATA = null;
|
|
1378
|
+
let initialRenderDone = false;
|
|
1379
|
+
|
|
1380
|
+
function getThemePref() {
|
|
1381
|
+
return localStorage.getItem('codelens-theme') || 'system';
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function resolveTheme(pref) {
|
|
1385
|
+
if (pref === 'system') return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
1386
|
+
return pref;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
function getTheme() {
|
|
1390
|
+
return document.documentElement.getAttribute('data-theme') || 'dark';
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function applyTheme(pref) {
|
|
1394
|
+
const resolved = resolveTheme(pref);
|
|
1395
|
+
document.documentElement.classList.add('theme-transition');
|
|
1396
|
+
if (resolved === 'light') {
|
|
1397
|
+
document.documentElement.setAttribute('data-theme', 'light');
|
|
1398
|
+
} else {
|
|
1399
|
+
document.documentElement.removeAttribute('data-theme');
|
|
1400
|
+
}
|
|
1401
|
+
localStorage.setItem('codelens-theme', pref);
|
|
1402
|
+
updateChartTheme(resolved);
|
|
1403
|
+
updateThemeSwitcher(pref);
|
|
1404
|
+
setTimeout(() => document.documentElement.classList.remove('theme-transition'), 350);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
function updateThemeSwitcher(pref) {
|
|
1408
|
+
const switcher = document.getElementById('theme-switcher');
|
|
1409
|
+
if (!switcher) return;
|
|
1410
|
+
switcher.querySelectorAll('button').forEach(btn => {
|
|
1411
|
+
btn.classList.toggle('active', btn.dataset.themePref === pref);
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function updateChartTheme(theme) {
|
|
1416
|
+
const isLight = theme === 'light';
|
|
1417
|
+
Chart.defaults.color = isLight ? '#475569' : '#94a3b8';
|
|
1418
|
+
Chart.defaults.borderColor = isLight ? '#cbd5e1' : '#1f2d3d';
|
|
1419
|
+
Chart.defaults.plugins.tooltip.backgroundColor = isLight ? 'rgba(15, 23, 42, 0.92)' : 'rgba(10, 14, 23, 0.95)';
|
|
1420
|
+
Chart.defaults.plugins.tooltip.borderColor = isLight ? 'rgba(255, 255, 255, 0.1)' : 'rgba(255, 255, 255, 0.08)';
|
|
1421
|
+
Object.values(Chart.instances || {}).forEach(chart => {
|
|
1422
|
+
if (chart.options.scales) {
|
|
1423
|
+
Object.values(chart.options.scales).forEach(scale => {
|
|
1424
|
+
if (scale.grid) scale.grid.color = isLight ? '#e2e8f0' : '#1f2d3d';
|
|
1425
|
+
if (scale.ticks) scale.ticks.color = isLight ? '#475569' : '#94a3b8';
|
|
1426
|
+
if (scale.title) scale.title.color = isLight ? '#475569' : '#94a3b8';
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
chart.update('none');
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
function initTheme() {
|
|
1434
|
+
const switcher = document.getElementById('theme-switcher');
|
|
1435
|
+
if (!switcher) return;
|
|
1436
|
+
const pref = getThemePref();
|
|
1437
|
+
updateThemeSwitcher(pref);
|
|
1438
|
+
switcher.addEventListener('click', (e) => {
|
|
1439
|
+
const btn = e.target.closest('button[data-theme-pref]');
|
|
1440
|
+
if (!btn) return;
|
|
1441
|
+
applyTheme(btn.dataset.themePref);
|
|
1442
|
+
});
|
|
1443
|
+
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', () => {
|
|
1444
|
+
if (getThemePref() === 'system') applyTheme('system');
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
async function refreshData() {
|
|
1449
|
+
const btn = document.getElementById('refresh-btn');
|
|
1450
|
+
if (!btn || btn.classList.contains('refreshing')) return;
|
|
1451
|
+
btn.classList.add('refreshing');
|
|
1452
|
+
try {
|
|
1453
|
+
// Trigger server-side re-parse: clears cache, re-reads sessions, re-analyzes git, recomputes metrics
|
|
1454
|
+
const refreshRes = await fetch('/api/refresh', { method: 'POST' });
|
|
1455
|
+
if (!refreshRes.ok) {
|
|
1456
|
+
const err = await refreshRes.json().catch(() => ({}));
|
|
1457
|
+
throw new Error(err.error || 'Refresh failed');
|
|
1458
|
+
}
|
|
1459
|
+
// Fetch the newly computed payload
|
|
1460
|
+
const res = await fetch('/api/all');
|
|
1461
|
+
DATA = await res.json();
|
|
1462
|
+
initialRenderDone = false;
|
|
1463
|
+
render();
|
|
1464
|
+
} catch (err) {
|
|
1465
|
+
document.getElementById('app').innerHTML = `
|
|
1466
|
+
<div class="loading" style="color: var(--accent-red);">
|
|
1467
|
+
Failed to refresh: ${err.message}
|
|
1468
|
+
</div>`;
|
|
1469
|
+
} finally {
|
|
1470
|
+
btn.classList.remove('refreshing');
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
907
1473
|
|
|
908
1474
|
function formatTokens(n) {
|
|
909
1475
|
if (n >= 1_000_000_000) return (n / 1_000_000_000).toFixed(1) + 'B';
|
|
@@ -911,6 +1477,21 @@ function formatTokens(n) {
|
|
|
911
1477
|
if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K';
|
|
912
1478
|
return n.toString();
|
|
913
1479
|
}
|
|
1480
|
+
|
|
1481
|
+
function animateValue(element, start, end, duration, prefix = '', suffix = '') {
|
|
1482
|
+
const startTime = performance.now();
|
|
1483
|
+
const range = end - start;
|
|
1484
|
+
function update(currentTime) {
|
|
1485
|
+
const elapsed = currentTime - startTime;
|
|
1486
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
1487
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
1488
|
+
const current = start + range * eased;
|
|
1489
|
+
element.textContent = prefix + (Number.isInteger(end) ? Math.round(current).toLocaleString() : current.toFixed(2)) + suffix;
|
|
1490
|
+
if (progress < 1) requestAnimationFrame(update);
|
|
1491
|
+
}
|
|
1492
|
+
requestAnimationFrame(update);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
914
1495
|
let currentPage = 1;
|
|
915
1496
|
let sortCol = 'startTime';
|
|
916
1497
|
let sortOrder = -1;
|
|
@@ -918,6 +1499,9 @@ let timelineChart = null;
|
|
|
918
1499
|
let timelineLogScale = false;
|
|
919
1500
|
|
|
920
1501
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
1502
|
+
initTheme();
|
|
1503
|
+
const refreshBtn = document.getElementById('refresh-btn');
|
|
1504
|
+
if (refreshBtn) refreshBtn.addEventListener('click', refreshData);
|
|
921
1505
|
try {
|
|
922
1506
|
const res = await fetch('/api/all');
|
|
923
1507
|
DATA = await res.json();
|
|
@@ -936,14 +1520,17 @@ function render() {
|
|
|
936
1520
|
const summary = d.summary;
|
|
937
1521
|
const t = d.tokenAnalytics;
|
|
938
1522
|
|
|
1523
|
+
const revealClass = !initialRenderDone ? 'reveal' : '';
|
|
1524
|
+
const rd = (n) => !initialRenderDone ? `reveal reveal-delay-${n}` : '';
|
|
1525
|
+
|
|
939
1526
|
document.getElementById('app').innerHTML = `
|
|
940
|
-
${renderHeroStats(summary)}
|
|
941
|
-
${renderCostByPeriod(summary.costByPeriod)}
|
|
942
|
-
${renderTokenHeroStats(t)}
|
|
943
|
-
${renderTokenFunnel(t)}
|
|
944
|
-
${renderTokenFunFacts(t.funFacts)}
|
|
945
|
-
${renderInsights(d.insights)}
|
|
946
|
-
<div class="charts-grid">
|
|
1527
|
+
<div class="${rd(1)}">${renderHeroStats(summary)}</div>
|
|
1528
|
+
<div class="${rd(2)}">${renderCostByPeriod(summary.costByPeriod)}</div>
|
|
1529
|
+
<div class="${rd(3)}">${renderTokenHeroStats(t)}</div>
|
|
1530
|
+
<div class="${rd(4)}">${renderTokenFunnel(t)}</div>
|
|
1531
|
+
<div class="${rd(5)}">${renderTokenFunFacts(t.funFacts)}</div>
|
|
1532
|
+
<div class="${rd(6)}">${renderInsights(d.insights)}</div>
|
|
1533
|
+
<div class="charts-grid scroll-reveal">
|
|
947
1534
|
<div class="chart-card full-width">
|
|
948
1535
|
<div class="chart-header">
|
|
949
1536
|
<h3>Token Burn Rate <i class="info-tip" data-tip="Daily token consumption stacked by type (input/output/cache). Dashed orange line shows cumulative total — watch it climb.">i</i></h3>
|
|
@@ -974,9 +1561,9 @@ function render() {
|
|
|
974
1561
|
<div id="heatmap-container"></div>
|
|
975
1562
|
</div>
|
|
976
1563
|
</div>
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1564
|
+
<div class="scroll-reveal">${renderCacheEfficiency(t)}</div>
|
|
1565
|
+
<div class="scroll-reveal">${renderSurvival(d.lineSurvival)}</div>
|
|
1566
|
+
<div class="scroll-reveal">${renderSessionsTable(d.sessions)}</div>
|
|
980
1567
|
`;
|
|
981
1568
|
|
|
982
1569
|
if (typeof Chart !== 'undefined') {
|
|
@@ -987,7 +1574,7 @@ function render() {
|
|
|
987
1574
|
}
|
|
988
1575
|
|
|
989
1576
|
function renderHeroStats(s) {
|
|
990
|
-
const gradeColor =
|
|
1577
|
+
const gradeColor = GRADE_VAR[s.overallGrade] || GRADE_VAR.F;
|
|
991
1578
|
const GRADE_DEG = { A: 324, B: 270, C: 216, D: 144, F: 72 };
|
|
992
1579
|
const gradeDeg = GRADE_DEG[s.overallGrade] || 72;
|
|
993
1580
|
return `<div class="stats-section">
|
|
@@ -1001,7 +1588,7 @@ function renderHeroStats(s) {
|
|
|
1001
1588
|
<div class="sub">${s.totalSessions} sessions</div>
|
|
1002
1589
|
</div>
|
|
1003
1590
|
<div class="stat-value-wrap">
|
|
1004
|
-
<div class="value" style="color: var(--accent-orange);"
|
|
1591
|
+
<div class="value" style="color: var(--accent-orange);" data-animate-value="${s.totalCost.toFixed(2)}" data-animate-prefix="$">$0.00</div>
|
|
1005
1592
|
</div>
|
|
1006
1593
|
</div>
|
|
1007
1594
|
<div class="stat-card glow commits-card">
|
|
@@ -1011,7 +1598,7 @@ function renderHeroStats(s) {
|
|
|
1011
1598
|
<div class="sub">${s.totalLinesAdded.toLocaleString()} lines added</div>
|
|
1012
1599
|
</div>
|
|
1013
1600
|
<div class="stat-value-wrap">
|
|
1014
|
-
<div class="value" style="color: var(--accent-green);"
|
|
1601
|
+
<div class="value" style="color: var(--accent-green);" data-animate-value="${s.totalCommits}" data-animate-prefix="">0</div>
|
|
1015
1602
|
</div>
|
|
1016
1603
|
</div>
|
|
1017
1604
|
<div class="stat-card glow avg-card">
|
|
@@ -1021,7 +1608,7 @@ function renderHeroStats(s) {
|
|
|
1021
1608
|
<div class="sub">${s.totalFilesChanged} files changed</div>
|
|
1022
1609
|
</div>
|
|
1023
1610
|
<div class="stat-value-wrap">
|
|
1024
|
-
<div class="value" style="color: var(--accent-blue);"
|
|
1611
|
+
<div class="value" style="color: var(--accent-blue);" ${s.avgCostPerCommit !== null ? `data-animate-value="${s.avgCostPerCommit.toFixed(2)}" data-animate-prefix="$"` : ''}>${s.avgCostPerCommit !== null ? '$0.00' : 'N/A'}</div>
|
|
1025
1612
|
</div>
|
|
1026
1613
|
</div>
|
|
1027
1614
|
</div>
|
|
@@ -1098,19 +1685,24 @@ function renderInsights(insights) {
|
|
|
1098
1685
|
|
|
1099
1686
|
function renderSurvival(s) {
|
|
1100
1687
|
const pct = s.survivalRate;
|
|
1101
|
-
const
|
|
1688
|
+
const accentColor = pct >= 80 ? 'var(--accent-purple)' : pct >= 50 ? 'var(--accent-orange)' : 'var(--accent-red)';
|
|
1689
|
+
const gradient = pct >= 80
|
|
1690
|
+
? 'linear-gradient(90deg, var(--accent-purple), var(--accent-blue))'
|
|
1691
|
+
: pct >= 50
|
|
1692
|
+
? 'linear-gradient(90deg, var(--accent-orange), var(--accent-purple))'
|
|
1693
|
+
: 'var(--accent-red)';
|
|
1102
1694
|
return `<div class="survival-section">
|
|
1103
1695
|
<div class="survival-card">
|
|
1104
1696
|
<h3>
|
|
1105
1697
|
Line Survival Rate
|
|
1106
1698
|
<i class="info-tip" data-tip="Of all lines your AI agent added, how many are still in the codebase after 24 hours. Approximate — based on churn detection, not exact git blame tracking.">i</i>
|
|
1107
1699
|
</h3>
|
|
1108
|
-
<div class="survival-bar"><div class="fill" style="width: ${pct}%; background: ${
|
|
1700
|
+
<div class="survival-bar"><div class="fill" style="width: ${pct}%; background: ${gradient};"></div></div>
|
|
1109
1701
|
<div class="survival-stats">
|
|
1110
1702
|
<div><span>${s.totalAdded.toLocaleString()}</span> lines added</div>
|
|
1111
1703
|
<div><span>${s.totalChurned.toLocaleString()}</span> churned within 24h</div>
|
|
1112
1704
|
<div><span>${s.surviving.toLocaleString()}</span> surviving</div>
|
|
1113
|
-
<div><span style="color:${
|
|
1705
|
+
<div><span style="color:${accentColor};">${pct}%</span> survival rate</div>
|
|
1114
1706
|
</div>
|
|
1115
1707
|
</div>
|
|
1116
1708
|
</div>`;
|
|
@@ -1231,12 +1823,12 @@ function renderTokenFunnel(t) {
|
|
|
1231
1823
|
</div>
|
|
1232
1824
|
<div class="funnel-bar">
|
|
1233
1825
|
<div class="segment" style="width:${pProd}%;background:var(--accent-green);" title="Productive: ${formatTokens(t.funnel.productive)}">${parseFloat(pProd) > 8 ? pProd + '% Shipped' : ''}</div>
|
|
1234
|
-
<div class="segment" style="width:${pExpl}%;background:rgba(
|
|
1826
|
+
<div class="segment" style="width:${pExpl}%;background:rgba(59,130,246,0.4);" title="Exploratory: ${formatTokens(t.funnel.exploratory)}">${parseFloat(pExpl) > 8 ? pExpl + '% Idle' : ''}</div>
|
|
1235
1827
|
<div class="segment ${wasteHigh ? 'waste-pulse' : ''}" style="width:${pOrph}%;background:var(--accent-red);" title="WASTED: ${formatTokens(t.funnel.orphaned)}">${parseFloat(pOrph) > 5 ? pOrph + '% WASTED' : ''}</div>
|
|
1236
1828
|
</div>
|
|
1237
1829
|
<div class="funnel-legend">
|
|
1238
1830
|
<span><span class="dot" style="background:var(--accent-green);"></span>Productive (${pProd}%) — led to commits</span>
|
|
1239
|
-
<span><span class="dot" style="background:rgba(
|
|
1831
|
+
<span><span class="dot" style="background:rgba(59,130,246,0.4);"></span>Exploratory (${pExpl}%) — short sessions, no commits</span>
|
|
1240
1832
|
<span><span class="dot" style="background:var(--accent-red);"></span>WASTED (${pOrph}%) — long sessions, zero output</span>
|
|
1241
1833
|
</div>
|
|
1242
1834
|
</div>
|
|
@@ -1261,19 +1853,24 @@ function renderTokenFunFacts(facts) {
|
|
|
1261
1853
|
|
|
1262
1854
|
function renderCacheEfficiency(t) {
|
|
1263
1855
|
const hitRate = t.cacheHitRate;
|
|
1264
|
-
const
|
|
1856
|
+
const accentColor = hitRate >= 60 ? 'var(--accent-blue)' : hitRate >= 30 ? 'var(--accent-orange)' : 'var(--accent-red)';
|
|
1857
|
+
const gradient = hitRate >= 60
|
|
1858
|
+
? 'linear-gradient(90deg, var(--accent-blue), var(--accent-cyan))'
|
|
1859
|
+
: hitRate >= 30
|
|
1860
|
+
? 'linear-gradient(90deg, var(--accent-orange), var(--accent-blue))'
|
|
1861
|
+
: 'var(--accent-red)';
|
|
1265
1862
|
return `<div class="survival-section">
|
|
1266
1863
|
<div class="survival-card">
|
|
1267
1864
|
<h3>
|
|
1268
1865
|
Cache Efficiency
|
|
1269
1866
|
<i class="info-tip" data-tip="Cache reads are 90% cheaper than regular input tokens. This shows what percentage of your input context was served from cache and how much money that saved you.">i</i>
|
|
1270
1867
|
</h3>
|
|
1271
|
-
<div class="survival-bar"><div class="fill" style="width: ${hitRate}%; background: ${
|
|
1868
|
+
<div class="survival-bar"><div class="fill" style="width: ${hitRate}%; background: ${gradient};"></div></div>
|
|
1272
1869
|
<div class="survival-stats">
|
|
1273
1870
|
<div><span>${formatTokens(t.totalCacheReadTokens)}</span> tokens from cache</div>
|
|
1274
1871
|
<div><span>${formatTokens(t.totalInputTokens)}</span> fresh input tokens</div>
|
|
1275
|
-
<div><span style="color:var(--accent-
|
|
1276
|
-
<div><span style="color:${
|
|
1872
|
+
<div><span style="color:var(--accent-cyan);">$${t.cacheSavingsDollars.toFixed(2)}</span> saved</div>
|
|
1873
|
+
<div><span style="color:${accentColor};">${hitRate}%</span> cache hit rate</div>
|
|
1277
1874
|
</div>
|
|
1278
1875
|
</div>
|
|
1279
1876
|
</div>`;
|
|
@@ -1312,7 +1909,8 @@ function renderSessionsTable(sessions) {
|
|
|
1312
1909
|
<tbody>
|
|
1313
1910
|
${pageData.map((s, i) => {
|
|
1314
1911
|
const idx = (page - 1) * pageSize + i;
|
|
1315
|
-
const gradeColor =
|
|
1912
|
+
const gradeColor = GRADE_VAR[s.grade] || GRADE_VAR.F;
|
|
1913
|
+
const gradeBg = GRADE_BG_VAR[s.grade] || GRADE_BG_VAR.F;
|
|
1316
1914
|
const primaryName = formatModelName(s.model || 'unknown');
|
|
1317
1915
|
const subModels = Object.keys(s.modelBreakdown || {})
|
|
1318
1916
|
.filter(m => m !== s.model)
|
|
@@ -1330,8 +1928,8 @@ function renderSessionsTable(sessions) {
|
|
|
1330
1928
|
<td>${s.userMessageCount + s.assistantMessageCount}</td>
|
|
1331
1929
|
<td>$${s.cost.totalCost.toFixed(2)}</td>
|
|
1332
1930
|
<td>${s.commitCount}</td>
|
|
1333
|
-
<td><span style="color
|
|
1334
|
-
<td><span class="grade-badge" style="background:${
|
|
1931
|
+
<td><span style="color:var(--accent-green)">+${s.linesAdded.toLocaleString()}</span> / <span style="color:var(--accent-red)">-${s.linesDeleted.toLocaleString()}</span></td>
|
|
1932
|
+
<td><span class="grade-badge" style="background:${gradeBg};color:${gradeColor};">${s.grade}</span></td>
|
|
1335
1933
|
</tr>
|
|
1336
1934
|
<tr class="expand-row" id="expand-${idx}">
|
|
1337
1935
|
<td colspan="8">
|
|
@@ -1384,9 +1982,17 @@ function formatDate(iso) {
|
|
|
1384
1982
|
}
|
|
1385
1983
|
|
|
1386
1984
|
function initCharts() {
|
|
1387
|
-
|
|
1388
|
-
Chart.defaults.
|
|
1389
|
-
Chart.defaults.
|
|
1985
|
+
const isLight = getTheme() === 'light';
|
|
1986
|
+
Chart.defaults.color = isLight ? '#475569' : '#94a3b8';
|
|
1987
|
+
Chart.defaults.borderColor = isLight ? '#cbd5e1' : '#1f2d3d';
|
|
1988
|
+
Chart.defaults.font.family = "'DM Sans', sans-serif";
|
|
1989
|
+
Chart.defaults.plugins.tooltip.backgroundColor = isLight ? 'rgba(15, 23, 42, 0.92)' : 'rgba(10, 14, 23, 0.95)';
|
|
1990
|
+
Chart.defaults.plugins.tooltip.borderColor = isLight ? 'rgba(255, 255, 255, 0.1)' : 'rgba(255, 255, 255, 0.08)';
|
|
1991
|
+
Chart.defaults.plugins.tooltip.borderWidth = 1;
|
|
1992
|
+
Chart.defaults.plugins.tooltip.cornerRadius = 8;
|
|
1993
|
+
Chart.defaults.plugins.tooltip.titleFont = { family: "'JetBrains Mono', monospace", size: 12, weight: '600' };
|
|
1994
|
+
Chart.defaults.plugins.tooltip.bodyFont = { family: "'DM Sans', sans-serif", size: 12 };
|
|
1995
|
+
Chart.defaults.plugins.tooltip.padding = { top: 10, right: 14, bottom: 10, left: 14 };
|
|
1390
1996
|
|
|
1391
1997
|
// Token Burn Rate chart
|
|
1392
1998
|
const dailyTokens = DATA.daily;
|
|
@@ -1401,21 +2007,21 @@ function initCharts() {
|
|
|
1401
2007
|
{
|
|
1402
2008
|
label: 'Input',
|
|
1403
2009
|
data: dailyTokens.map(d => d.inputTokens || 0),
|
|
1404
|
-
backgroundColor: 'rgba(
|
|
2010
|
+
backgroundColor: 'rgba(59, 130, 246, 0.8)',
|
|
1405
2011
|
stack: 'tokens',
|
|
1406
2012
|
borderRadius: 2,
|
|
1407
2013
|
},
|
|
1408
2014
|
{
|
|
1409
2015
|
label: 'Output',
|
|
1410
2016
|
data: dailyTokens.map(d => d.outputTokens || 0),
|
|
1411
|
-
backgroundColor: 'rgba(
|
|
2017
|
+
backgroundColor: 'rgba(168, 85, 247, 0.8)',
|
|
1412
2018
|
stack: 'tokens',
|
|
1413
2019
|
borderRadius: 2,
|
|
1414
2020
|
},
|
|
1415
2021
|
{
|
|
1416
2022
|
label: 'Cache Read',
|
|
1417
2023
|
data: dailyTokens.map(d => d.cacheReadTokens || 0),
|
|
1418
|
-
backgroundColor: 'rgba(
|
|
2024
|
+
backgroundColor: 'rgba(6, 182, 212, 0.6)',
|
|
1419
2025
|
stack: 'tokens',
|
|
1420
2026
|
borderRadius: 2,
|
|
1421
2027
|
},
|
|
@@ -1423,7 +2029,7 @@ function initCharts() {
|
|
|
1423
2029
|
label: 'Cumulative',
|
|
1424
2030
|
data: cumulativeData,
|
|
1425
2031
|
type: 'line',
|
|
1426
|
-
borderColor: '#
|
|
2032
|
+
borderColor: '#f59e0b',
|
|
1427
2033
|
borderDash: [5, 5],
|
|
1428
2034
|
pointRadius: 0,
|
|
1429
2035
|
fill: false,
|
|
@@ -1475,8 +2081,8 @@ function initCharts() {
|
|
|
1475
2081
|
{
|
|
1476
2082
|
label: 'Cost ($)',
|
|
1477
2083
|
data: daily.map(d => d.cost),
|
|
1478
|
-
borderColor: '#
|
|
1479
|
-
backgroundColor: 'rgba(
|
|
2084
|
+
borderColor: '#f59e0b',
|
|
2085
|
+
backgroundColor: 'rgba(245,158,11,0.1)',
|
|
1480
2086
|
fill: true,
|
|
1481
2087
|
tension: 0.3,
|
|
1482
2088
|
yAxisID: 'y',
|
|
@@ -1485,8 +2091,8 @@ function initCharts() {
|
|
|
1485
2091
|
{
|
|
1486
2092
|
label: 'Lines Added',
|
|
1487
2093
|
data: daily.map(d => d.linesAdded),
|
|
1488
|
-
borderColor: '#
|
|
1489
|
-
backgroundColor: 'rgba(
|
|
2094
|
+
borderColor: '#22d3a8',
|
|
2095
|
+
backgroundColor: 'rgba(34,211,168,0.1)',
|
|
1490
2096
|
fill: true,
|
|
1491
2097
|
tension: 0.3,
|
|
1492
2098
|
yAxisID: 'y1',
|
|
@@ -1555,7 +2161,7 @@ function initCharts() {
|
|
|
1555
2161
|
// Model doughnut
|
|
1556
2162
|
const models = DATA.modelBreakdown;
|
|
1557
2163
|
const modelLabels = Object.keys(models).filter(k => models[k].cost > 0);
|
|
1558
|
-
const modelColors = { opus: '#
|
|
2164
|
+
const modelColors = { opus: '#a855f7', sonnet: '#3b82f6', haiku: '#22d3a8', unknown: '#94a3b8' };
|
|
1559
2165
|
const modelTotalCost = modelLabels.reduce((s, m) => s + models[m].cost, 0);
|
|
1560
2166
|
new Chart(document.getElementById('chart-models'), {
|
|
1561
2167
|
type: 'doughnut',
|
|
@@ -1566,7 +2172,7 @@ function initCharts() {
|
|
|
1566
2172
|
}),
|
|
1567
2173
|
datasets: [{
|
|
1568
2174
|
data: modelLabels.map(m => models[m].cost),
|
|
1569
|
-
backgroundColor: modelLabels.map(m => modelColors[m] || '#
|
|
2175
|
+
backgroundColor: modelLabels.map(m => modelColors[m] || '#94a3b8'),
|
|
1570
2176
|
borderWidth: 0,
|
|
1571
2177
|
}],
|
|
1572
2178
|
},
|
|
@@ -1626,14 +2232,14 @@ function initCharts() {
|
|
|
1626
2232
|
{
|
|
1627
2233
|
label: 'Avg $/Commit',
|
|
1628
2234
|
data: bucketLabels.map(b => buckets[b].avgCostPerCommit),
|
|
1629
|
-
backgroundColor: '#
|
|
2235
|
+
backgroundColor: '#3b82f6',
|
|
1630
2236
|
borderRadius: 4,
|
|
1631
2237
|
yAxisID: 'y',
|
|
1632
2238
|
},
|
|
1633
2239
|
{
|
|
1634
2240
|
label: 'Sessions',
|
|
1635
2241
|
data: bucketLabels.map(b => buckets[b].sessions),
|
|
1636
|
-
backgroundColor: 'rgba(
|
|
2242
|
+
backgroundColor: 'rgba(168,85,247,0.4)',
|
|
1637
2243
|
borderRadius: 4,
|
|
1638
2244
|
yAxisID: 'y1',
|
|
1639
2245
|
},
|
|
@@ -1663,7 +2269,7 @@ function initHeatmap() {
|
|
|
1663
2269
|
for (let hour = 0; hour < 24; hour++) {
|
|
1664
2270
|
const val = heatmap[day][hour];
|
|
1665
2271
|
const intensity = val / maxVal;
|
|
1666
|
-
const bg = val === 0 ? 'var(--bg-hover)' : `rgba(
|
|
2272
|
+
const bg = val === 0 ? 'var(--bg-hover)' : `rgba(34,211,168,${0.2 + intensity * 0.8})`;
|
|
1667
2273
|
html += `<div class="heatmap-cell" style="background:${bg};" title="${DAY_LABELS[day]} ${hour}:00 — ${val} commits"></div>`;
|
|
1668
2274
|
}
|
|
1669
2275
|
}
|
|
@@ -1680,14 +2286,47 @@ function initHeatmap() {
|
|
|
1680
2286
|
html += '<span>Fewer</span><div style="display:flex;gap:1px;">';
|
|
1681
2287
|
const legendSteps = [0.2, 0.4, 0.6, 0.8, 1.0];
|
|
1682
2288
|
for (const step of legendSteps) {
|
|
1683
|
-
html += `<div style="width:14px;height:14px;border-radius:3px;background:rgba(
|
|
2289
|
+
html += `<div style="width:14px;height:14px;border-radius:3px;background:rgba(34,211,168,${0.2 + step * 0.8});"></div>`;
|
|
1684
2290
|
}
|
|
1685
2291
|
html += '</div><span>More commits</span></div>';
|
|
1686
2292
|
|
|
1687
2293
|
container.innerHTML = html;
|
|
1688
2294
|
}
|
|
1689
2295
|
|
|
1690
|
-
function bindEvents() {
|
|
2296
|
+
function bindEvents() {
|
|
2297
|
+
// Animate hero stat values on first render
|
|
2298
|
+
if (!initialRenderDone) {
|
|
2299
|
+
document.querySelectorAll('[data-animate-value]').forEach(el => {
|
|
2300
|
+
const target = parseFloat(el.dataset.animateValue);
|
|
2301
|
+
const prefix = el.dataset.animatePrefix || '';
|
|
2302
|
+
const suffix = el.dataset.animateSuffix || '';
|
|
2303
|
+
animateValue(el, 0, target, 1200, prefix, suffix);
|
|
2304
|
+
});
|
|
2305
|
+
|
|
2306
|
+
// Animate bar fills
|
|
2307
|
+
document.querySelectorAll('.survival-bar .fill, .waste-bar .fill').forEach(el => {
|
|
2308
|
+
const targetWidth = el.style.width;
|
|
2309
|
+
el.style.width = '0%';
|
|
2310
|
+
requestAnimationFrame(() => {
|
|
2311
|
+
el.style.transition = 'width 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
|
|
2312
|
+
el.style.width = targetWidth;
|
|
2313
|
+
});
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
// Scroll-triggered reveals
|
|
2317
|
+
const observer = new IntersectionObserver((entries) => {
|
|
2318
|
+
entries.forEach(entry => {
|
|
2319
|
+
if (entry.isIntersecting) {
|
|
2320
|
+
entry.target.classList.add('revealed');
|
|
2321
|
+
observer.unobserve(entry.target);
|
|
2322
|
+
}
|
|
2323
|
+
});
|
|
2324
|
+
}, { threshold: 0.1 });
|
|
2325
|
+
document.querySelectorAll('.scroll-reveal').forEach(el => observer.observe(el));
|
|
2326
|
+
|
|
2327
|
+
initialRenderDone = true;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
1691
2330
|
|
|
1692
2331
|
window.toggleExpand = function(idx) {
|
|
1693
2332
|
const row = document.getElementById(`expand-${idx}`);
|