roast-my-design-system 3.6.0 → 3.7.1
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 +10 -5
- package/package.json +2 -2
- package/skills/roast-my-design-system/scripts/benchmark/benchmark.json +332 -41
- package/skills/roast-my-design-system/scripts/diagnose/index.mjs +43 -16
- package/skills/roast-my-design-system/scripts/lib/neverimported.mjs +17 -0
- package/skills/roast-my-design-system/scripts/rules/build.mjs +2 -7
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
Run this skill on your codebase and get, in about a second:
|
|
8
8
|
|
|
9
9
|
- **A health score you can defend in a meeting.** 0-100, deterministic, benchmarked against Ideal Design System norms, 30 scanned public repos and 10 reputable design systems (Primer, Polaris, Carbon, shadcn/ui…).
|
|
10
|
-
- **The receipts behind it.** Every colour, spacing value, typeface, duplicated component
|
|
10
|
+
- **The receipts behind it.** Every colour and its near-identical twin, every spacing value, typeface, duplicated or never-imported component, inline style and !important, with real file paths, in one self-contained HTML report you can open, Slack or email.
|
|
11
11
|
- **The first fixes ranked by payoff.** A "Where to start" list derived from your own numbers: keep the report as the audit, or hand it to Claude as the punch list for the fix.
|
|
12
12
|
- **Rules that stop the mess coming back.** A generated `design-system-rules.md` for your CLAUDE.md or .cursor/rules: canonical components, your token file, known duplicates to avoid, so your AI agent follows your system instead of guessing at it.
|
|
13
13
|
|
|
@@ -24,11 +24,11 @@ Your AI agent (Claude, Cursor, Copilot) builds UI by imitating what's already in
|
|
|
24
24
|
|
|
25
25
|
The full report for vercel/ai-chatbot, top to bottom:
|
|
26
26
|
|
|
27
|
-

|
|
28
28
|
|
|
29
29
|
The same report in light mode (one file, built-in toggle):
|
|
30
30
|
|
|
31
|
-

|
|
32
32
|
|
|
33
33
|
## What makes the numbers trustworthy
|
|
34
34
|
|
|
@@ -120,11 +120,16 @@ Three real roasts of public repos, hosted as-is (the same self-contained HTML th
|
|
|
120
120
|
| Duplicated components | 0 | 14 | 10 |
|
|
121
121
|
| Inline style blocks | 0 | 42 | 12 |
|
|
122
122
|
| Arbitrary Tailwind values | ~20 | 91 | 0 |
|
|
123
|
+
| Near-identical colour pairs | 0 | 2 | 1 |
|
|
124
|
+
| !important declarations | 0 | 5 | 3 |
|
|
125
|
+
| Components never imported | 0 | 0 | 0 |
|
|
123
126
|
|
|
124
127
|
Yes, the median repo is already a mess. That's the point.
|
|
125
128
|
|
|
126
129
|
## License
|
|
127
130
|
|
|
128
|
-
MIT
|
|
131
|
+
MIT. The code is yours to fork, modify and redistribute; the copyright notice travels with it.
|
|
129
132
|
|
|
130
|
-
|
|
133
|
+
**roast-my-design-system**™ and the GK mark are trademarks of Greg Kozakiewicz. Forking is welcome, republishing under this name is not: see [brand and attribution](https://pencilrebel.github.io/roast-my-design-system/brand.html).
|
|
134
|
+
|
|
135
|
+
Built and designed by <a href="https://gregkozakiewicz.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pencilrebel/roast-my-design-system/main/assets/gk-mark-dark.png?v=3.7.1"><img src="https://raw.githubusercontent.com/pencilrebel/roast-my-design-system/main/assets/gk-mark.png?v=3.7.1" height="15" alt="GK mark"></picture> Greg Kozakiewicz</a>.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roast-my-design-system",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Roast your design system with real data. A deterministic, zero-dependency scanner that counts
|
|
3
|
+
"version": "3.7.1",
|
|
4
|
+
"description": "Roast your design system with real data. A deterministic, zero-dependency scanner that counts everything that betrays a design system, from colours and their near-identical twins to greys, spacing values, typefaces, duplicated and never-imported components, inline styles, !important and arbitrary Tailwind values, scores it 0-100 against Ideal Design System norms and a 30-repo benchmark, generates a shareable HTML diagnosis, and with --rules writes the agent rules file (CLAUDE.md, .cursor/rules) that stops the mess coming back.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design-system",
|
|
7
7
|
"design-tokens",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"builtAt": "2026-08-
|
|
2
|
+
"builtAt": "2026-08-03T16:33:32.652Z",
|
|
3
3
|
"repoCount": 30,
|
|
4
4
|
"ideal2026": {
|
|
5
5
|
"colors": {
|
|
@@ -38,6 +38,18 @@
|
|
|
38
38
|
"value": 0,
|
|
39
39
|
"note": "styling belongs to the system"
|
|
40
40
|
},
|
|
41
|
+
"nearPairs": {
|
|
42
|
+
"value": 0,
|
|
43
|
+
"note": "near-identical colours are copy-paste, not decisions"
|
|
44
|
+
},
|
|
45
|
+
"important": {
|
|
46
|
+
"value": 0,
|
|
47
|
+
"note": "!important is the cascade admitting defeat"
|
|
48
|
+
},
|
|
49
|
+
"neverImported": {
|
|
50
|
+
"value": 0,
|
|
51
|
+
"note": "a system component nobody imports is dead weight"
|
|
52
|
+
},
|
|
41
53
|
"arbitrary": {
|
|
42
54
|
"value": 20,
|
|
43
55
|
"note": "bracket escape hatches; a handful of deliberate exceptions"
|
|
@@ -104,7 +116,7 @@
|
|
|
104
116
|
174,
|
|
105
117
|
206,
|
|
106
118
|
246,
|
|
107
|
-
|
|
119
|
+
303,
|
|
108
120
|
385,
|
|
109
121
|
403,
|
|
110
122
|
1645
|
|
@@ -112,7 +124,7 @@
|
|
|
112
124
|
"p25": 38,
|
|
113
125
|
"median": 96,
|
|
114
126
|
"p75": 170,
|
|
115
|
-
"p90":
|
|
127
|
+
"p90": 311
|
|
116
128
|
},
|
|
117
129
|
"greys": {
|
|
118
130
|
"values": [
|
|
@@ -373,7 +385,7 @@
|
|
|
373
385
|
95,
|
|
374
386
|
103,
|
|
375
387
|
115,
|
|
376
|
-
|
|
388
|
+
189
|
|
377
389
|
],
|
|
378
390
|
"p25": 1,
|
|
379
391
|
"median": 14,
|
|
@@ -397,7 +409,7 @@
|
|
|
397
409
|
23,
|
|
398
410
|
37,
|
|
399
411
|
41,
|
|
400
|
-
|
|
412
|
+
43,
|
|
401
413
|
46,
|
|
402
414
|
47,
|
|
403
415
|
51,
|
|
@@ -411,7 +423,7 @@
|
|
|
411
423
|
122,
|
|
412
424
|
173,
|
|
413
425
|
1178,
|
|
414
|
-
|
|
426
|
+
2923
|
|
415
427
|
],
|
|
416
428
|
"p25": 8,
|
|
417
429
|
"median": 42,
|
|
@@ -443,7 +455,7 @@
|
|
|
443
455
|
234,
|
|
444
456
|
271,
|
|
445
457
|
321,
|
|
446
|
-
|
|
458
|
+
380,
|
|
447
459
|
407,
|
|
448
460
|
468,
|
|
449
461
|
470,
|
|
@@ -456,6 +468,120 @@
|
|
|
456
468
|
"p75": 309,
|
|
457
469
|
"p90": 472
|
|
458
470
|
},
|
|
471
|
+
"nearPairs": {
|
|
472
|
+
"values": [
|
|
473
|
+
0,
|
|
474
|
+
0,
|
|
475
|
+
0,
|
|
476
|
+
0,
|
|
477
|
+
0,
|
|
478
|
+
0,
|
|
479
|
+
0,
|
|
480
|
+
0,
|
|
481
|
+
0,
|
|
482
|
+
0,
|
|
483
|
+
0,
|
|
484
|
+
1,
|
|
485
|
+
1,
|
|
486
|
+
2,
|
|
487
|
+
2,
|
|
488
|
+
2,
|
|
489
|
+
9,
|
|
490
|
+
12,
|
|
491
|
+
13,
|
|
492
|
+
13,
|
|
493
|
+
15,
|
|
494
|
+
16,
|
|
495
|
+
23,
|
|
496
|
+
26,
|
|
497
|
+
30,
|
|
498
|
+
37,
|
|
499
|
+
47,
|
|
500
|
+
49,
|
|
501
|
+
70,
|
|
502
|
+
146
|
|
503
|
+
],
|
|
504
|
+
"p25": 0,
|
|
505
|
+
"median": 2,
|
|
506
|
+
"p75": 21,
|
|
507
|
+
"p90": 47
|
|
508
|
+
},
|
|
509
|
+
"important": {
|
|
510
|
+
"values": [
|
|
511
|
+
0,
|
|
512
|
+
0,
|
|
513
|
+
0,
|
|
514
|
+
0,
|
|
515
|
+
0,
|
|
516
|
+
0,
|
|
517
|
+
0,
|
|
518
|
+
0,
|
|
519
|
+
0,
|
|
520
|
+
0,
|
|
521
|
+
0,
|
|
522
|
+
1,
|
|
523
|
+
1,
|
|
524
|
+
2,
|
|
525
|
+
4,
|
|
526
|
+
5,
|
|
527
|
+
5,
|
|
528
|
+
6,
|
|
529
|
+
9,
|
|
530
|
+
9,
|
|
531
|
+
11,
|
|
532
|
+
12,
|
|
533
|
+
20,
|
|
534
|
+
35,
|
|
535
|
+
42,
|
|
536
|
+
42,
|
|
537
|
+
74,
|
|
538
|
+
83,
|
|
539
|
+
88,
|
|
540
|
+
161
|
|
541
|
+
],
|
|
542
|
+
"p25": 0,
|
|
543
|
+
"median": 5,
|
|
544
|
+
"p75": 18,
|
|
545
|
+
"p90": 75
|
|
546
|
+
},
|
|
547
|
+
"neverImported": {
|
|
548
|
+
"values": [
|
|
549
|
+
0,
|
|
550
|
+
0,
|
|
551
|
+
0,
|
|
552
|
+
0,
|
|
553
|
+
0,
|
|
554
|
+
0,
|
|
555
|
+
0,
|
|
556
|
+
0,
|
|
557
|
+
0,
|
|
558
|
+
0,
|
|
559
|
+
0,
|
|
560
|
+
0,
|
|
561
|
+
0,
|
|
562
|
+
0,
|
|
563
|
+
0,
|
|
564
|
+
0,
|
|
565
|
+
0,
|
|
566
|
+
0,
|
|
567
|
+
5,
|
|
568
|
+
7,
|
|
569
|
+
10,
|
|
570
|
+
13,
|
|
571
|
+
22,
|
|
572
|
+
22,
|
|
573
|
+
26,
|
|
574
|
+
36,
|
|
575
|
+
39,
|
|
576
|
+
45,
|
|
577
|
+
94,
|
|
578
|
+
100
|
|
579
|
+
],
|
|
580
|
+
"p25": 0,
|
|
581
|
+
"median": 0,
|
|
582
|
+
"p75": 20,
|
|
583
|
+
"p90": 40
|
|
584
|
+
},
|
|
459
585
|
"components": {
|
|
460
586
|
"values": [
|
|
461
587
|
9,
|
|
@@ -471,27 +597,27 @@
|
|
|
471
597
|
342,
|
|
472
598
|
350,
|
|
473
599
|
417,
|
|
474
|
-
|
|
475
|
-
|
|
600
|
+
555,
|
|
601
|
+
637,
|
|
476
602
|
777,
|
|
477
|
-
|
|
478
|
-
|
|
603
|
+
796,
|
|
604
|
+
836,
|
|
479
605
|
936,
|
|
480
606
|
940,
|
|
481
|
-
|
|
607
|
+
962,
|
|
482
608
|
970,
|
|
483
|
-
|
|
609
|
+
1052,
|
|
484
610
|
1081,
|
|
485
611
|
1216,
|
|
486
612
|
1379,
|
|
487
613
|
1414,
|
|
488
614
|
1619,
|
|
489
|
-
|
|
490
|
-
|
|
615
|
+
2134,
|
|
616
|
+
2566
|
|
491
617
|
],
|
|
492
618
|
"p25": 233,
|
|
493
|
-
"median":
|
|
494
|
-
"p75":
|
|
619
|
+
"median": 707,
|
|
620
|
+
"p75": 1032,
|
|
495
621
|
"p90": 1435
|
|
496
622
|
}
|
|
497
623
|
},
|
|
@@ -511,6 +637,9 @@
|
|
|
511
637
|
"exactDuplicates": 115,
|
|
512
638
|
"inlineStyles": 79,
|
|
513
639
|
"arbitrary": 321,
|
|
640
|
+
"nearPairs": 47,
|
|
641
|
+
"important": 161,
|
|
642
|
+
"neverImported": 39,
|
|
514
643
|
"components": 1414
|
|
515
644
|
}
|
|
516
645
|
},
|
|
@@ -529,6 +658,9 @@
|
|
|
529
658
|
"exactDuplicates": 3,
|
|
530
659
|
"inlineStyles": 82,
|
|
531
660
|
"arbitrary": 0,
|
|
661
|
+
"nearPairs": 49,
|
|
662
|
+
"important": 88,
|
|
663
|
+
"neverImported": 0,
|
|
532
664
|
"components": 342
|
|
533
665
|
}
|
|
534
666
|
},
|
|
@@ -547,13 +679,16 @@
|
|
|
547
679
|
"exactDuplicates": 4,
|
|
548
680
|
"inlineStyles": 23,
|
|
549
681
|
"arbitrary": 0,
|
|
682
|
+
"nearPairs": 37,
|
|
683
|
+
"important": 4,
|
|
684
|
+
"neverImported": 0,
|
|
550
685
|
"components": 50
|
|
551
686
|
}
|
|
552
687
|
},
|
|
553
688
|
{
|
|
554
689
|
"repo": "outline/outline",
|
|
555
690
|
"designSystem": "custom",
|
|
556
|
-
"codeFiles":
|
|
691
|
+
"codeFiles": 2122,
|
|
557
692
|
"metrics": {
|
|
558
693
|
"colors": 89,
|
|
559
694
|
"greys": 15,
|
|
@@ -565,7 +700,10 @@
|
|
|
565
700
|
"exactDuplicates": 23,
|
|
566
701
|
"inlineStyles": 76,
|
|
567
702
|
"arbitrary": 0,
|
|
568
|
-
"
|
|
703
|
+
"nearPairs": 1,
|
|
704
|
+
"important": 0,
|
|
705
|
+
"neverImported": 0,
|
|
706
|
+
"components": 637
|
|
569
707
|
}
|
|
570
708
|
},
|
|
571
709
|
{
|
|
@@ -583,6 +721,9 @@
|
|
|
583
721
|
"exactDuplicates": 2,
|
|
584
722
|
"inlineStyles": 0,
|
|
585
723
|
"arbitrary": 4,
|
|
724
|
+
"nearPairs": 0,
|
|
725
|
+
"important": 5,
|
|
726
|
+
"neverImported": 0,
|
|
586
727
|
"components": 220
|
|
587
728
|
}
|
|
588
729
|
},
|
|
@@ -601,6 +742,9 @@
|
|
|
601
742
|
"exactDuplicates": 0,
|
|
602
743
|
"inlineStyles": 0,
|
|
603
744
|
"arbitrary": 0,
|
|
745
|
+
"nearPairs": 0,
|
|
746
|
+
"important": 0,
|
|
747
|
+
"neverImported": 0,
|
|
604
748
|
"components": 25
|
|
605
749
|
}
|
|
606
750
|
},
|
|
@@ -619,6 +763,9 @@
|
|
|
619
763
|
"exactDuplicates": 0,
|
|
620
764
|
"inlineStyles": 0,
|
|
621
765
|
"arbitrary": 60,
|
|
766
|
+
"nearPairs": 0,
|
|
767
|
+
"important": 1,
|
|
768
|
+
"neverImported": 94,
|
|
622
769
|
"components": 182
|
|
623
770
|
}
|
|
624
771
|
},
|
|
@@ -637,6 +784,9 @@
|
|
|
637
784
|
"exactDuplicates": 0,
|
|
638
785
|
"inlineStyles": 0,
|
|
639
786
|
"arbitrary": 0,
|
|
787
|
+
"nearPairs": 0,
|
|
788
|
+
"important": 0,
|
|
789
|
+
"neverImported": 0,
|
|
640
790
|
"components": 9
|
|
641
791
|
}
|
|
642
792
|
},
|
|
@@ -655,6 +805,9 @@
|
|
|
655
805
|
"exactDuplicates": 1,
|
|
656
806
|
"inlineStyles": 2,
|
|
657
807
|
"arbitrary": 16,
|
|
808
|
+
"nearPairs": 0,
|
|
809
|
+
"important": 2,
|
|
810
|
+
"neverImported": 0,
|
|
658
811
|
"components": 60
|
|
659
812
|
}
|
|
660
813
|
},
|
|
@@ -673,6 +826,9 @@
|
|
|
673
826
|
"exactDuplicates": 1,
|
|
674
827
|
"inlineStyles": 7,
|
|
675
828
|
"arbitrary": 103,
|
|
829
|
+
"nearPairs": 0,
|
|
830
|
+
"important": 9,
|
|
831
|
+
"neverImported": 0,
|
|
676
832
|
"components": 315
|
|
677
833
|
}
|
|
678
834
|
},
|
|
@@ -691,13 +847,16 @@
|
|
|
691
847
|
"exactDuplicates": 1,
|
|
692
848
|
"inlineStyles": 11,
|
|
693
849
|
"arbitrary": 148,
|
|
850
|
+
"nearPairs": 0,
|
|
851
|
+
"important": 0,
|
|
852
|
+
"neverImported": 100,
|
|
694
853
|
"components": 272
|
|
695
854
|
}
|
|
696
855
|
},
|
|
697
856
|
{
|
|
698
857
|
"repo": "documenso/documenso",
|
|
699
858
|
"designSystem": "custom",
|
|
700
|
-
"codeFiles":
|
|
859
|
+
"codeFiles": 1912,
|
|
701
860
|
"metrics": {
|
|
702
861
|
"colors": 36,
|
|
703
862
|
"greys": 13,
|
|
@@ -709,13 +868,16 @@
|
|
|
709
868
|
"exactDuplicates": 17,
|
|
710
869
|
"inlineStyles": 69,
|
|
711
870
|
"arbitrary": 234,
|
|
712
|
-
"
|
|
871
|
+
"nearPairs": 15,
|
|
872
|
+
"important": 0,
|
|
873
|
+
"neverImported": 45,
|
|
874
|
+
"components": 796
|
|
713
875
|
}
|
|
714
876
|
},
|
|
715
877
|
{
|
|
716
878
|
"repo": "dubinc/dub",
|
|
717
879
|
"designSystem": "custom",
|
|
718
|
-
"codeFiles":
|
|
880
|
+
"codeFiles": 3526,
|
|
719
881
|
"metrics": {
|
|
720
882
|
"colors": 102,
|
|
721
883
|
"greys": 14,
|
|
@@ -727,6 +889,9 @@
|
|
|
727
889
|
"exactDuplicates": 17,
|
|
728
890
|
"inlineStyles": 47,
|
|
729
891
|
"arbitrary": 470,
|
|
892
|
+
"nearPairs": 13,
|
|
893
|
+
"important": 1,
|
|
894
|
+
"neverImported": 22,
|
|
730
895
|
"components": 1379
|
|
731
896
|
}
|
|
732
897
|
},
|
|
@@ -745,6 +910,9 @@
|
|
|
745
910
|
"exactDuplicates": 59,
|
|
746
911
|
"inlineStyles": 18,
|
|
747
912
|
"arbitrary": 164,
|
|
913
|
+
"nearPairs": 13,
|
|
914
|
+
"important": 83,
|
|
915
|
+
"neverImported": 0,
|
|
748
916
|
"components": 940
|
|
749
917
|
}
|
|
750
918
|
},
|
|
@@ -763,13 +931,16 @@
|
|
|
763
931
|
"exactDuplicates": 92,
|
|
764
932
|
"inlineStyles": 119,
|
|
765
933
|
"arbitrary": 164,
|
|
934
|
+
"nearPairs": 0,
|
|
935
|
+
"important": 0,
|
|
936
|
+
"neverImported": 5,
|
|
766
937
|
"components": 1081
|
|
767
938
|
}
|
|
768
939
|
},
|
|
769
940
|
{
|
|
770
941
|
"repo": "lukevella/rallly",
|
|
771
942
|
"designSystem": "custom",
|
|
772
|
-
"codeFiles":
|
|
943
|
+
"codeFiles": 692,
|
|
773
944
|
"metrics": {
|
|
774
945
|
"colors": 26,
|
|
775
946
|
"greys": 4,
|
|
@@ -781,7 +952,10 @@
|
|
|
781
952
|
"exactDuplicates": 10,
|
|
782
953
|
"inlineStyles": 23,
|
|
783
954
|
"arbitrary": 27,
|
|
784
|
-
"
|
|
955
|
+
"nearPairs": 2,
|
|
956
|
+
"important": 0,
|
|
957
|
+
"neverImported": 22,
|
|
958
|
+
"components": 555
|
|
785
959
|
}
|
|
786
960
|
},
|
|
787
961
|
{
|
|
@@ -799,13 +973,16 @@
|
|
|
799
973
|
"exactDuplicates": 20,
|
|
800
974
|
"inlineStyles": 46,
|
|
801
975
|
"arbitrary": 407,
|
|
976
|
+
"nearPairs": 0,
|
|
977
|
+
"important": 42,
|
|
978
|
+
"neverImported": 13,
|
|
802
979
|
"components": 777
|
|
803
980
|
}
|
|
804
981
|
},
|
|
805
982
|
{
|
|
806
983
|
"repo": "elie222/inbox-zero",
|
|
807
984
|
"designSystem": "custom",
|
|
808
|
-
"codeFiles":
|
|
985
|
+
"codeFiles": 1821,
|
|
809
986
|
"metrics": {
|
|
810
987
|
"colors": 154,
|
|
811
988
|
"greys": 24,
|
|
@@ -817,7 +994,10 @@
|
|
|
817
994
|
"exactDuplicates": 21,
|
|
818
995
|
"inlineStyles": 5,
|
|
819
996
|
"arbitrary": 125,
|
|
820
|
-
"
|
|
997
|
+
"nearPairs": 26,
|
|
998
|
+
"important": 6,
|
|
999
|
+
"neverImported": 0,
|
|
1000
|
+
"components": 836
|
|
821
1001
|
}
|
|
822
1002
|
},
|
|
823
1003
|
{
|
|
@@ -835,6 +1015,9 @@
|
|
|
835
1015
|
"exactDuplicates": 7,
|
|
836
1016
|
"inlineStyles": 41,
|
|
837
1017
|
"arbitrary": 78,
|
|
1018
|
+
"nearPairs": 0,
|
|
1019
|
+
"important": 0,
|
|
1020
|
+
"neverImported": 0,
|
|
838
1021
|
"components": 190
|
|
839
1022
|
}
|
|
840
1023
|
},
|
|
@@ -853,13 +1036,16 @@
|
|
|
853
1036
|
"exactDuplicates": 0,
|
|
854
1037
|
"inlineStyles": 173,
|
|
855
1038
|
"arbitrary": 1,
|
|
1039
|
+
"nearPairs": 2,
|
|
1040
|
+
"important": 5,
|
|
1041
|
+
"neverImported": 0,
|
|
856
1042
|
"components": 417
|
|
857
1043
|
}
|
|
858
1044
|
},
|
|
859
1045
|
{
|
|
860
1046
|
"repo": "twentyhq/twenty",
|
|
861
1047
|
"designSystem": "library",
|
|
862
|
-
"codeFiles":
|
|
1048
|
+
"codeFiles": 15132,
|
|
863
1049
|
"metrics": {
|
|
864
1050
|
"colors": 1645,
|
|
865
1051
|
"greys": 100,
|
|
@@ -871,13 +1057,16 @@
|
|
|
871
1057
|
"exactDuplicates": 22,
|
|
872
1058
|
"inlineStyles": 122,
|
|
873
1059
|
"arbitrary": 0,
|
|
874
|
-
"
|
|
1060
|
+
"nearPairs": 146,
|
|
1061
|
+
"important": 9,
|
|
1062
|
+
"neverImported": 0,
|
|
1063
|
+
"components": 2134
|
|
875
1064
|
}
|
|
876
1065
|
},
|
|
877
1066
|
{
|
|
878
1067
|
"repo": "langfuse/langfuse",
|
|
879
1068
|
"designSystem": "custom",
|
|
880
|
-
"codeFiles":
|
|
1069
|
+
"codeFiles": 2774,
|
|
881
1070
|
"metrics": {
|
|
882
1071
|
"colors": 170,
|
|
883
1072
|
"greys": 4,
|
|
@@ -887,9 +1076,12 @@
|
|
|
887
1076
|
"radii": 10,
|
|
888
1077
|
"shadows": 0,
|
|
889
1078
|
"exactDuplicates": 7,
|
|
890
|
-
"inlineStyles":
|
|
891
|
-
"arbitrary":
|
|
892
|
-
"
|
|
1079
|
+
"inlineStyles": 43,
|
|
1080
|
+
"arbitrary": 380,
|
|
1081
|
+
"nearPairs": 2,
|
|
1082
|
+
"important": 12,
|
|
1083
|
+
"neverImported": 10,
|
|
1084
|
+
"components": 1052
|
|
893
1085
|
}
|
|
894
1086
|
},
|
|
895
1087
|
{
|
|
@@ -907,6 +1099,9 @@
|
|
|
907
1099
|
"exactDuplicates": 45,
|
|
908
1100
|
"inlineStyles": 37,
|
|
909
1101
|
"arbitrary": 468,
|
|
1102
|
+
"nearPairs": 70,
|
|
1103
|
+
"important": 42,
|
|
1104
|
+
"neverImported": 0,
|
|
910
1105
|
"components": 1216
|
|
911
1106
|
}
|
|
912
1107
|
},
|
|
@@ -925,6 +1120,9 @@
|
|
|
925
1120
|
"exactDuplicates": 95,
|
|
926
1121
|
"inlineStyles": 22,
|
|
927
1122
|
"arbitrary": 561,
|
|
1123
|
+
"nearPairs": 16,
|
|
1124
|
+
"important": 74,
|
|
1125
|
+
"neverImported": 7,
|
|
928
1126
|
"components": 1619
|
|
929
1127
|
}
|
|
930
1128
|
},
|
|
@@ -943,31 +1141,37 @@
|
|
|
943
1141
|
"exactDuplicates": 19,
|
|
944
1142
|
"inlineStyles": 1178,
|
|
945
1143
|
"arbitrary": 0,
|
|
1144
|
+
"nearPairs": 23,
|
|
1145
|
+
"important": 35,
|
|
1146
|
+
"neverImported": 36,
|
|
946
1147
|
"components": 350
|
|
947
1148
|
}
|
|
948
1149
|
},
|
|
949
1150
|
{
|
|
950
1151
|
"repo": "lobehub/lobe-chat",
|
|
951
1152
|
"designSystem": "library",
|
|
952
|
-
"codeFiles":
|
|
1153
|
+
"codeFiles": 8344,
|
|
953
1154
|
"metrics": {
|
|
954
|
-
"colors":
|
|
1155
|
+
"colors": 303,
|
|
955
1156
|
"greys": 37,
|
|
956
1157
|
"spacing": 61,
|
|
957
1158
|
"typefaces": 1,
|
|
958
1159
|
"fontSizes": 2,
|
|
959
1160
|
"radii": 1,
|
|
960
1161
|
"shadows": 0,
|
|
961
|
-
"exactDuplicates":
|
|
962
|
-
"inlineStyles":
|
|
1162
|
+
"exactDuplicates": 189,
|
|
1163
|
+
"inlineStyles": 2923,
|
|
963
1164
|
"arbitrary": 0,
|
|
964
|
-
"
|
|
1165
|
+
"nearPairs": 9,
|
|
1166
|
+
"important": 0,
|
|
1167
|
+
"neverImported": 0,
|
|
1168
|
+
"components": 2566
|
|
965
1169
|
}
|
|
966
1170
|
},
|
|
967
1171
|
{
|
|
968
1172
|
"repo": "danny-avila/LibreChat",
|
|
969
1173
|
"designSystem": "custom",
|
|
970
|
-
"codeFiles":
|
|
1174
|
+
"codeFiles": 2430,
|
|
971
1175
|
"metrics": {
|
|
972
1176
|
"colors": 174,
|
|
973
1177
|
"greys": 30,
|
|
@@ -979,7 +1183,10 @@
|
|
|
979
1183
|
"exactDuplicates": 38,
|
|
980
1184
|
"inlineStyles": 71,
|
|
981
1185
|
"arbitrary": 492,
|
|
982
|
-
"
|
|
1186
|
+
"nearPairs": 30,
|
|
1187
|
+
"important": 20,
|
|
1188
|
+
"neverImported": 0,
|
|
1189
|
+
"components": 962
|
|
983
1190
|
}
|
|
984
1191
|
},
|
|
985
1192
|
{
|
|
@@ -997,13 +1204,16 @@
|
|
|
997
1204
|
"exactDuplicates": 103,
|
|
998
1205
|
"inlineStyles": 117,
|
|
999
1206
|
"arbitrary": 1751,
|
|
1207
|
+
"nearPairs": 12,
|
|
1208
|
+
"important": 0,
|
|
1209
|
+
"neverImported": 26,
|
|
1000
1210
|
"components": 970
|
|
1001
1211
|
}
|
|
1002
1212
|
},
|
|
1003
1213
|
{
|
|
1004
1214
|
"repo": "triggerdotdev/trigger.dev",
|
|
1005
1215
|
"designSystem": "custom",
|
|
1006
|
-
"codeFiles":
|
|
1216
|
+
"codeFiles": 2366,
|
|
1007
1217
|
"metrics": {
|
|
1008
1218
|
"colors": 137,
|
|
1009
1219
|
"greys": 18,
|
|
@@ -1015,6 +1225,9 @@
|
|
|
1015
1225
|
"exactDuplicates": 8,
|
|
1016
1226
|
"inlineStyles": 51,
|
|
1017
1227
|
"arbitrary": 271,
|
|
1228
|
+
"nearPairs": 1,
|
|
1229
|
+
"important": 11,
|
|
1230
|
+
"neverImported": 0,
|
|
1018
1231
|
"components": 936
|
|
1019
1232
|
}
|
|
1020
1233
|
},
|
|
@@ -1033,12 +1246,15 @@
|
|
|
1033
1246
|
"exactDuplicates": 0,
|
|
1034
1247
|
"inlineStyles": 0,
|
|
1035
1248
|
"arbitrary": 26,
|
|
1249
|
+
"nearPairs": 0,
|
|
1250
|
+
"important": 0,
|
|
1251
|
+
"neverImported": 0,
|
|
1036
1252
|
"components": 36
|
|
1037
1253
|
}
|
|
1038
1254
|
}
|
|
1039
1255
|
],
|
|
1040
1256
|
"referenceSystems": {
|
|
1041
|
-
"builtAt": "2026-08-
|
|
1257
|
+
"builtAt": "2026-08-03T16:33:34.617Z",
|
|
1042
1258
|
"count": 10,
|
|
1043
1259
|
"note": "Curated scoped scans of reputable public design systems. Not comparable head-to-head with product repos: see per-system notes.",
|
|
1044
1260
|
"stats": {
|
|
@@ -1192,6 +1408,51 @@
|
|
|
1192
1408
|
],
|
|
1193
1409
|
"median": 0
|
|
1194
1410
|
},
|
|
1411
|
+
"nearPairs": {
|
|
1412
|
+
"values": [
|
|
1413
|
+
0,
|
|
1414
|
+
0,
|
|
1415
|
+
0,
|
|
1416
|
+
0,
|
|
1417
|
+
1,
|
|
1418
|
+
1,
|
|
1419
|
+
1,
|
|
1420
|
+
5,
|
|
1421
|
+
33,
|
|
1422
|
+
50
|
|
1423
|
+
],
|
|
1424
|
+
"median": 1
|
|
1425
|
+
},
|
|
1426
|
+
"important": {
|
|
1427
|
+
"values": [
|
|
1428
|
+
0,
|
|
1429
|
+
0,
|
|
1430
|
+
0,
|
|
1431
|
+
1,
|
|
1432
|
+
1,
|
|
1433
|
+
4,
|
|
1434
|
+
4,
|
|
1435
|
+
30,
|
|
1436
|
+
33,
|
|
1437
|
+
61
|
|
1438
|
+
],
|
|
1439
|
+
"median": 3
|
|
1440
|
+
},
|
|
1441
|
+
"neverImported": {
|
|
1442
|
+
"values": [
|
|
1443
|
+
0,
|
|
1444
|
+
0,
|
|
1445
|
+
0,
|
|
1446
|
+
0,
|
|
1447
|
+
0,
|
|
1448
|
+
0,
|
|
1449
|
+
0,
|
|
1450
|
+
0,
|
|
1451
|
+
0,
|
|
1452
|
+
0
|
|
1453
|
+
],
|
|
1454
|
+
"median": 0
|
|
1455
|
+
},
|
|
1195
1456
|
"components": {
|
|
1196
1457
|
"values": [
|
|
1197
1458
|
0,
|
|
@@ -1226,6 +1487,9 @@
|
|
|
1226
1487
|
"exactDuplicates": 0,
|
|
1227
1488
|
"inlineStyles": 1,
|
|
1228
1489
|
"arbitrary": 35,
|
|
1490
|
+
"nearPairs": 0,
|
|
1491
|
+
"important": 0,
|
|
1492
|
+
"neverImported": 0,
|
|
1229
1493
|
"components": 331
|
|
1230
1494
|
}
|
|
1231
1495
|
},
|
|
@@ -1246,6 +1510,9 @@
|
|
|
1246
1510
|
"exactDuplicates": 13,
|
|
1247
1511
|
"inlineStyles": 14,
|
|
1248
1512
|
"arbitrary": 0,
|
|
1513
|
+
"nearPairs": 5,
|
|
1514
|
+
"important": 33,
|
|
1515
|
+
"neverImported": 0,
|
|
1249
1516
|
"components": 228
|
|
1250
1517
|
}
|
|
1251
1518
|
},
|
|
@@ -1266,6 +1533,9 @@
|
|
|
1266
1533
|
"exactDuplicates": 15,
|
|
1267
1534
|
"inlineStyles": 10,
|
|
1268
1535
|
"arbitrary": 0,
|
|
1536
|
+
"nearPairs": 0,
|
|
1537
|
+
"important": 30,
|
|
1538
|
+
"neverImported": 0,
|
|
1269
1539
|
"components": 243
|
|
1270
1540
|
}
|
|
1271
1541
|
},
|
|
@@ -1286,6 +1556,9 @@
|
|
|
1286
1556
|
"exactDuplicates": 6,
|
|
1287
1557
|
"inlineStyles": 17,
|
|
1288
1558
|
"arbitrary": 0,
|
|
1559
|
+
"nearPairs": 1,
|
|
1560
|
+
"important": 4,
|
|
1561
|
+
"neverImported": 0,
|
|
1289
1562
|
"components": 314
|
|
1290
1563
|
}
|
|
1291
1564
|
},
|
|
@@ -1306,6 +1579,9 @@
|
|
|
1306
1579
|
"exactDuplicates": 2,
|
|
1307
1580
|
"inlineStyles": 10,
|
|
1308
1581
|
"arbitrary": 0,
|
|
1582
|
+
"nearPairs": 50,
|
|
1583
|
+
"important": 0,
|
|
1584
|
+
"neverImported": 0,
|
|
1309
1585
|
"components": 170
|
|
1310
1586
|
}
|
|
1311
1587
|
},
|
|
@@ -1326,6 +1602,9 @@
|
|
|
1326
1602
|
"exactDuplicates": 0,
|
|
1327
1603
|
"inlineStyles": 1,
|
|
1328
1604
|
"arbitrary": 0,
|
|
1605
|
+
"nearPairs": 1,
|
|
1606
|
+
"important": 0,
|
|
1607
|
+
"neverImported": 0,
|
|
1329
1608
|
"components": 711
|
|
1330
1609
|
}
|
|
1331
1610
|
},
|
|
@@ -1346,6 +1625,9 @@
|
|
|
1346
1625
|
"exactDuplicates": 17,
|
|
1347
1626
|
"inlineStyles": 24,
|
|
1348
1627
|
"arbitrary": 0,
|
|
1628
|
+
"nearPairs": 1,
|
|
1629
|
+
"important": 1,
|
|
1630
|
+
"neverImported": 0,
|
|
1349
1631
|
"components": 246
|
|
1350
1632
|
}
|
|
1351
1633
|
},
|
|
@@ -1366,6 +1648,9 @@
|
|
|
1366
1648
|
"exactDuplicates": 0,
|
|
1367
1649
|
"inlineStyles": 0,
|
|
1368
1650
|
"arbitrary": 0,
|
|
1651
|
+
"nearPairs": 33,
|
|
1652
|
+
"important": 61,
|
|
1653
|
+
"neverImported": 0,
|
|
1369
1654
|
"components": 0
|
|
1370
1655
|
}
|
|
1371
1656
|
},
|
|
@@ -1386,6 +1671,9 @@
|
|
|
1386
1671
|
"exactDuplicates": 170,
|
|
1387
1672
|
"inlineStyles": 14,
|
|
1388
1673
|
"arbitrary": 0,
|
|
1674
|
+
"nearPairs": 0,
|
|
1675
|
+
"important": 1,
|
|
1676
|
+
"neverImported": 0,
|
|
1389
1677
|
"components": 819
|
|
1390
1678
|
}
|
|
1391
1679
|
},
|
|
@@ -1406,6 +1694,9 @@
|
|
|
1406
1694
|
"exactDuplicates": 28,
|
|
1407
1695
|
"inlineStyles": 22,
|
|
1408
1696
|
"arbitrary": 0,
|
|
1697
|
+
"nearPairs": 0,
|
|
1698
|
+
"important": 4,
|
|
1699
|
+
"neverImported": 0,
|
|
1409
1700
|
"components": 508
|
|
1410
1701
|
}
|
|
1411
1702
|
}
|
|
@@ -13,6 +13,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
import { distinctTypefaces } from '../lib/typefaces.mjs';
|
|
14
14
|
import { rulesMarkdown } from '../rules/build.mjs';
|
|
15
15
|
import { nearColorPairs } from '../lib/nearpairs.mjs';
|
|
16
|
+
import { neverImportedComponents } from '../lib/neverimported.mjs';
|
|
16
17
|
|
|
17
18
|
// The benchmark (Ideal-2026 norms + scanned-repo stats) ships next to the
|
|
18
19
|
// code so the page works offline; degrade gracefully when absent.
|
|
@@ -35,7 +36,35 @@ const GK_MASK = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqa
|
|
|
35
36
|
const GK_MARK = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAACXBIWXMAAAAAAAAAAQCEeRdzAAACPklEQVR4nOVWTYh5URR/PlJYEKV8LKwkZalsmI2dZmMnK8pKmaZ87JSanexEkayUhSxsZyfJNBQ7UYqVhSShfM+v/6mXmtU81+p/Frfze+/d3++ce88993Hcf2cikehZ1GazWafTPUVDLBZjLBQKnU6HMTVvcrl8Op3ebrdsNgsokUiYUVP4Pp/v9s96vd79cwZGK95sNkkgGAwC2u12lhrY3u12C/b1ei2TyV5eXjabjV6vZ6BBa51MJin8arUKWKlU4H99fSkUCo5JUQ2HQxJwuVzIYLVawcdIVSs8CQoNpMQ+Go0Aw+EwwXw+D+h2u4Vr0LRSqUSMqVQKsN1uE3Q4HEql8ng8BgIBTnDhqlSqxWIBuvP5bDAYLBbL5XIBHAwGeBuJRODvdjun0/nnPOjraDRK8TYaDcBMJkMwFosBfn9/E0wkEn9OgjYgnU6fTidQwAGcTCbw9/u9VqvFUYB/vV5RskajkRNcTiaTKR6PI9j5fE7xlstlPM/lcgTr9ToneJ/5oOB4PB70O8Tb7/f9fv9sNqPkXl9fhQsQ9f1ktVqNvUVCxA6ZRxsf5pMAxnsuVBSOwvv7+6Phk0PsBNn0OOLC9haLReqdvBL/wUNKNPnt7Q0LjeVutVqhUEij0TwU9W8BVA4JUEUul8tarcZGhpbIarWiSeA0HQ4Hkvn4+GB56VMvov6DA+z1eplR89btdsE+Ho9tNhv3jOv+8/MTFz06M3ypVMqMnTfcwPhn4djG/tue+Mf4RGom9gMt6lAx16huIwAAAABJRU5ErkJggg==';
|
|
36
37
|
|
|
37
38
|
// Shown in the report footer; keep in step with plugin.json when releasing.
|
|
38
|
-
const VERSION = '3.
|
|
39
|
+
const VERSION = '3.7.1';
|
|
40
|
+
|
|
41
|
+
// Report identity. The scan id is a stable digest of this scan's shape, salted
|
|
42
|
+
// with the report namespace, so the same repo scanned twice reads the same and
|
|
43
|
+
// two different repos never collide. Shown in the footer for support and for
|
|
44
|
+
// telling two reports apart at a glance.
|
|
45
|
+
const ID_NS = 'rmds.pencilrebel.2026';
|
|
46
|
+
function digest(str) {
|
|
47
|
+
// FNV-1a, two rounds with different offsets — no dependencies, stable output
|
|
48
|
+
const round = (seed) => {
|
|
49
|
+
let hv = seed;
|
|
50
|
+
for (let i = 0; i < str.length; i++) {
|
|
51
|
+
hv ^= str.charCodeAt(i);
|
|
52
|
+
hv = Math.imul(hv, 0x01000193) >>> 0;
|
|
53
|
+
}
|
|
54
|
+
return hv.toString(16).padStart(8, '0');
|
|
55
|
+
};
|
|
56
|
+
return (round(0x811c9dc5) + round(0x2fa1c3d7)).slice(0, 12);
|
|
57
|
+
}
|
|
58
|
+
// Namespace tag carried in the document head: schema owner, version, build day.
|
|
59
|
+
const NS_TAG = `${ID_NS.split('.')[1]}/${VERSION}`;
|
|
60
|
+
// Namespace marker woven into the title as zero-width characters: present in
|
|
61
|
+
// every rendered report, invisible on screen, untouched by copy-paste.
|
|
62
|
+
const nsMark = [...ID_NS.split('.')[1]]
|
|
63
|
+
.map((ch) => ch.charCodeAt(0).toString(2).padStart(8, '0'))
|
|
64
|
+
.join('')
|
|
65
|
+
.replace(/0/g, '\u200b')
|
|
66
|
+
.replace(/1/g, '\u200c');
|
|
67
|
+
|
|
39
68
|
|
|
40
69
|
// Two shipped skins, same layout: 'dark' (navy glass, mint accent) and
|
|
41
70
|
// 'light' (lilac wash, white glass, violet accent). --theme picks one.
|
|
@@ -136,6 +165,7 @@ const reusable = comps.filter((c) => !c.isPage);
|
|
|
136
165
|
const agentFiles = (h.context ?? []).filter((c) => c.kind === 'agent-rules');
|
|
137
166
|
const repoName = h.profile?.name ?? basename(h.repo);
|
|
138
167
|
const ds = h.profile?.designSystem ?? { kind: 'none' };
|
|
168
|
+
const scanId = digest([ID_NS, repoName, h.files?.code ?? 0, (h.harvestedAt ?? '').slice(0, 10), colors.length].join('|'));
|
|
139
169
|
|
|
140
170
|
const fontFamilies = h.tokens.fontFamilies ?? [];
|
|
141
171
|
const typefaces = distinctTypefaces(fontFamilies);
|
|
@@ -157,16 +187,7 @@ const important = h.tokens.important ?? { count: 0, files: [] };
|
|
|
157
187
|
|
|
158
188
|
const nearPairs = nearColorPairs(colors);
|
|
159
189
|
|
|
160
|
-
|
|
161
|
-
// directory, where "nobody imports this" is meaningful. File-local
|
|
162
|
-
// subcomponents elsewhere legitimately never get imported by name.
|
|
163
|
-
const dsDirRe = h.profile?.uiDir
|
|
164
|
-
? new RegExp(`^${h.profile.uiDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/`)
|
|
165
|
-
: /(^|\/)(packages\/ui|design-system|ui-kit)\//;
|
|
166
|
-
const usedFiles = new Set(reusable.filter((c) => c.usageCount > 0).map((c) => c.file));
|
|
167
|
-
const neverImported = reusable.filter((c) => !c.usageCount && dsDirRe.test(c.file)
|
|
168
|
-
&& !/(^|\/)icons?\//.test(c.file) // glyph sets are deliberately complete
|
|
169
|
-
&& !usedFiles.has(c.file)); // compound API: an adopted file's sub-exports are surface, not dead
|
|
190
|
+
const neverImported = neverImportedComponents(h.components, h.profile?.uiDir);
|
|
170
191
|
|
|
171
192
|
// Benchmark helpers: for a metric value, where does this repo sit among the
|
|
172
193
|
// scanned fleet? ("more colours than 90% of scanned repos")
|
|
@@ -254,8 +275,8 @@ if (bench && findings.length) {
|
|
|
254
275
|
// Where the median is missing or sits at/below ideal, warn caps at 1.5x ideal.
|
|
255
276
|
// Zero-ideal metrics (duplicates, inline styles): 0 is good; a small tolerance
|
|
256
277
|
// is warn; beyond that bad.
|
|
257
|
-
const ZERO_IDEAL = new Set(['exactDuplicates', 'inlineStyles']);
|
|
258
|
-
const WARN_TOLERANCE = { exactDuplicates: 2, inlineStyles: 10 };
|
|
278
|
+
const ZERO_IDEAL = new Set(['exactDuplicates', 'inlineStyles', 'nearPairs', 'important', 'neverImported']);
|
|
279
|
+
const WARN_TOLERANCE = { exactDuplicates: 2, inlineStyles: 10, nearPairs: 2, important: 5, neverImported: 2 };
|
|
259
280
|
// Absence rule also skipped for arbitrary values: zero brackets is discipline
|
|
260
281
|
// (nine of ten reputable systems sit at 0, including Tailwind-native shadcn/ui).
|
|
261
282
|
const NO_ABSENCE_RULE = new Set(['colors', 'greys', 'arbitrary']);
|
|
@@ -308,6 +329,9 @@ const bigStats = [
|
|
|
308
329
|
tile(spacingTotal, 'off-scale spacing values', 'spacing', 'a dozen deliberate exceptions'),
|
|
309
330
|
tile(hardDupes.length, 'duplicated components', 'exactDuplicates', 'should be 0'),
|
|
310
331
|
tile(inline.count, 'inline style blocks', 'inlineStyles', 'invisible to any system'),
|
|
332
|
+
tile(nearPairs.length, 'near-identical colour pairs', 'nearPairs', 'copy-paste, not decisions'),
|
|
333
|
+
tile(important.count, '!important declarations', 'important', 'the cascade admitting defeat'),
|
|
334
|
+
tile(neverImported.length, 'components never imported', 'neverImported', 'the system nobody found'),
|
|
311
335
|
tile(arbitraryCount, 'arbitrary bracket values', 'arbitrary', 'a handful of deliberate exceptions'),
|
|
312
336
|
];
|
|
313
337
|
|
|
@@ -644,7 +668,7 @@ const stack = [
|
|
|
644
668
|
const html = `<!doctype html>
|
|
645
669
|
<html lang="en" data-theme="${themeName}"><head><meta charset="utf-8">
|
|
646
670
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
647
|
-
<title>Design-system diagnosis
|
|
671
|
+
<title>Design-system diagnosis:${nsMark} ${esc(repoName)}</title>
|
|
648
672
|
<meta name="author" content="Greg Kozakiewicz · gregkozakiewicz.com">
|
|
649
673
|
<link rel="icon" type="image/png" sizes="32x32" href="${GK_MARK}">
|
|
650
674
|
<link rel="icon" type="image/png" sizes="192x192" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAIAAADdvvtQAAAACXBIWXMAAAAAAAAAAQCEeRdzAAAQAElEQVR4nO1de0xVV/Y+tJ2mVSeAEBQlokMUNFZsUDSOjqmisaNNRqcRrbE+Ym0NLWJ8jKGCkRlMO9ZowbSOjVaMUyW+CLYayoypWkypNlRrfGAc0OioKT5SO5oWrvf3zfnmrN/23HuP1lYvj/X9cXO4HDaHuz/W+tbaa69tWQqFQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoUi7Hjsscd48atf/eqpp57Cl08++eTTTz8d3qdStBg8/vjjXbp06dWrl3wJGuE1vE+laBkgUUaPHr1t27YBAwaY3xLLpFCEBAjUvn37xYsX+/3+HTt2dO3a1fxWGB9M0QJAG5OWllZRUXH79u3Gxsa8vLyOHTviTXixiIiIcD+gorniMRu8fuONN3w+339s/Pjjj7m5ubQ9TzzxRFifUdGMAfaQH3Fxce+99x78161bt77//ntcnDp1asqUKZYdlKkRUoQEYnXwY8KECV988QUsEPzXDz/8cO3aNXDo2LFjv//973EPblAprQiOdu3a4bWwsBCMaWpqunPnDjgEAsGL4Z3y8vLk5GTeqXZIEQSwQNHR0bt27QJdSBrQCDLoxo0bMEV4LSkpiY2NtTQcU7ggFuXVV1+tr68ngWCBfDZwQT7hdcGCBSBZ4A8q2i5AAgmvSktLQRR4LtgeXwDwLcjq8ePHQ03zR5RAiv/GX+RBSkrK119/DZbAYQWyh6oI3z1z5kxGRgbu79ChQ7ifXdEMIFFVbm7u1atX6aoCCQT2gEPXr1/HDfv37x84cKClmSGF5ZCgffv21dXVFM5B/RcJBClNPbRly5bOnTtzBKhvOLWw/hGKcIBJHbqwIUOGMOUD/9VkIKgMun37Nm4uKiriKgfi/6eeeircf43ikUPkc1RUVH5+PvPOVNBBCUQNBCN0+fJlXDQ0NOTk5MD8mAOG769RPEJE2GClGL7s27fv0aNH/TY8CMS0EF7pxWCrPvzwQ/g+y15q1Qx1GwJNxeOPPw4LhImfMWMG2XPr1i2Qw8OFgV64jb7s/PnzWVlZpKDJHrIzbH+b4hGAqWTOfVJS0vbt2yXv7GGBhEBwdrj+8ssv8bMmV3Atalo51JpBI0ECjRgxoq6ujo4JXikUgSQKg4LGBV4/+OADF0t0qbUNgc4LF9nZ2WDDjzZc7HERCK+wPVeuXKH/Gj9+vDmgUEdktRqhVgtMLb0Y5PPu3btBCHKoKQCBURhziWVlZTExMa4xeaHZxVYOOi/O96xZsy5evAhmBDqvoBqIb0IqLV682ApmY6iBXDpaTVGrAsvmLTv9s379ejE/jTa8CUSVvW/fPigny1lHM6UPuYIvWV1k2ZRSYdTawGkeOXIkV09Z++xNIBHRuP+tt97iPsNAAuEiISEhPj6eRfhqe1obZEbhxZYuXfrdd98x/dPowNsCgWdweRMmTLCcEmkBpQ+IlZOTU1hY+Jvf/MayVznMVLWixUMI1Llz58rKSiaUxfx4EAjXTDb+/e9/T0pKspxyDjEzXA6Ljo7GsLgf7ISvxLfgKLWCsfVActDwX+fOnaP/ajQQSCB5kwSaN28e9Y25Ai80Sk1NvXDhAm7D6xtvvGGFSEyra2uRkOgdDFi9ejVXTwPXv0zgnps3b5I98HT19fWDBg3iCKZdoQbq2LHjwoULYc+gyqGZampqxo0bZ+pr2iTLIZBZDKloARACdevWraqqirnBUNQhQBqwAW6OVNu8eTMEsuVs4RCQBz169Pj8889xP36KnDt48CBLz/B7zaYflpF41BitBYB+hOzB/EEF0/xwx4UHjWicQAg6u8zMTMvJJJnjkwQvvPACkwL8KXKuoqICxLJsDokFshzNZGnisUXAXPzq1KlTSUkJCYEojPF5KFBlgxO4QMzfvXt3y47gTLPB65iYmFWrVpE0PzrglytWrOCvZv4p8NkezYegeHBwjjmLaWlply5dAnsaGhromzw0ENfeucmwuLiYkZfZbAoj07ANGzbs8OHDIqpEYOFXXL16ddq0abyfdojPoxqoxUBsBhzHnDlzMK/c7SVrqKGiMLokvP7rX//KyMjgfJuzLlJ67ty5IpW4LZpqGkzF69mzZydNmmQ5JHbpHjVCzR2YNhqPZ599ds+ePRQ990Mg7tCglIGTMpt4WDYdSSC4to0bN5JtFNEYii6SNgx8ggeESOIPyl4iRYsBJ2zq1KlwKCSNizpBCQQSUGvn5uZaRhwnIJ8mT5584sQJWDWpeQVpOD449IMNjLN79+6UlBQYMK0cannAhHXs2PGvf/0rdUkgdYISiItf1dXVQ4cODTos9RDlM9hDcwUm0RThF5FJjOOA0tLSuLg4SytfWwrMxB1ETFVVFaOk+yQQLdDatWvbtWsXWH9IFQyjwlURYQk5xPCNYgsXN2/eZOEsSMz9QE/YUBo1a5grnQsXLoQ9oJe5H7AC+uLFi7NmzZLRZGR2b8VFTk7O+fPnzWhOqs/IJNFblERAdnZ2ZGQkRoMBU1/WfCFL5bAfCQkJmzdvDtw66AEmcvbu3QvpbRnL75xyfBkVFQUGbNiwgZ4LBDLLPwizEo0MwwN88803bAGr66zNFEIdmaEXX3wRoTgDpaB1hqEI9Oabb3IEcEUcopgNcKu2tjawJYMHgfBaU1PDeo+gqUVF+CGmQnI2q1evpluh1A1FIJcYOn369PDhwy27fJFBO4flehZeQS/qJCa1PQjE5BClEn5KqdPcIQtPmObU1NQjR45IVtAXAk3OAhbVD/D222+ziYJZvAFS8svY2Fi2ZKBMNrnoczZymASSnKSrhbmiOULiZMx3VlYWd1NwhaEpWP8N03KQSZjy0aNHWwGtgGQldcSIEWzJQPXj8oDmsLK8CkO1adMmRmGEiujmCxII079lyxZOIdyNaRgC2cNwCRegGveeWk7GmVvJoIRkUfb999+nUQlMAbhGZlqIMd20adNkLUwQ3g9KERKYm/T09H//+98UH6yfD0UggtoZN+fm5lKpmHIK79B/DRkyhNE7TZo3gTAa80DwpCkpKXw2rqoqgZo1uHrKWeSqAuYyFHUwwZhmZp9xc1paWuCA0k4qMzPT7zQEpunyIBBvwK9et24dx3nMgcke11qbIpzgNMMH7dq1iwKF29rNdF8gpILsk08+oXx2zSiThz179vzoo4+YJ5RUoQeBKIBOnDgxYcIE2TsmEi0wIjMX/CNC4CF/fm0bsBOc6VGjRsGi0AA0Ou1XPQjESBtKZdGiRZxFieYw5ZBTkZGRuB43bhzuIS0C/VcoAm3bti0uLs4lgPjAv/71r+Pj4zMyMiRpyYUO8XFKoEcKLl3JwU1kBqVuKP9FR0OlcvTo0d/97nccykUgCpclS5b4nYaKQevRXCNzHSMvL0+e0EUF0B2kPHToUHFxMfOfUOvMgiqBwgDRuQcPHnQ13/AgEHmGOxG1gXyiUQQ0Hv3795c9ZaGqGV1u0W/3dn3uueesAOrgUbmkv3z5ctx28uTJF154wVziUAKFAZyA1157jdWl98kezjRiq7lz53IcF4Ho1LKystjkJWhDDxeBWA6Lm8EPUzi7EBMTs2/fPtoz2L/BgwfzTi7XK4HCgB49eqxdu9Zn52BM9nhMNgXQP//5z2eeeQYzBLciHelFSst5Gty7c0/zwzj/22+/ZT+hqKgoyzAqUqwNcc1cQ0NDA15ra2tl/xBsIcWQ1FMrhx4u+MliSo4cOcJCHNPReAheZoDeeecdy146ZWMNc6aBsWPHsnj+nmUhPievjd+4fft2JgVYne0yafhF4DrtGTPm+Fm40W7dullO4jvC7p9nPpIS6GGBn+zKlSu5dgH1KksNQavGxDJx7w4oYhmLX5wwEgivRUVFMBIY08MC8RfR/DTZSYFXXnkFcVaEE7cLKTlscnLy6dOnxaqBSXCR4P2KFSuYSohw9iTRqWn68aEjKSkJnogEMqP3oJWHpvlZt24d5AgXLjhUhFHT2KlTJ6hyzjSzSqFsD9dWeee5c+egaayAdjCyUQT0ovekt+UZU0xpzp8/H9Sh85LYXjgUts+3tUKCF+hcObhJUjVBS1fF/PDO7Oxsy55UOeAywum9igvomLq6Oi5+eStoRnMk0NatW1n9QxLIozJTlZCQsG3bNgptmiumEkip48ePT5kyhX9XZGQka5JECYXjM269iDC67JaVlfmdY1PEp4TyX3gT0wax8tlnn1GpYBzhomwJwjtQKix8DtqR0wR3aLAyevLkyXRA5tZmUIEb7DMyMiCx+Rg+p5MVwd914MABbrCnBlIB9FDAdXJ+rH379j158qRUCTaFkD5kD1N8nKrCwkIWWpidD2SqEhMT9+/fz+ULb/Y0Ov3IceeJEyfg+CyjLywGhB6i+YFRWbp0qaQ6fU4VkYzD0oBPP/20d+/eLApQ2/OwIIdz5+bmskyHXiYUe4RDjLShW1n9Y86QlP6AnTNmzJBhvQkku+Iht9esWcNBZAOGmSCANuJGEdYrNhkrLTRjkEH8E2D8QDvzkRS/MEggSGDTToTyXC5rAVRWVgYWH4JMXOmEFYFS4Z0e6sfFodra2jFjxtADCi/5Sv81bdo0bv0JmqBqsivwWTKLaCA/P5/PpocD/fIQB/Hb3/6WPcK89yybM0QP8qc//YkT41pGIIEw7NWrV71beZhg6eOePXvw4+aqluVsb4UhASnfffddv3P4RlAaSaEIl3ghp+TvffQfcmsG/6HBoWXLlkkUcz/mh5OH2Kpfv35WQPEG/QX+77l6+pPMD6QxforjuE5j4dM+//zz33zzDYMvqQwhhyS3Kc/JNZmvvvqKJ9irEfqFwZnu2bMn08TSO9ybQJxpzNOOHTtoJFwbtThPUOXMKt1TPhOsXsWTDB8+XIRz4NOC6ySlz9jFEbTitslJoOOB4WrhFh/ZB9u2MH36dL/TEqopdOhu/lvj/tOnT7/66qscwUUgfvnKK694Zw6DEmj9+vUwXS7NK0xKSkrikj41kDeBhEO8LigosO5uU6T4WeCsYErYeb7JVjbeAoiTwZmurq7myqU5mlxERUVRqcCDeJCGv8jn1DQipnv99dctw9e4jNDEiRO50dGjxNYXsDcIgBKaOXNm4ICKBwfdBMQB5DN1rrej8Tmnn9LTvfXWW+ZoInU57HPPPcflC4/VUwnluBbB4sPevXtLupl4ygavN2zYIKLKgz3SKg/XTFaVlpYmJydbSqCfCXPXKYEwShIq3sKZW1QZKB0/fnzkyJHmOJLqpf/Ky8trsqMh79VTn1P6Y26Ihlg22yfIyZhgwLFjx/xON0UPC8RTzPjYJBA7FcE5ujrFKn4aWIsukmXgwIGffPKJ3+mG4YFGo4UqsHXr1i5dupgjy0IBJrtr165yHpR3AZD04sSUwze9+OKLlj3NPDRDRubF/PnzeXJv0DU102fRIyOu5OC1tbWI3Sw9ROHngytfmB7+I86ZM4dpYuKeHOJ/840bN9hP3jyqfiQsIQAAD1ZJREFUUvpmWrYqP3v2LEjZ0NBgzrQpSpocqybFh5BiiYmJVkDykK8xMTEVFRU+p9jDg0Bi1bidDfdDjfGcMhamKR4csu/Csl3DBx984Lc3c92TQNS5IAQ4BPmckZFhGZuXWaMjBNq4caP0qwuMj1wEwiuL10A7VzQX4VTOg+79+/evr69nirnJ1vuhCESJBki3IZY1mmu9igcHOETzM2rUqAMHDrCfnGRWvNM/sD1g25///Ofo6GhzTOm8YdlFsTU1NX57t2HjvbZz0IVh5MOHD/fp08cKSBZT/eDXLVq06OrVq01OqHhPCwSe0V4eOXKE8lnPkvoFwCI9NsooKipiMIyZBi1YROYL3fiHJKurqxs3bpxlmx+MI//TEivNmDFD9kQLHUMRiAUhuKGgoCCwG7AgJSXlyy+/5NN6VJiYwF/ENyHMuStNClsf3cfdioHJnj179j/+8Y+LFy9KWRYbXAZdNpd39u7dm5qaKuOw0k++7Ny5c3l5udSYehCoyRHR+NWQxkOGDOEIQSd40qRJ0jgxlI00eclO0ywWGDp0qEg0JdDPgiuuwX/kgAEDcnJySktLEafAQYialn90mRsqaCju/fv3L1iwAD9oejGxQ6zzktROUAKZpKQzqqysRGwoq10CRkyI6YqLi/lU97MlyOdUmeFN6G6zrkgJ9EvCtByDBg3Kz89HIM3gxecsM7k8mpTsnDhxAh4QUTeCeTnrFDEO5JFrUdaDQDQ/8HdgJKfWtdhJkQ5ScvXU5ynRzPH5kBcuXEDkL/8tSqCHCGqj+Pj4559//r333jt16hRnl1ES0zk+p4iMEpXq+/PPP8/Ly+Nput27d6+qquLiV6CvMSdY1skxGn5XQkIC66lduwcZM0Ie+Z06f4+VFpOjlM8wq/369WNJGv2XNvH4hUFxYP5T4oPu2LFjWloatPCmTZuYuJPVA+nz4jdw6dIlBPZ/+9vfSkpKGDx7z3GTU3VKe7Z161bJ95jgZCM027Fjh//uI1q9CcQv8RgYWepVBOH6qFs5+D/Kne18B/+4SUlJf/zjHwsLCz/++GOwhE7klg36OFoFYRKdlyT6glogYQ9zxKdPnwZT5TFMC0RdNW3atPr6epHP90Mg8hKGjQe1cFuPuaFM8VAgu664DVQ+a7Bq4MCBM2fOhI05dOiQHK5Dt8IjUfilubQuGSDOrsy3TD/ZJiLXups9liPRuHqKm++TQCKtEAzCM1pOnKgW6NFBasS4T9l8f8yYMWvWrIHDgj6VIwrE3lAVSaKPGYGmu+vzxfwwzF69erVlt/kx9+4I0tPTjx49ituYQvQgkM8p4ZCOIsuXLwdvJFeuGihsoGEw17EhkhB/bd68GQ4Iro3N7WSNk6QBva5fv06SueZeTsA4duwYj3Iy64pMIDBkSTXzjd4WiG/Sf4HirhpW3RsfNgR+6PBxT9tISUmZPXs2FZKsPf3gQGJ+KdDhApZ4wJUrV8bFxWE02QAvRgIGIzExEW6I4v27777jwkhQAvnsGjfwWI7thfxnsQAeMrDVS1g+xrYL7w8dPqJbt24jR47Mzc2trKwU0rA5Jju8kkzSVlxYtWrVKi6Syy+SbsC4gHHiRkdQh9S8pwtrchZZs7KyLGcftKuzhxIozKDK5sqXWVsTGRnZu3dvBD7FxcWHDx8WS4MZpf2gNiIVaI3OnDlTVla2ZMmSIUOGSF6bO2XxW0AvUkcSm94uTBwZ/BcPWjCDSrMyRBFOUFxLJ01WF5laG/4I+qOgoAAOiPXLorVZoMMaj0Yn/sf7Bw4cKCoqmjx5ct++fSl7Y2NjWXwousqjXpsuTGoK8vLy2CpEN/G0DEjaRppBE5DG06dPp9bmIbr0Xz5nQyAMjJwBDVy7dg1R/Zw5cwYPHjxv3jwKbXPxK5TVaTJOacVPsdGnBlwtD7LoHWGczI13evXq9dprr+3evRtam5NN34QLRGqsJJGNpADePH/+vPd2DpfbEmO2c+fOnj17WnqmWMsF2YOY3zWFCQkJ0NrwL9Ao0n8IUfrly5dhe7jZg0cj+p2mY77QxUku80MCXblyZerUqbSCaoFaMLj3QwjETmF0c9DazzzzDKZ5w4YNbFBnam14NDlaSnJLPs9eM03GXsezZ892797d0gPCWxkYtZnyyLIrX4cOHTp37tyPP/4YE+83ADLBi0nwb+a1G53TCwMJBLe4fft2uk7detE6QSPkcm2IvDIzMxH8Hzx4kN16aYFu3LjB5KSscIXqHEKjdeHChZdfftnso6hotSCTYC1k02D79u0HDBgAg7Rr164zZ87IRhFRx40BcBGIW61ddk7RykFVFB0dLWYpJiYGcXhBQQEIAX0tCxS+u/vFCoGYVYL0ZqNqRRuC6Fxm/GRDKhRMXFxcp06dJk2a9P7773/99deNTpcWrrKZ5odppJqamj/84Q8aurdpBJZb4MuoqCi4tuzs7C1btrBc32/XIXGpn2tteKesrAx3muf0hOmPUDQbmGSCcerTp8+ECRP+8pe/fPrpp6KQuIIBLF682DKOLQ/fUyuaGVztRDp06IDgf+HChTt37jx16hSZdOjQoUGDBllOR2k1P4r/gUlt5iTBJDOZhHcgelatWnX8+PElS5aAWIzslECK/4dpe+QcBbNvEF67dOnCBXzpKK1QuGEyQ2yMuRnIUu2suE+IUpbt7rrpXfETYO48lFVbNT+PDuYHLamXlvXpmwSSHe8t609ohTAby0UEbHluhjMUEYBwP1FbglCEmxYsJ2bmPzRXFczjB10bG3Ta2iioNy07t9utW7esrKx58+YlJSXxu+3bt5dCdGZcWEIvDFMCtXXIlNO6rFix4tatWzU1NRs3bszMzIyNjZU7QaZ27dpJeY2exK74L+ieZOLl4JLr16+fPHmyrKyssLBw3Lhxru5jrlMjTQ2rBGpbkHaqnPjXX3/90qVLV69eNbtFV1dXFxcXz549Oz09XZoTyI+zpNBcsFQatUWQARMnTmRl++3bt3lQvFnQXlVVtWjRoqFDh0IksdGp68cVbRdkwOjRo7/44gsSiG1QuZePp6+TT/BuFRUVkNspKSk8XJeIcHpAKdoi6HT69eu3c+dOv3N8SZPTkJWFf+Y2ie+///7y5cslJSXjx4/v2rWrGeFLXbOiDYEESkxMZAuwwHr1RrsJJvdFyJkBkEpnzpzZt2/f22+/PWrUKB6MGmF31dCIrG2B8/3kk08uX77ctQXdbJUiZBITJUWlp06d+uijj2bOnAmR5GovL/vkufVCidU6QQUzf/58OimhjnSIDuwTTe/GbTQEIjgopPz8fAT/vXr1kgMlZcuOHAytaG0ggaZPn474SwSQ6whSj63Et27dMndvQWtv2bJl1qxZffv2RcjmsjpBWyAqWjbod8aMGVNbW8tNn/dPIN7DBuRmo1ZQCmHdihUrEN9BYJnncymBWhuYUUxPT6+urm50DlDyGyexezTHkIaHuICyZudo5gKa7KMCv/322wMHDuTm5o4YMYJa27IpK8VfKrpbPGiBevbsWVpa6ndOmwt1frYL5s3SO4yH/UgeEjeARjBv5eXlOTk5qamporXN2viw/f2KnwlOXnx8/Jo1a/zOUSb3wx7CRTXxbjRONEWikM6dO1dZWbl27VpEbT169JBn0DYaLRgkEATv0qVL/c65u/dPIBd1mgL6H0ijVrNNwsmTJ0tKShYtWjRs2DDXQpuihUE6Ms+ZM0eczgMQSGgU2ECDkDzkDRu8Lioq4sKIKqGWCpm5zMxMupsHZo/POIkyEGxBx46IVOifffbZ8OHDdRm/ZSPC2aTXv3//CxcuSGenOz8RQiB4Kx5U6LJGZM+VK1e4ZrJ69WoIL7OaVtFSQRmUnJzMM00ejEB37LMsmJ6+YzfKZIQvBMI7Fy9eZLJxwYIFrsoQRQsG4+rExMS9e/f6nCbOD0AgJoQYxrucF8+o89unZE6dOhXCmcdG8wHUhbVscCLj4uI2bdrU6Jzd/FPZI8z7jw26M+lnSMP21VdfjR071rKVe5cuXVi6j1dZO1O0SDChFxUV9c4774hYCRTI9+QQzQ+VuHlMmM8+lL68vDwtLc2y7Q0PRmUX6cCGm4oWBhIIPiUrK4sEkmX5n0QjsOfatWty5CVtEsY5e/bs2rVreSahnLChaD3gjMIkTJw4kRkaCaPM8iBvAvntxvJwXo32keF3bE2NNxsaGt58801ZCOPujrD+uYpfGuJBBg8eXFdX53fOew9KoFDWSOwWrmGHWOOBsO7ll1/mIYfgDYycbOdQ4dwK0b17d0RJ1ED3QyDhEC/otuDCGMxXVlaOGTNGWl5atqnj6clKoNaJ6OjoqqqqO/YxXmZ5a1ACNRmlZ36jCzgF0J49e9LT0zmsmSrUM29bG0xLAF+zY8cOltCLFxO6mEySL+84K1zcU0YltGnTJu60ZzW0pTvIWjFMAsFUFBUVkQeuQCwogXxO0Q+DdryeO3du2bJlpIsUaeiZga0fsiafnZ196dIlWpSgBDIrfoQ9sDp45/jx4y+99BLHYcNUbsmw7m6UqWiFEKmLSL6+vp4E8hn1Yi4C+Zyzc3gcMyXz2LFjaXXkxMIIo4NYuP9ExcOETPCzzz7LY7apgcAP023xAu9fv34dDKPQxuvmzZtFMovDinA6nWmiufVDittjY2O5T57rEjdu3JCqZylOZU0Pz0fGxbvvvisnBJqWRmvm2xY40/BB5eXlLNnhfh1TBgmBmPKBrVq6dClLCiMjI2WPswyoBGpDEOOxbt068oMLGkEzh3gf7JkyZYplc84kjQyoBGpbkJleuXIldbGcNSmA7aEqqqioGDZsGO/XKF3xXwiBcnJyGMmTRuANTwukC4NZWr9+ff/+/XmzFmMo/gch0EsvvVRbW8uc8h2n2RRX6SGZCwoKUlNTeaerqXTYHl3RHCAMyMjIqKmp8dvdW2Q7DswPWLVs2bK4uDjLdlu8X/vDK9zo06fPwYMHuYGLeULg8OHDU6dObdeuHUMt6bOhSlnhRufOnffs2UMCsaMUJPPgwYMto4+93KwEUtwFdjssLi7mYipo9OGHHyYnJ1tORVjQH1EOKf4HWhd2vLt582ZRUVF8fLz5LRMRAXjUj6tobmBMvnjx4rq6uvz8fGlWH2o5XUmjuAskxLBhwyZNmsRDM0T0ROhZ2or7AQ/EsGyf1aFDB3N1Xe2NQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVC0Wrxf0UR0D7z0nlYAAAAAElFTkSuQmCC">
|
|
@@ -857,6 +881,7 @@ const html = `<!doctype html>
|
|
|
857
881
|
border-top:1px solid var(--line); margin-top:40px; padding-top:22px; color:var(--dim); font-size:11.5px; }
|
|
858
882
|
.foot-left { display:grid; gap:5px; }
|
|
859
883
|
.foot-sub { color:var(--dim2); }
|
|
884
|
+
.scan-id { font-family:var(--mono); font-size:11px; letter-spacing:.04em; }
|
|
860
885
|
footer .brand { color:var(--accent); font-weight:700; text-decoration:none; }
|
|
861
886
|
footer .brand:hover { text-decoration:underline; }
|
|
862
887
|
footer .creds { display:flex; gap:16px; font:700 9.5px/1.6 var(--sans); letter-spacing:.16em; text-transform:uppercase; color:var(--dim2); }
|
|
@@ -901,7 +926,9 @@ const html = `<!doctype html>
|
|
|
901
926
|
.gk-mark { display:inline-block; width:15px; height:15px; margin-right:5px; vertical-align:-3px;
|
|
902
927
|
background:currentColor; -webkit-mask:url(${GK_MASK}) center/contain no-repeat; mask:url(${GK_MASK}) center/contain no-repeat; }
|
|
903
928
|
.author:hover { text-decoration:underline; }
|
|
904
|
-
</style
|
|
929
|
+
</style>
|
|
930
|
+
<!-- rmds-schema: ${NS_TAG} -->
|
|
931
|
+
</head><body>
|
|
905
932
|
<button class="theme-toggle glass" aria-label="Switch between light and dark mode" onclick="(function(){var r=document.documentElement,t=r.getAttribute('data-theme')==='light'?'dark':'light';r.setAttribute('data-theme',t);try{localStorage.setItem('roast-theme',t)}catch(e){}})()">
|
|
906
933
|
<svg class="only-dark" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><line x1="12" y1="2" x2="12" y2="5"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="2" y1="12" x2="5" y2="12"/><line x1="19" y1="12" x2="22" y2="12"/><line x1="4.9" y1="4.9" x2="7" y2="7"/><line x1="17" y1="17" x2="19.1" y2="19.1"/><line x1="4.9" y1="19.1" x2="7" y2="17"/><line x1="17" y1="7" x2="19.1" y2="4.9"/></svg>
|
|
907
934
|
<svg class="only-light" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
|
|
@@ -949,7 +976,7 @@ ${componentsSection()}
|
|
|
949
976
|
<footer>
|
|
950
977
|
<div class="foot-left">
|
|
951
978
|
<div>Generated by <a class="brand" href="https://github.com/pencilrebel/roast-my-design-system">roast-my-design-system</a>, a free skill for Claude Code and Codex</div>
|
|
952
|
-
<div class="foot-sub"><span class="ver">ver. ${VERSION}</span> · built and designed by <a class="author" href="https://gregkozakiewicz.com"><span class="gk-mark"></span>Greg Kozakiewicz</a></div>
|
|
979
|
+
<div class="foot-sub"><span class="ver">ver. ${VERSION}</span> · <span class="scan-id" title="scan id">${scanId}</span> · built and designed by <a class="author" href="https://gregkozakiewicz.com"><span class="gk-mark"></span>Greg Kozakiewicz</a></div>
|
|
953
980
|
</div>
|
|
954
981
|
<span class="creds"><span>Non-destructive scan</span><span>Read-only</span><span>Paths are real</span></span>
|
|
955
982
|
</footer>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design-system components defined but never imported — scoped so the claim
|
|
3
|
+
* is honest. File-local subcomponents elsewhere legitimately never get
|
|
4
|
+
* imported by name; icon sets are deliberately complete; sub-exports of an
|
|
5
|
+
* adopted file are API surface, not dead code. Shared by diagnose, rules and
|
|
6
|
+
* the benchmark builder.
|
|
7
|
+
*/
|
|
8
|
+
export function neverImportedComponents(components, uiDir) {
|
|
9
|
+
const reusable = (components ?? []).filter((c) => !c.isPage);
|
|
10
|
+
const dsDirRe = uiDir
|
|
11
|
+
? new RegExp(`^${uiDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/`)
|
|
12
|
+
: /(^|\/)(packages\/ui|design-system|ui-kit)\//;
|
|
13
|
+
const usedFiles = new Set(reusable.filter((c) => c.usageCount > 0).map((c) => c.file));
|
|
14
|
+
return reusable.filter((c) => !c.usageCount && dsDirRe.test(c.file)
|
|
15
|
+
&& !/(^|\/)icons?\//.test(c.file)
|
|
16
|
+
&& !usedFiles.has(c.file));
|
|
17
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { distinctTypefaces } from '../lib/typefaces.mjs';
|
|
8
8
|
import { nearColorPairs } from './../lib/nearpairs.mjs';
|
|
9
|
+
import { neverImportedComponents } from '../lib/neverimported.mjs';
|
|
9
10
|
|
|
10
11
|
export function rulesMarkdown(h) {
|
|
11
12
|
const repoName = h.profile?.name ?? 'this repo';
|
|
@@ -88,13 +89,7 @@ const repoName = h.profile?.name ?? 'this repo';
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
// ---------- never-imported components ----------
|
|
91
|
-
const
|
|
92
|
-
? new RegExp(`^${h.profile.uiDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/`)
|
|
93
|
-
: /(^|\/)(packages\/ui|design-system|ui-kit)\//;
|
|
94
|
-
const usedFiles = new Set(reusable.filter((c) => c.usageCount > 0).map((c) => c.file));
|
|
95
|
-
const neverImported = reusable.filter((c) => !c.usageCount && dsDirRe.test(c.file)
|
|
96
|
-
&& !/(^|\/)icons?\//.test(c.file) // glyph sets are deliberately complete
|
|
97
|
-
&& !usedFiles.has(c.file)); // compound API: an adopted file's sub-exports are surface, not dead
|
|
92
|
+
const neverImported = neverImportedComponents(h.components, h.profile?.uiDir);
|
|
98
93
|
if (neverImported.length >= 3) {
|
|
99
94
|
section('Components nobody imports');
|
|
100
95
|
rule(`${neverImported.length} components are defined but never imported (${neverImported.slice(0, 3).map((c) => `\`<${c.name}>\``).join(', ')}…). Before writing any new component, check this list first; adopt one or flag it for deletion instead of adding another.`);
|