evol-npm-pulse 1.0.0 → 2.0.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/README.md CHANGED
@@ -3,11 +3,11 @@
3
3
  One page to see how all your npm packages are doing: downloads over the last week, month, or year, a weekly sparkline for each package, the trend against the previous month, and the GitHub stars that go with it.
4
4
 
5
5
  - [See the packages](https://evoluteur.github.io/npm-pulse/)
6
- - [See the charts](https://evoluteur.github.io/npm-pulse/chart.html)
6
+ - [See the dashboard](https://evoluteur.github.io/npm-pulse/dashboard.html)
7
7
 
8
- ![npm Pulse](npm-pulse.png)
8
+ ![npm Pulse](screenshots/cards-dark.png)
9
9
 
10
- ![npm Pulse charts](npm-pulse-charts.png)
10
+ ![npm Pulse charts](screenshots/dash-dark.png)
11
11
 
12
12
  Everything is computed in the browser from three public APIs - no backend, no build step, no library:
13
13
 
@@ -15,13 +15,15 @@ Everything is computed in the browser from three public APIs - no backend, no bu
15
15
  - the [npm download counts API](https://github.com/npm/registry/blob/main/docs/download-counts.md) for a year of daily downloads (fetched in a single bulk call),
16
16
  - the [GitHub API](https://docs.github.com/en/rest) for stars and forks.
17
17
 
18
- The sparklines are 52 weekly buckets drawn as plain SVG, and the charts page shows total downloads month by month plus the ranking of the most downloaded packages.
18
+ The sparklines are 52 weekly buckets drawn as plain SVG, and the charts page shows total downloads month by month plus the ranking of the most downloaded packages. Each panel can be switched between bars, a pie, and a table (top-right toggle); the pie keeps names and values out of the wedges and lists them instead in a color-key legend below the chart.
19
19
 
20
20
  Downloads are recomputed once a day by npm, so the answers are cached in `localStorage` for 6 hours. Use the "Refresh" link to fetch them again.
21
21
 
22
22
  ## Use it for your own packages
23
23
 
24
- Change the `user` value (at the top of the [/js/npm-data.js](https://github.com/evoluteur/npm-pulse/blob/main/js/npm-data.js) file) to see your packages instead of mine:
24
+ The quickest way is to click the maintainer name in the title and type a different one, or add a `?user=` parameter to the page URL (e.g. `?user=sindresorhus`) - either way it sets both the npm maintainer and the GitHub user (for stars and forks) without editing any file, and carries over between the packages page and the charts page.
25
+
26
+ To make it stick as the default, change the `user` value (at the top of the [/js/npm-data.js](https://github.com/evoluteur/npm-pulse/blob/main/js/npm-data.js) file) to see your packages instead of mine:
25
27
 
26
28
  ```js
27
29
  const user = "evoluteur"; // npm maintainer whose packages are shown
package/css/me.css CHANGED
@@ -1,74 +1,130 @@
1
1
  /*
2
2
  https://evoluteur.github.io/
3
3
  (c) 2026 Olivier Giulieri
4
+
5
+ Layout only - every color comes from the theme loaded in css/themes
6
+ (see js/theme.js and https://evoluteur.github.io/omg-themes/).
4
7
  */
5
8
 
6
9
  html {
7
10
  margin: 0;
8
11
  padding: 0;
9
- scroll-behavior: smooth;
10
- background: #0288d1 url("spirals.png");
11
12
  }
12
13
  body {
13
14
  margin: 0;
14
15
  padding: 0;
15
- font-family: "Marcellus", serif;
16
+ font-family: "Overpass", sans-serif;
16
17
  font-size: 20px;
17
18
  font-weight: 300;
18
19
  line-height: 1.4em;
19
- color: white;
20
20
  overflow-x: hidden;
21
21
  }
22
22
  h1,
23
23
  h2 {
24
- color: #ffcc80;
24
+ color: var(--heading);
25
25
  }
26
26
  h1 {
27
27
  font-size: 2.5rem;
28
28
  }
29
29
  a,
30
30
  a:visited {
31
- color: #ffb74d;
31
+ color: var(--link);
32
32
  text-decoration: none;
33
33
  font-weight: 400;
34
- /* background-color: #2b88d2; */
35
34
  }
36
35
  a:active,
37
36
  a:hover {
38
- color: #ffcc80;
37
+ color: var(--link-hover);
39
38
  text-decoration: underline;
40
39
  font-weight: 400;
41
40
  }
42
41
  footer {
43
- color: #bdcff1;
44
- margin: 50px 20px 10px 20px;
42
+ color: var(--muted);
43
+ margin: 50px 20px 10px calc(20px + var(--content-left, 0px));
45
44
  > a {
46
45
  white-space: nowrap;
47
46
  }
48
47
  &:hover {
49
- color: white;
48
+ color: var(--text);
50
49
  }
51
50
  }
52
51
 
53
52
  .nav {
54
- color: white;
55
- background-color: #1a1a1a;
53
+ display: flex;
54
+ flex-wrap: wrap;
55
+ align-items: center;
56
+ justify-content: space-between;
57
+ gap: 20px;
58
+ color: var(--nav-text);
59
+ background-color: var(--nav-bg);
56
60
  padding: 8px 10px;
57
- > a {
58
- color: #92cddc;
59
- background-color: #1a1a1a !important;
61
+ /* the breadcrumb only: the theme picker keeps its own swatch colors */
62
+ .crumbs a {
63
+ color: var(--nav-link);
64
+ background-color: inherit !important;
60
65
  text-decoration: none;
61
66
  &:hover {
62
- color: #ff9900;
67
+ color: var(--nav-link-hover);
68
+ }
69
+ }
70
+ }
71
+
72
+ /* theme picker, same widget as OMG Themes */
73
+ #omg-theme-picker {
74
+ display: inline-flex;
75
+ align-items: center;
76
+ gap: 2px;
77
+ > a {
78
+ display: inline-flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ height: 28px;
82
+ width: 28px;
83
+ border-radius: 50%;
84
+ border: 1px solid transparent;
85
+ color: var(--nav-link);
86
+ &:hover {
87
+ color: var(--nav-link-hover);
88
+ }
89
+ &.selected {
90
+ color: var(--nav-link-hover);
63
91
  }
64
92
  }
65
93
  }
94
+
95
+ /* the nav bar spans the full width, the content clears the background
96
+ stripe of the light and dark themes */
66
97
  .gpc {
67
98
  padding: 0 20px;
99
+ padding-left: calc(20px + var(--content-left, 0px));
100
+ }
101
+
102
+ /* wraps the theme picker and the GitHub link on the right of the nav bar */
103
+ .nav-right {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ gap: 34px;
107
+ }
108
+
109
+ .github-link {
110
+ display: inline-flex;
111
+ align-items: center;
112
+ /* the octicon markup hardcodes fill="currentColor" on the <path>, so
113
+ set fill on the path itself rather than relying on the SVG's fill
114
+ to be inherited or on currentColor -- the currentColor + inherited
115
+ color approach was painting the wrong color (nearly invisible in
116
+ the light theme) until :hover forced a repaint */
117
+ svg path {
118
+ fill: var(--nav-link);
119
+ }
120
+ &:hover svg path {
121
+ fill: var(--nav-link-hover);
122
+ }
68
123
  }
124
+
69
125
  @media print {
70
- body {
71
- color: #1a1a1a !important;
126
+ .nav-right {
127
+ display: none;
72
128
  }
73
129
  @page {
74
130
  margin: 1cm 1cm;
package/css/pulse.css CHANGED
@@ -2,11 +2,133 @@
2
2
  npm-pulse
3
3
  https://evoluteur.github.io/npm-pulse/
4
4
  (c) 2026 Olivier Giulieri
5
+
6
+ Colors come from the theme loaded in css/themes (see js/theme.js).
5
7
  */
6
8
 
7
- #title > a {
9
+ #title {
10
+ display: flex;
11
+ flex-wrap: wrap;
12
+ align-items: center;
13
+ justify-content: space-between;
14
+ gap: 20px;
15
+ }
16
+ #title-main > a {
8
17
  text-transform: none;
9
18
  }
19
+ .header-links {
20
+ display: inline-flex;
21
+ align-items: center;
22
+ gap: 14px;
23
+ }
24
+ /* Meet-the-Fans, in the title next to the site picker */
25
+ .fans-link {
26
+ margin-left: 6px;
27
+ vertical-align: -3px;
28
+ }
29
+ .icon-link {
30
+ display: inline-flex;
31
+ align-items: center;
32
+ svg {
33
+ height: 24px;
34
+ width: 24px;
35
+ }
36
+ svg {
37
+ fill: var(--link);
38
+ }
39
+ &:hover svg {
40
+ fill: var(--link-hover);
41
+ }
42
+ &.current svg {
43
+ fill: var(--icon-current);
44
+ }
45
+ }
46
+ .user-input {
47
+ font-family: inherit;
48
+ font-size: 2.5rem;
49
+ font-weight: inherit;
50
+ line-height: inherit;
51
+ color: var(--heading);
52
+ background: transparent;
53
+ border: none;
54
+ border-bottom: 2px dashed transparent;
55
+ padding: 0;
56
+ }
57
+ .user-input:hover,
58
+ .user-input:focus {
59
+ border-bottom-color: var(--heading);
60
+ outline: none;
61
+ }
62
+ .site-picker {
63
+ /* sized to the selected option where the browser supports it */
64
+ field-sizing: content;
65
+ font-family: inherit;
66
+ font-size: 2.5rem;
67
+ font-weight: inherit;
68
+ line-height: inherit;
69
+ color: var(--heading);
70
+ background: transparent;
71
+ border: none;
72
+ border-bottom: 2px dashed transparent;
73
+ padding: 0;
74
+ cursor: pointer;
75
+ &:hover,
76
+ &:focus {
77
+ border-bottom-color: var(--heading);
78
+ outline: none;
79
+ }
80
+ > option {
81
+ font-size: 1rem;
82
+ color: var(--input-text);
83
+ background-color: var(--input-bg);
84
+ }
85
+ }
86
+ .user-edit {
87
+ display: inline-flex;
88
+ align-items: center;
89
+ gap: 8px;
90
+ }
91
+ .edit-icon {
92
+ display: inline-flex;
93
+ align-items: center;
94
+ background: none;
95
+ border: none;
96
+ padding: 0;
97
+ cursor: pointer;
98
+ color: var(--heading);
99
+ }
100
+ .edit-icon:hover {
101
+ color: var(--text);
102
+ }
103
+ .edit-icon svg {
104
+ fill: currentColor;
105
+ width: 18px;
106
+ height: 18px;
107
+ }
108
+ .duration-picker {
109
+ /* sized to the selected option where the browser supports it */
110
+ field-sizing: content;
111
+ font-family: inherit;
112
+ font-size: inherit;
113
+ font-weight: inherit;
114
+ line-height: inherit;
115
+ color: var(--heading);
116
+ background: transparent;
117
+ border: none;
118
+ border-bottom: 2px dashed transparent;
119
+ padding: 0;
120
+ cursor: pointer;
121
+ &:hover,
122
+ &:focus {
123
+ border-bottom-color: var(--heading);
124
+ outline: none;
125
+ }
126
+ > option {
127
+ font-size: 1rem;
128
+ color: var(--input-text);
129
+ background-color: var(--input-bg);
130
+ }
131
+ }
10
132
  .totals {
11
133
  font-size: 1.2rem;
12
134
  white-space: nowrap;
@@ -26,17 +148,19 @@
26
148
  }
27
149
  input,
28
150
  select {
151
+ font-family: inherit;
29
152
  font-size: 16px;
30
153
  padding: 5px 10px;
31
154
  margin-left: 5px;
32
- }
33
- .tools {
34
- white-space: nowrap;
155
+ color: var(--input-text);
156
+ background-color: var(--input-bg);
157
+ border: 1px solid var(--input-border);
158
+ border-radius: 4px;
35
159
  }
36
160
  }
37
161
  #summary {
38
162
  font-size: 0.9em;
39
- color: #bbdefb;
163
+ color: var(--muted);
40
164
  }
41
165
 
42
166
  .projects {
@@ -49,10 +173,10 @@
49
173
  flex-direction: column;
50
174
  justify-content: space-between;
51
175
  flex-grow: 1;
52
- background-color: #5594f4;
176
+ background-color: var(--card-bg);
53
177
  width: 300px;
54
178
  max-width: 100%;
55
- border: 3px solid #0288d1;
179
+ border: 3px solid var(--card-border);
56
180
  border-radius: 10px;
57
181
  padding: 5px 20px 15px;
58
182
  .desc {
@@ -76,7 +200,7 @@
76
200
  }
77
201
  > .dls {
78
202
  font-size: 1.1rem;
79
- color: white;
203
+ color: var(--card-strong);
80
204
  white-space: nowrap;
81
205
  }
82
206
  }
@@ -88,22 +212,28 @@
88
212
  align-items: baseline;
89
213
  margin-top: 12px;
90
214
  font-size: 0.85em;
91
- color: #dbe9ff;
215
+ color: var(--card-foot);
92
216
  }
93
217
  .lang {
218
+ display: inline-flex;
219
+ align-items: center;
94
220
  margin-right: 10px;
95
- padding: 3px 10px;
96
- border-radius: 10px;
97
- border: solid 1px white;
98
221
  white-space: nowrap;
99
222
  }
223
+ .lang-dot {
224
+ display: inline-block;
225
+ width: 12px;
226
+ height: 12px;
227
+ margin-right: 6px;
228
+ border-radius: 50%;
229
+ }
100
230
  .trend {
101
231
  white-space: nowrap;
102
232
  &.up {
103
- color: #b9f6ca;
233
+ color: var(--up);
104
234
  }
105
235
  &.down {
106
- color: #ffcdd2;
236
+ color: var(--down);
107
237
  }
108
238
  }
109
239
  .crud-icon {
@@ -126,17 +256,17 @@
126
256
  height: 46px;
127
257
  overflow: visible;
128
258
  > polygon {
129
- fill: rgba(255, 255, 255, 0.22);
259
+ fill: var(--spark-fill);
130
260
  }
131
261
  > polyline {
132
262
  fill: none;
133
- stroke: #ffcc80;
263
+ stroke: var(--spark-line);
134
264
  stroke-width: 1.5;
135
265
  stroke-linejoin: round;
136
266
  vector-effect: non-scaling-stroke;
137
267
  }
138
268
  > circle {
139
- fill: #ffcc80;
269
+ fill: var(--spark-line);
140
270
  vector-effect: non-scaling-stroke;
141
271
  }
142
272
  }
@@ -145,7 +275,7 @@
145
275
  display: flex;
146
276
  align-items: center;
147
277
  font-size: 0.85em;
148
- color: #dbe9ff;
278
+ color: var(--card-foot);
149
279
  }
150
280
 
151
281
  .noresults {
@@ -153,46 +283,243 @@
153
283
  padding: 30px;
154
284
  }
155
285
 
156
- /* charts page */
286
+ /* charts page - the charts sit on a panel, like github-projects-cards */
287
+ .chart-row {
288
+ display: flex;
289
+ flex-wrap: wrap;
290
+ gap: 24px;
291
+ > .series-panel {
292
+ flex: 1 1 300px;
293
+ min-width: 0;
294
+ margin: 0;
295
+ }
296
+ }
297
+ .panels-note {
298
+ font-size: 0.85em;
299
+ color: var(--muted);
300
+ margin: 0 0 12px 0;
301
+ }
302
+ .chart-head {
303
+ display: flex;
304
+ align-items: center;
305
+ justify-content: space-between;
306
+ gap: 12px;
307
+ }
308
+ .chart-toggle {
309
+ display: inline-flex;
310
+ gap: 4px;
311
+ > button {
312
+ display: inline-flex;
313
+ align-items: center;
314
+ padding: 4px;
315
+ cursor: pointer;
316
+ border: 1px solid transparent;
317
+ border-radius: 6px;
318
+ background: none;
319
+ color: var(--panel-muted);
320
+ svg {
321
+ height: 18px;
322
+ width: 18px;
323
+ fill: currentColor;
324
+ }
325
+ &:hover {
326
+ color: var(--panel-text);
327
+ }
328
+ &.selected {
329
+ color: var(--panel-text);
330
+ border-color: var(--panel-grid);
331
+ }
332
+ }
333
+ }
334
+ .data-table {
335
+ width: 100%;
336
+ border-collapse: collapse;
337
+ font-size: 0.85em;
338
+ color: var(--panel-text);
339
+ th,
340
+ td {
341
+ text-align: left;
342
+ padding: 5px 10px;
343
+ border-bottom: 1px solid var(--panel-grid);
344
+ }
345
+ td + td,
346
+ th + th {
347
+ text-align: right;
348
+ width: 22%;
349
+ }
350
+ }
157
351
  .chart-block {
158
- margin: 0 0 50px 0;
352
+ margin: 0 0 24px 0;
353
+ padding: 16px 20px 20px;
354
+ border: 3px solid var(--panel-border);
355
+ border-radius: 10px;
356
+ background: var(--panel-bg);
357
+ color: var(--panel-text);
159
358
  > h2 {
359
+ color: var(--heading);
160
360
  margin-bottom: 5px;
161
361
  }
162
362
  > .chart-note {
163
363
  font-size: 0.85em;
164
- color: #bbdefb;
364
+ color: var(--panel-muted);
165
365
  margin: 0 0 15px 0;
166
366
  }
167
367
  }
368
+
369
+ /* pies and their legend, also used outside a panel (the shared legend of
370
+ the Stars / Forks / Downloads row) */
371
+ .legend {
372
+ white-space: nowrap;
373
+ text-transform: capitalize;
374
+ }
375
+ /* bars and pie are the same boxes, morphed with CSS transitions
376
+ (ported from react-morph-charts) */
377
+ .morph {
378
+ position: relative;
379
+ width: 100%;
380
+ height: 230px;
381
+ }
382
+ .morph-item {
383
+ position: absolute;
384
+ top: 0;
385
+ left: 0;
386
+ display: flex;
387
+ align-items: center;
388
+ justify-content: center;
389
+ box-sizing: border-box;
390
+ overflow: hidden;
391
+ border-radius: 6px;
392
+ text-align: center;
393
+ contain: layout style paint;
394
+ transition:
395
+ transform 0.7s cubic-bezier(0.22, 1, 0.36, 1),
396
+ width 0.7s cubic-bezier(0.22, 1, 0.36, 1),
397
+ height 0.7s cubic-bezier(0.22, 1, 0.36, 1),
398
+ clip-path 0.7s cubic-bezier(0.22, 1, 0.36, 1);
399
+ }
400
+ .morph.no-transition .morph-item,
401
+ .morph.no-transition .morph-label {
402
+ transition: none;
403
+ }
404
+ .morph-label {
405
+ display: flex;
406
+ flex-direction: column;
407
+ gap: 1px;
408
+ padding: 2px;
409
+ font-size: 0.7rem;
410
+ line-height: 1.15;
411
+ transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
412
+ }
413
+ .morph-name {
414
+ overflow-wrap: anywhere;
415
+ }
416
+ .morph-value {
417
+ font-size: 0.9em;
418
+ opacity: 0.85;
419
+ }
420
+ .morph-item.narrow .morph-name {
421
+ display: none;
422
+ }
423
+ .morph-item.tiny .morph-label {
424
+ display: none;
425
+ }
426
+ .morph-item.pie .morph-label {
427
+ /* names/values already shown in the legend below the pie */
428
+ display: none;
429
+ }
430
+ .pie-legend {
431
+ display: flex;
432
+ flex-wrap: wrap;
433
+ justify-content: center;
434
+ gap: 4px 14px;
435
+ font-size: 0.8em;
436
+ color: var(--muted);
437
+ margin: 4px 0 24px;
438
+ .swatch {
439
+ margin: 0 4px 0 0;
440
+ }
441
+ }
442
+ .pie-key {
443
+ display: inline-flex;
444
+ align-items: center;
445
+ white-space: nowrap;
446
+ }
447
+ .swatch {
448
+ display: inline-block;
449
+ flex: none;
450
+ width: 12px;
451
+ height: 12px;
452
+ border-radius: 3px;
453
+ margin: 0 2px 0 12px;
454
+ &.stars {
455
+ background-color: var(--accent);
456
+ }
457
+ &.forks {
458
+ background-color: var(--fork);
459
+ }
460
+ &.downloads {
461
+ background-color: var(--card-foot);
462
+ }
463
+ }
168
464
  .chart {
169
465
  width: 100%;
170
466
  height: auto;
171
467
  overflow: visible;
172
468
  .grid {
173
- stroke: rgba(255, 255, 255, 0.25);
469
+ stroke: var(--panel-grid);
174
470
  stroke-width: 1;
175
471
  }
176
472
  .bar {
177
- fill: #ffcc80;
473
+ fill: var(--accent);
178
474
  }
179
475
  .bar.partial {
180
- fill: rgba(255, 204, 128, 0.6);
181
- stroke: #ffcc80;
476
+ fill: var(--accent);
477
+ fill-opacity: 0.45;
478
+ stroke: var(--accent);
182
479
  stroke-dasharray: 3 3;
183
480
  }
481
+ .bar.stars {
482
+ fill: var(--accent);
483
+ }
484
+ .bar.forks {
485
+ fill: var(--fork);
486
+ }
487
+ .bar.downloads {
488
+ fill: var(--card-foot);
489
+ }
490
+ .bar.up {
491
+ fill: var(--up);
492
+ }
493
+ .bar.down {
494
+ fill: var(--down);
495
+ }
496
+ .zero {
497
+ stroke: var(--panel-grid);
498
+ stroke-width: 1;
499
+ }
184
500
  .axis {
185
- fill: white;
501
+ fill: var(--panel-text);
186
502
  font-size: 12px;
187
- font-family: "Marcellus", serif;
503
+ font-family: inherit;
188
504
  }
189
505
  .axis.value {
190
- fill: #dbe9ff;
506
+ fill: var(--panel-muted);
191
507
  }
192
508
  .bar-label {
193
- fill: white;
509
+ fill: var(--panel-text);
194
510
  font-size: 13px;
195
- font-family: "Marcellus", serif;
511
+ font-family: inherit;
512
+ }
513
+ }
514
+
515
+ /* downloads over the picked period, in its own color */
516
+ .period-chart {
517
+ .bar {
518
+ fill: var(--period-bar);
519
+ }
520
+ .bar.partial {
521
+ fill: var(--period-bar);
522
+ stroke: var(--period-bar);
196
523
  }
197
524
  }
198
525
 
@@ -205,3 +532,23 @@
205
532
  break-inside: avoid;
206
533
  }
207
534
  }
535
+
536
+ /* tooltip of the pie slices, following the pointer */
537
+ .chart-tooltip {
538
+ position: fixed;
539
+ z-index: 900;
540
+ display: flex;
541
+ flex-direction: column;
542
+ pointer-events: none;
543
+ padding: 6px 10px;
544
+ border-radius: 6px;
545
+ font-size: 0.8rem;
546
+ line-height: 1.3;
547
+ white-space: nowrap;
548
+ color: var(--nav-text);
549
+ background-color: var(--nav-bg);
550
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
551
+ &[hidden] {
552
+ display: none;
553
+ }
554
+ }
Binary file