@felloinnovations/brand-kit 0.5.0 → 0.5.3
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.md +29 -9
- package/CHANGELOG.md +110 -0
- package/README.md +14 -6
- package/design-system/01-how-claude-decides.md +5 -3
- package/design-system/03-colour.md +22 -3
- package/design-system/05-space-and-layout.md +2 -1
- package/design-system/08-choosing-components.md +20 -4
- package/design-system/09-charts.md +7 -4
- package/design-system/10-patterns.md +1 -1
- package/design-system/11-page-recipes.md +40 -1
- package/design-system/13-motion.md +4 -4
- package/design-system/15-ai-experiences.md +11 -0
- package/design-system/17-decisions.md +51 -0
- package/design-system/components/AppShell/README.md +89 -21
- package/design-system/components/AppShell/preview.html +148 -3
- package/design-system/components/AppShell/style.css +47 -0
- package/design-system/components/Avatar/README.md +6 -3
- package/design-system/components/Avatar/preview.html +2 -1
- package/design-system/components/Avatar/style.css +3 -0
- package/design-system/components/Banner/README.md +9 -5
- package/design-system/components/Banner/preview.html +9 -0
- package/design-system/components/Banner/style.css +3 -0
- package/design-system/components/Button/README.md +29 -2
- package/design-system/components/Button/preview.html +33 -1
- package/design-system/components/Button/style.css +21 -0
- package/design-system/components/Calendar/README.md +148 -0
- package/design-system/components/Calendar/preview.html +449 -0
- package/design-system/components/Calendar/style.css +61 -0
- package/design-system/components/Card/README.md +13 -0
- package/design-system/components/Card/preview.html +23 -0
- package/design-system/components/Card/style.css +19 -0
- package/design-system/components/ChatCard/README.md +74 -3
- package/design-system/components/ChatCard/preview.html +126 -25
- package/design-system/components/ChatCard/style.css +6 -0
- package/design-system/components/CheckRow/README.md +1 -1
- package/design-system/components/CheckRow/preview.html +1 -1
- package/design-system/components/CheckRow/style.css +1 -1
- package/design-system/components/Chip/README.md +16 -7
- package/design-system/components/Chip/preview.html +25 -1
- package/design-system/components/Chip/style.css +5 -0
- package/design-system/components/Combobox/README.md +10 -4
- package/design-system/components/Combobox/preview.html +19 -4
- package/design-system/components/Combobox/style.css +7 -1
- package/design-system/components/CommentThread/README.md +138 -0
- package/design-system/components/CommentThread/preview.html +134 -0
- package/design-system/components/CommentThread/style.css +41 -0
- package/design-system/components/DateInput/README.md +38 -4
- package/design-system/components/DateInput/preview.html +50 -2
- package/design-system/components/DateInput/style.css +6 -0
- package/design-system/components/Dialog/README.md +26 -3
- package/design-system/components/Dialog/preview.html +80 -0
- package/design-system/components/Dialog/style.css +28 -3
- package/design-system/components/EmptyState/README.md +1 -1
- package/design-system/components/Fold/README.md +21 -0
- package/design-system/components/Fold/preview.html +44 -1
- package/design-system/components/Fold/style.css +30 -0
- package/design-system/components/InlineEdit/README.md +116 -0
- package/design-system/components/InlineEdit/preview.html +135 -0
- package/design-system/components/InlineEdit/style.css +34 -0
- package/design-system/components/Input/README.md +20 -1
- package/design-system/components/Input/preview.html +24 -0
- package/design-system/components/Input/style.css +13 -1
- package/design-system/components/KanbanBoard/README.md +69 -5
- package/design-system/components/KanbanBoard/preview.html +129 -0
- package/design-system/components/KanbanBoard/style.css +32 -1
- package/design-system/components/ListDetail/preview.html +1 -1
- package/design-system/components/Matrix/README.md +1 -1
- package/design-system/components/Matrix/preview.html +1 -1
- package/design-system/components/Motion/README.md +2 -2
- package/design-system/components/Motion/preview.html +8 -8
- package/design-system/components/PlanTimeline/README.md +144 -0
- package/design-system/components/PlanTimeline/preview.html +109 -0
- package/design-system/components/PlanTimeline/style.css +64 -0
- package/design-system/components/README.md +12 -7
- package/design-system/components/ScoreHero/README.md +2 -2
- package/design-system/components/ScoreHero/style.css +1 -1
- package/design-system/components/ShareBar/README.md +1 -1
- package/design-system/components/ShareBar/style.css +1 -1
- package/design-system/components/Skeleton/README.md +82 -17
- package/design-system/components/Skeleton/preview.html +84 -2
- package/design-system/components/Skeleton/style.css +9 -14
- package/design-system/components/Starter/README.md +1 -1
- package/design-system/components/Starter/preview.html +1 -1
- package/design-system/components/Stepper/README.md +81 -5
- package/design-system/components/Stepper/preview.html +64 -1
- package/design-system/components/Stepper/style.css +29 -0
- package/design-system/components/StreakCalendar/README.md +1 -0
- package/design-system/components/Table/README.md +8 -4
- package/design-system/components/Table/preview.html +16 -1
- package/design-system/components/Table/style.css +3 -1
- package/design-system/components/TextDiff/README.md +96 -0
- package/design-system/components/TextDiff/preview.html +42 -0
- package/design-system/components/TextDiff/style.css +21 -0
- package/design-system/components/TickStrip/README.md +1 -1
- package/design-system/components/TickStrip/preview.html +1 -1
- package/design-system/components/TickStrip/style.css +1 -1
- package/design-system/components/Timeline/README.md +4 -2
- package/design-system/components/TrendLine/README.md +38 -7
- package/design-system/components/TrendLine/preview.html +140 -3
- package/design-system/components/TrendLine/style.css +12 -2
- package/design-system/components/Well/README.md +1 -1
- package/design-system/components/Well/style.css +2 -2
- package/design-system/components/_house.css +1 -1
- package/design-system/components/bundle.css +501 -30
- package/design-system/tokens.json +17 -7
- package/dist/fello-core.css +2 -2
- package/dist/fello.css +2 -2
- package/dist/fello.scoped.css +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/scoped.js +2 -2
- package/dist/tokens.d.ts +16 -8
- package/dist/tokens.js +16 -8
- package/package.json +7 -2
- package/scripts/check-drift.mjs +325 -0
- package/scripts/token-model.mjs +2 -1
- package/tokens/_dark.css +3 -2
- package/tokens/_product.css +13 -11
- package/tokens/palettes.ts +1 -1
- package/tokens/shadcn-compat.css +1 -1
- package/tokens/shadcn-compat.hsl.css +1 -1
- package/tokens/tokens.css +23 -15
- package/tokens/tokens.md +56 -50
- package/tokens/tokens.root.css +23 -15
- package/tokens/tokens.ts +17 -9
package/dist/tokens.d.ts
CHANGED
|
@@ -122,6 +122,7 @@ declare const tokens: {
|
|
|
122
122
|
readonly "--ws-tint-accent": "color-mix(in srgb, var(--color-bg-page) 96%, #4f4c7c)";
|
|
123
123
|
readonly "--ws-tint-needs": "color-mix(in srgb, var(--ws-chart-under-half) 10%, var(--color-surface))";
|
|
124
124
|
readonly "--ws-tint-live": "color-mix(in srgb, var(--ws-chart-finished) 12%, var(--color-surface))";
|
|
125
|
+
readonly "--ws-tint-alert": "color-mix(in srgb, var(--ws-chart-alert) 8%, var(--color-surface))";
|
|
125
126
|
readonly "--ws-edge": "var(--color-gray-stroke)";
|
|
126
127
|
readonly "--ws-edge-card": "var(--color-gray-3)";
|
|
127
128
|
readonly "--ws-edge-card-1x": "color-mix(in srgb, var(--ws-edge-card) 50%, transparent)";
|
|
@@ -195,8 +196,8 @@ declare const tokens: {
|
|
|
195
196
|
readonly "--ws-rating-strong": "var(--color-stat-positive)";
|
|
196
197
|
readonly "--ws-rating-middle": "var(--color-brand-mid-blue)";
|
|
197
198
|
readonly "--ws-rating-needs-work": "var(--ws-chart-needs-work)";
|
|
198
|
-
readonly "--ws-shimmer-ground": "
|
|
199
|
-
readonly "--ws-shimmer": "linear-gradient(100deg in srgb, var(--ws-shimmer-ground) 0 34%, color-mix(in srgb, var(--ws-
|
|
199
|
+
readonly "--ws-shimmer-ground": "var(--ws-ghost)";
|
|
200
|
+
readonly "--ws-shimmer": "linear-gradient(100deg in srgb, var(--ws-shimmer-ground) 0 34%, color-mix(in srgb, var(--ws-shimmer-ground) 30%, var(--color-bg-page)) 50%, var(--ws-shimmer-ground) 66% 100%)";
|
|
200
201
|
readonly "--ws-card-tint-blue": "color-mix(in srgb, var(--ws-rating-middle) 10%, var(--color-surface))";
|
|
201
202
|
readonly "--ws-card-tint-done": "color-mix(in srgb, var(--ws-chart-finished) 10%, var(--color-surface))";
|
|
202
203
|
readonly "--ws-scrim": "color-mix(in srgb, var(--color-text-heading) 35%, transparent)";
|
|
@@ -269,6 +270,7 @@ declare const tokens: {
|
|
|
269
270
|
readonly "--ws-shadow-menu": "var(--ws-float-rim), 0 16px 32px -16px color-mix(in srgb, var(--ws-shade) 35%, transparent)";
|
|
270
271
|
readonly "--ws-shadow-tip": "var(--ws-float-rim), 0 16px 32px -20px color-mix(in srgb, var(--ws-shade) 45%, transparent)";
|
|
271
272
|
readonly "--ws-shadow-float": "var(--ws-float-rim), 0 40px 80px -30px color-mix(in srgb, var(--ws-shade) 30%, transparent), 0 10px 24px -12px color-mix(in srgb, var(--ws-shade) 14%, transparent)";
|
|
273
|
+
readonly "--ws-shadow-rise": "var(--ws-float-rim), 0 -16px 32px -16px color-mix(in srgb, var(--ws-shade) 35%, transparent)";
|
|
272
274
|
readonly "--ws-shadow-bar": "var(--ws-float-rim), 0 24px 60px -24px color-mix(in srgb, var(--ws-shade) 32%, transparent), 0 2px 10px color-mix(in srgb, var(--ws-shade) 6%, transparent)";
|
|
273
275
|
readonly "--ws-hover-faint": "color-mix(in srgb, var(--ws-ink) 3.137254902%, transparent)";
|
|
274
276
|
readonly "--ws-hover": "color-mix(in srgb, var(--ws-ink) 5.0980392157%, transparent)";
|
|
@@ -551,6 +553,7 @@ declare const themes: {
|
|
|
551
553
|
readonly "--ws-tint-accent": "#f7f4f4";
|
|
552
554
|
readonly "--ws-tint-needs": "#fff1ef";
|
|
553
555
|
readonly "--ws-tint-live": "#e1f0f0";
|
|
556
|
+
readonly "--ws-tint-alert": "#f9ebee";
|
|
554
557
|
readonly "--ws-edge": "#e8e8e8";
|
|
555
558
|
readonly "--ws-edge-card": "#d3d3d3";
|
|
556
559
|
readonly "--ws-edge-card-1x": "rgba(211, 211, 211, 0.5)";
|
|
@@ -624,8 +627,8 @@ declare const themes: {
|
|
|
624
627
|
readonly "--ws-rating-strong": "#098486";
|
|
625
628
|
readonly "--ws-rating-middle": "#495883";
|
|
626
629
|
readonly "--ws-rating-needs-work": "#f370a4";
|
|
627
|
-
readonly "--ws-shimmer-ground": "#
|
|
628
|
-
readonly "--ws-shimmer": "linear-gradient(100deg in srgb, #
|
|
630
|
+
readonly "--ws-shimmer-ground": "#e3dfdb";
|
|
631
|
+
readonly "--ws-shimmer": "linear-gradient(100deg in srgb, #e3dfdb 0 34%, #f6f3f0 50%, #e3dfdb 66% 100%)";
|
|
629
632
|
readonly "--ws-card-tint-blue": "#edeef3";
|
|
630
633
|
readonly "--ws-card-tint-done": "#e6f3f3";
|
|
631
634
|
readonly "--ws-scrim": "rgba(9, 53, 85, 0.35)";
|
|
@@ -698,6 +701,7 @@ declare const themes: {
|
|
|
698
701
|
readonly "--ws-shadow-menu": "0 0 0 0 transparent, 0 16px 32px -16px rgba(9, 53, 85, 0.35)";
|
|
699
702
|
readonly "--ws-shadow-tip": "0 0 0 0 transparent, 0 16px 32px -20px rgba(9, 53, 85, 0.45)";
|
|
700
703
|
readonly "--ws-shadow-float": "0 0 0 0 transparent, 0 40px 80px -30px rgba(9, 53, 85, 0.3), 0 10px 24px -12px rgba(9, 53, 85, 0.14)";
|
|
704
|
+
readonly "--ws-shadow-rise": "0 0 0 0 transparent, 0 -16px 32px -16px rgba(9, 53, 85, 0.35)";
|
|
701
705
|
readonly "--ws-shadow-bar": "0 0 0 0 transparent, 0 24px 60px -24px rgba(9, 53, 85, 0.32), 0 2px 10px rgba(9, 53, 85, 0.06)";
|
|
702
706
|
readonly "--ws-hover-faint": "rgba(9, 53, 85, 0.031)";
|
|
703
707
|
readonly "--ws-hover": "rgba(9, 53, 85, 0.051)";
|
|
@@ -975,6 +979,7 @@ declare const themes: {
|
|
|
975
979
|
readonly "--ws-tint-accent": "#131e28";
|
|
976
980
|
readonly "--ws-tint-needs": "#2e252a";
|
|
977
981
|
readonly "--ws-tint-live": "#112a32";
|
|
982
|
+
readonly "--ws-tint-alert": "#2e212c";
|
|
978
983
|
readonly "--ws-edge": "#1e2a36";
|
|
979
984
|
readonly "--ws-edge-card": "#273544";
|
|
980
985
|
readonly "--ws-edge-card-1x": "#273544";
|
|
@@ -1048,8 +1053,8 @@ declare const themes: {
|
|
|
1048
1053
|
readonly "--ws-rating-strong": "#3bb8b2";
|
|
1049
1054
|
readonly "--ws-rating-middle": "#93a6dc";
|
|
1050
1055
|
readonly "--ws-rating-needs-work": "#f370a4";
|
|
1051
|
-
readonly "--ws-shimmer-ground": "#
|
|
1052
|
-
readonly "--ws-shimmer": "linear-gradient(100deg, #
|
|
1056
|
+
readonly "--ws-shimmer-ground": "#273544";
|
|
1057
|
+
readonly "--ws-shimmer": "linear-gradient(100deg in srgb, #273544 0 34%, #354250 50%, #273544 66% 100%)";
|
|
1053
1058
|
readonly "--ws-card-tint-blue": "#1b2637";
|
|
1054
1059
|
readonly "--ws-card-tint-done": "rgba(17, 40, 47, 1)";
|
|
1055
1060
|
readonly "--ws-scrim": "rgba(2, 6, 10, 0.62)";
|
|
@@ -1122,6 +1127,7 @@ declare const themes: {
|
|
|
1122
1127
|
readonly "--ws-shadow-menu": "0 0 0 0.5px rgba(232, 237, 243, 0.08), 0 16px 32px -16px rgba(0, 0, 0, 0.35)";
|
|
1123
1128
|
readonly "--ws-shadow-tip": "0 0 0 0.5px rgba(232, 237, 243, 0.08), 0 16px 32px -20px rgba(0, 0, 0, 0.45)";
|
|
1124
1129
|
readonly "--ws-shadow-float": "0 0 0 0.5px rgba(232, 237, 243, 0.08), 0 40px 80px -30px rgba(0, 0, 0, 0.3), 0 10px 24px -12px rgba(0, 0, 0, 0.14)";
|
|
1130
|
+
readonly "--ws-shadow-rise": "0 0 0 0.5px rgba(232, 237, 243, 0.08), 0 -16px 32px -16px rgba(0, 0, 0, 0.35)";
|
|
1125
1131
|
readonly "--ws-shadow-bar": "0 0 0 0.5px rgba(232, 237, 243, 0.08), 0 24px 60px -24px rgba(0, 0, 0, 0.32), 0 2px 10px rgba(0, 0, 0, 0.06)";
|
|
1126
1132
|
readonly "--ws-hover-faint": "rgba(232, 237, 243, 0.031)";
|
|
1127
1133
|
readonly "--ws-hover": "rgba(232, 237, 243, 0.051)";
|
|
@@ -1399,6 +1405,7 @@ declare const themes: {
|
|
|
1399
1405
|
readonly "--ws-tint-accent": "#11283e";
|
|
1400
1406
|
readonly "--ws-tint-needs": "#302a3a";
|
|
1401
1407
|
readonly "--ws-tint-live": "#062d41";
|
|
1408
|
+
readonly "--ws-tint-alert": "#30243d";
|
|
1402
1409
|
readonly "--ws-edge": "rgba(255, 255, 255, 0.1)";
|
|
1403
1410
|
readonly "--ws-edge-card": "rgba(255, 255, 255, 0.18)";
|
|
1404
1411
|
readonly "--ws-edge-card-1x": "rgba(255, 255, 255, 0.18)";
|
|
@@ -1472,8 +1479,8 @@ declare const themes: {
|
|
|
1472
1479
|
readonly "--ws-rating-strong": "#3bb8b2";
|
|
1473
1480
|
readonly "--ws-rating-middle": "#93a6dc";
|
|
1474
1481
|
readonly "--ws-rating-needs-work": "#f370a4";
|
|
1475
|
-
readonly "--ws-shimmer-ground": "#
|
|
1476
|
-
readonly "--ws-shimmer": "linear-gradient(100deg, #
|
|
1482
|
+
readonly "--ws-shimmer-ground": "#273544";
|
|
1483
|
+
readonly "--ws-shimmer": "linear-gradient(100deg in srgb, #273544 0 34%, #364351 50%, #273544 66% 100%)";
|
|
1477
1484
|
readonly "--ws-card-tint-blue": "#142a46";
|
|
1478
1485
|
readonly "--ws-card-tint-done": "#062d41";
|
|
1479
1486
|
readonly "--ws-scrim": "rgba(0, 8, 15, 0.62)";
|
|
@@ -1546,6 +1553,7 @@ declare const themes: {
|
|
|
1546
1553
|
readonly "--ws-shadow-menu": "0 0 0 0.5px rgba(255, 255, 255, 0.08), 0 16px 32px -16px rgba(0, 0, 0, 0.35)";
|
|
1547
1554
|
readonly "--ws-shadow-tip": "0 0 0 0.5px rgba(255, 255, 255, 0.08), 0 16px 32px -20px rgba(0, 0, 0, 0.45)";
|
|
1548
1555
|
readonly "--ws-shadow-float": "0 0 0 0.5px rgba(255, 255, 255, 0.08), 0 40px 80px -30px rgba(0, 0, 0, 0.3), 0 10px 24px -12px rgba(0, 0, 0, 0.14)";
|
|
1556
|
+
readonly "--ws-shadow-rise": "0 0 0 0.5px rgba(255, 255, 255, 0.08), 0 -16px 32px -16px rgba(0, 0, 0, 0.35)";
|
|
1549
1557
|
readonly "--ws-shadow-bar": "0 0 0 0.5px rgba(255, 255, 255, 0.08), 0 24px 60px -24px rgba(0, 0, 0, 0.32), 0 2px 10px rgba(0, 0, 0, 0.06)";
|
|
1550
1558
|
readonly "--ws-hover-faint": "rgba(255, 255, 255, 0.031)";
|
|
1551
1559
|
readonly "--ws-hover": "rgba(255, 255, 255, 0.051)";
|
package/dist/tokens.js
CHANGED
|
@@ -125,6 +125,7 @@ var tokens = {
|
|
|
125
125
|
"--ws-tint-accent": "color-mix(in srgb, var(--color-bg-page) 96%, #4f4c7c)",
|
|
126
126
|
"--ws-tint-needs": "color-mix(in srgb, var(--ws-chart-under-half) 10%, var(--color-surface))",
|
|
127
127
|
"--ws-tint-live": "color-mix(in srgb, var(--ws-chart-finished) 12%, var(--color-surface))",
|
|
128
|
+
"--ws-tint-alert": "color-mix(in srgb, var(--ws-chart-alert) 8%, var(--color-surface))",
|
|
128
129
|
"--ws-edge": "var(--color-gray-stroke)",
|
|
129
130
|
"--ws-edge-card": "var(--color-gray-3)",
|
|
130
131
|
"--ws-edge-card-1x": "color-mix(in srgb, var(--ws-edge-card) 50%, transparent)",
|
|
@@ -198,8 +199,8 @@ var tokens = {
|
|
|
198
199
|
"--ws-rating-strong": "var(--color-stat-positive)",
|
|
199
200
|
"--ws-rating-middle": "var(--color-brand-mid-blue)",
|
|
200
201
|
"--ws-rating-needs-work": "var(--ws-chart-needs-work)",
|
|
201
|
-
"--ws-shimmer-ground": "
|
|
202
|
-
"--ws-shimmer": "linear-gradient(100deg in srgb, var(--ws-shimmer-ground) 0 34%, color-mix(in srgb, var(--ws-
|
|
202
|
+
"--ws-shimmer-ground": "var(--ws-ghost)",
|
|
203
|
+
"--ws-shimmer": "linear-gradient(100deg in srgb, var(--ws-shimmer-ground) 0 34%, color-mix(in srgb, var(--ws-shimmer-ground) 30%, var(--color-bg-page)) 50%, var(--ws-shimmer-ground) 66% 100%)",
|
|
203
204
|
"--ws-card-tint-blue": "color-mix(in srgb, var(--ws-rating-middle) 10%, var(--color-surface))",
|
|
204
205
|
"--ws-card-tint-done": "color-mix(in srgb, var(--ws-chart-finished) 10%, var(--color-surface))",
|
|
205
206
|
"--ws-scrim": "color-mix(in srgb, var(--color-text-heading) 35%, transparent)",
|
|
@@ -272,6 +273,7 @@ var tokens = {
|
|
|
272
273
|
"--ws-shadow-menu": "var(--ws-float-rim), 0 16px 32px -16px color-mix(in srgb, var(--ws-shade) 35%, transparent)",
|
|
273
274
|
"--ws-shadow-tip": "var(--ws-float-rim), 0 16px 32px -20px color-mix(in srgb, var(--ws-shade) 45%, transparent)",
|
|
274
275
|
"--ws-shadow-float": "var(--ws-float-rim), 0 40px 80px -30px color-mix(in srgb, var(--ws-shade) 30%, transparent), 0 10px 24px -12px color-mix(in srgb, var(--ws-shade) 14%, transparent)",
|
|
276
|
+
"--ws-shadow-rise": "var(--ws-float-rim), 0 -16px 32px -16px color-mix(in srgb, var(--ws-shade) 35%, transparent)",
|
|
275
277
|
"--ws-shadow-bar": "var(--ws-float-rim), 0 24px 60px -24px color-mix(in srgb, var(--ws-shade) 32%, transparent), 0 2px 10px color-mix(in srgb, var(--ws-shade) 6%, transparent)",
|
|
276
278
|
"--ws-hover-faint": "color-mix(in srgb, var(--ws-ink) 3.137254902%, transparent)",
|
|
277
279
|
"--ws-hover": "color-mix(in srgb, var(--ws-ink) 5.0980392157%, transparent)",
|
|
@@ -551,6 +553,7 @@ var themes = {
|
|
|
551
553
|
"--ws-tint-accent": "#f7f4f4",
|
|
552
554
|
"--ws-tint-needs": "#fff1ef",
|
|
553
555
|
"--ws-tint-live": "#e1f0f0",
|
|
556
|
+
"--ws-tint-alert": "#f9ebee",
|
|
554
557
|
"--ws-edge": "#e8e8e8",
|
|
555
558
|
"--ws-edge-card": "#d3d3d3",
|
|
556
559
|
"--ws-edge-card-1x": "rgba(211, 211, 211, 0.5)",
|
|
@@ -624,8 +627,8 @@ var themes = {
|
|
|
624
627
|
"--ws-rating-strong": "#098486",
|
|
625
628
|
"--ws-rating-middle": "#495883",
|
|
626
629
|
"--ws-rating-needs-work": "#f370a4",
|
|
627
|
-
"--ws-shimmer-ground": "#
|
|
628
|
-
"--ws-shimmer": "linear-gradient(100deg in srgb, #
|
|
630
|
+
"--ws-shimmer-ground": "#e3dfdb",
|
|
631
|
+
"--ws-shimmer": "linear-gradient(100deg in srgb, #e3dfdb 0 34%, #f6f3f0 50%, #e3dfdb 66% 100%)",
|
|
629
632
|
"--ws-card-tint-blue": "#edeef3",
|
|
630
633
|
"--ws-card-tint-done": "#e6f3f3",
|
|
631
634
|
"--ws-scrim": "rgba(9, 53, 85, 0.35)",
|
|
@@ -698,6 +701,7 @@ var themes = {
|
|
|
698
701
|
"--ws-shadow-menu": "0 0 0 0 transparent, 0 16px 32px -16px rgba(9, 53, 85, 0.35)",
|
|
699
702
|
"--ws-shadow-tip": "0 0 0 0 transparent, 0 16px 32px -20px rgba(9, 53, 85, 0.45)",
|
|
700
703
|
"--ws-shadow-float": "0 0 0 0 transparent, 0 40px 80px -30px rgba(9, 53, 85, 0.3), 0 10px 24px -12px rgba(9, 53, 85, 0.14)",
|
|
704
|
+
"--ws-shadow-rise": "0 0 0 0 transparent, 0 -16px 32px -16px rgba(9, 53, 85, 0.35)",
|
|
701
705
|
"--ws-shadow-bar": "0 0 0 0 transparent, 0 24px 60px -24px rgba(9, 53, 85, 0.32), 0 2px 10px rgba(9, 53, 85, 0.06)",
|
|
702
706
|
"--ws-hover-faint": "rgba(9, 53, 85, 0.031)",
|
|
703
707
|
"--ws-hover": "rgba(9, 53, 85, 0.051)",
|
|
@@ -975,6 +979,7 @@ var themes = {
|
|
|
975
979
|
"--ws-tint-accent": "#131e28",
|
|
976
980
|
"--ws-tint-needs": "#2e252a",
|
|
977
981
|
"--ws-tint-live": "#112a32",
|
|
982
|
+
"--ws-tint-alert": "#2e212c",
|
|
978
983
|
"--ws-edge": "#1e2a36",
|
|
979
984
|
"--ws-edge-card": "#273544",
|
|
980
985
|
"--ws-edge-card-1x": "#273544",
|
|
@@ -1048,8 +1053,8 @@ var themes = {
|
|
|
1048
1053
|
"--ws-rating-strong": "#3bb8b2",
|
|
1049
1054
|
"--ws-rating-middle": "#93a6dc",
|
|
1050
1055
|
"--ws-rating-needs-work": "#f370a4",
|
|
1051
|
-
"--ws-shimmer-ground": "#
|
|
1052
|
-
"--ws-shimmer": "linear-gradient(100deg, #
|
|
1056
|
+
"--ws-shimmer-ground": "#273544",
|
|
1057
|
+
"--ws-shimmer": "linear-gradient(100deg in srgb, #273544 0 34%, #354250 50%, #273544 66% 100%)",
|
|
1053
1058
|
"--ws-card-tint-blue": "#1b2637",
|
|
1054
1059
|
"--ws-card-tint-done": "rgba(17, 40, 47, 1)",
|
|
1055
1060
|
"--ws-scrim": "rgba(2, 6, 10, 0.62)",
|
|
@@ -1122,6 +1127,7 @@ var themes = {
|
|
|
1122
1127
|
"--ws-shadow-menu": "0 0 0 0.5px rgba(232, 237, 243, 0.08), 0 16px 32px -16px rgba(0, 0, 0, 0.35)",
|
|
1123
1128
|
"--ws-shadow-tip": "0 0 0 0.5px rgba(232, 237, 243, 0.08), 0 16px 32px -20px rgba(0, 0, 0, 0.45)",
|
|
1124
1129
|
"--ws-shadow-float": "0 0 0 0.5px rgba(232, 237, 243, 0.08), 0 40px 80px -30px rgba(0, 0, 0, 0.3), 0 10px 24px -12px rgba(0, 0, 0, 0.14)",
|
|
1130
|
+
"--ws-shadow-rise": "0 0 0 0.5px rgba(232, 237, 243, 0.08), 0 -16px 32px -16px rgba(0, 0, 0, 0.35)",
|
|
1125
1131
|
"--ws-shadow-bar": "0 0 0 0.5px rgba(232, 237, 243, 0.08), 0 24px 60px -24px rgba(0, 0, 0, 0.32), 0 2px 10px rgba(0, 0, 0, 0.06)",
|
|
1126
1132
|
"--ws-hover-faint": "rgba(232, 237, 243, 0.031)",
|
|
1127
1133
|
"--ws-hover": "rgba(232, 237, 243, 0.051)",
|
|
@@ -1399,6 +1405,7 @@ var themes = {
|
|
|
1399
1405
|
"--ws-tint-accent": "#11283e",
|
|
1400
1406
|
"--ws-tint-needs": "#302a3a",
|
|
1401
1407
|
"--ws-tint-live": "#062d41",
|
|
1408
|
+
"--ws-tint-alert": "#30243d",
|
|
1402
1409
|
"--ws-edge": "rgba(255, 255, 255, 0.1)",
|
|
1403
1410
|
"--ws-edge-card": "rgba(255, 255, 255, 0.18)",
|
|
1404
1411
|
"--ws-edge-card-1x": "rgba(255, 255, 255, 0.18)",
|
|
@@ -1472,8 +1479,8 @@ var themes = {
|
|
|
1472
1479
|
"--ws-rating-strong": "#3bb8b2",
|
|
1473
1480
|
"--ws-rating-middle": "#93a6dc",
|
|
1474
1481
|
"--ws-rating-needs-work": "#f370a4",
|
|
1475
|
-
"--ws-shimmer-ground": "#
|
|
1476
|
-
"--ws-shimmer": "linear-gradient(100deg, #
|
|
1482
|
+
"--ws-shimmer-ground": "#273544",
|
|
1483
|
+
"--ws-shimmer": "linear-gradient(100deg in srgb, #273544 0 34%, #364351 50%, #273544 66% 100%)",
|
|
1477
1484
|
"--ws-card-tint-blue": "#142a46",
|
|
1478
1485
|
"--ws-card-tint-done": "#062d41",
|
|
1479
1486
|
"--ws-scrim": "rgba(0, 8, 15, 0.62)",
|
|
@@ -1546,6 +1553,7 @@ var themes = {
|
|
|
1546
1553
|
"--ws-shadow-menu": "0 0 0 0.5px rgba(255, 255, 255, 0.08), 0 16px 32px -16px rgba(0, 0, 0, 0.35)",
|
|
1547
1554
|
"--ws-shadow-tip": "0 0 0 0.5px rgba(255, 255, 255, 0.08), 0 16px 32px -20px rgba(0, 0, 0, 0.45)",
|
|
1548
1555
|
"--ws-shadow-float": "0 0 0 0.5px rgba(255, 255, 255, 0.08), 0 40px 80px -30px rgba(0, 0, 0, 0.3), 0 10px 24px -12px rgba(0, 0, 0, 0.14)",
|
|
1556
|
+
"--ws-shadow-rise": "0 0 0 0.5px rgba(255, 255, 255, 0.08), 0 -16px 32px -16px rgba(0, 0, 0, 0.35)",
|
|
1549
1557
|
"--ws-shadow-bar": "0 0 0 0.5px rgba(255, 255, 255, 0.08), 0 24px 60px -24px rgba(0, 0, 0, 0.32), 0 2px 10px rgba(0, 0, 0, 0.06)",
|
|
1550
1558
|
"--ws-hover-faint": "rgba(255, 255, 255, 0.031)",
|
|
1551
1559
|
"--ws-hover": "rgba(255, 255, 255, 0.051)",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@felloinnovations/brand-kit",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Fello brand tokens, the house stylesheet (fello.css), React components and the design system guide. One source of truth for every GTM app.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
21
|
"scripts/check-palette.mjs",
|
|
22
|
+
"scripts/check-drift.mjs",
|
|
22
23
|
"scripts/token-model.mjs",
|
|
23
24
|
"tokens",
|
|
24
25
|
"design-system",
|
|
@@ -26,6 +27,9 @@
|
|
|
26
27
|
"AGENTS.md",
|
|
27
28
|
"CHANGELOG.md"
|
|
28
29
|
],
|
|
30
|
+
"bin": {
|
|
31
|
+
"brand-kit-drift": "scripts/check-drift.mjs"
|
|
32
|
+
},
|
|
29
33
|
"main": "./dist/index.js",
|
|
30
34
|
"module": "./dist/index.js",
|
|
31
35
|
"types": "./dist/index.d.ts",
|
|
@@ -76,7 +80,8 @@
|
|
|
76
80
|
"hosted": "node scripts/build-hosted.mjs",
|
|
77
81
|
"check:geometry": "node scripts/check-geometry.mjs",
|
|
78
82
|
"check:scope": "node scripts/check-scope.mjs",
|
|
79
|
-
"check:palette": "node scripts/check-palette.mjs"
|
|
83
|
+
"check:palette": "node scripts/check-palette.mjs",
|
|
84
|
+
"check:drift": "node scripts/check-drift.mjs --selftest"
|
|
80
85
|
},
|
|
81
86
|
"dependencies": {
|
|
82
87
|
"@radix-ui/react-tabs": "^1.1.21",
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* check-drift: the static half of brand-audit as a script, so nothing depends on an agent remembering to grep every
|
|
4
|
+
* file. It walks an app's own source (never node_modules, builds or a vendored copy of the kit) and lists every line
|
|
5
|
+
* that has drifted from the Fello brand kit, by file and line:
|
|
6
|
+
*
|
|
7
|
+
* colour hex, rgb(), hsl(), oklch() literals and Tailwind palette classes (bg-blue-500); each hex is marked as
|
|
8
|
+
* matching a kit token, close to one, or no token
|
|
9
|
+
* tokens an app redefining a kit token (--ws-*, --color-*, ...) or restyling a kit class (.ws-*)
|
|
10
|
+
* type px font sizes, text-[14px], a font-family that is not the kit's
|
|
11
|
+
* radius raw border-radius, rounded-[...]
|
|
12
|
+
* shadow raw box-shadow, shadow-[...]
|
|
13
|
+
* trap text-[var(--x)]: Tailwind reads a bare var as a font size, so the colour silently drops
|
|
14
|
+
* motion raw durations and curves, transition-all, duration-300, animate-*, @keyframes, hover lifts
|
|
15
|
+
* underline a link underlined at rest, or its hover underline taken away (04-type: medium, underlined under the pointer)
|
|
16
|
+
* words em dashes in markup
|
|
17
|
+
* copy a local components/ui file named after a kit component that never touches the kit
|
|
18
|
+
*
|
|
19
|
+
* A line that is right as it is carries `brand-kit-ignore: <why>` on it or on the line above.
|
|
20
|
+
*
|
|
21
|
+
* node check-drift.mjs [dir] report; exit 1 when anything drifted
|
|
22
|
+
* node check-drift.mjs [dir] --json the findings as JSON
|
|
23
|
+
* node check-drift.mjs [dir] --write-baseline <file> record today's counts per file and rule, exit 0
|
|
24
|
+
* node check-drift.mjs [dir] --baseline <file> exit 1 only when a file and rule has more than its baseline
|
|
25
|
+
* (CI for an app mid-migration: nothing new gets in)
|
|
26
|
+
* node check-drift.mjs --selftest plants known drift in a temp folder and proves each is caught
|
|
27
|
+
*
|
|
28
|
+
* In an app: node node_modules/@felloinnovations/brand-kit/scripts/check-drift.mjs src
|
|
29
|
+
*/
|
|
30
|
+
import { readFileSync, writeFileSync, readdirSync, statSync, mkdtempSync, mkdirSync, rmSync } from 'node:fs';
|
|
31
|
+
import { join, relative, dirname, extname, basename } from 'node:path';
|
|
32
|
+
import { fileURLToPath } from 'node:url';
|
|
33
|
+
import { tmpdir } from 'node:os';
|
|
34
|
+
|
|
35
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
|
|
37
|
+
// ---- The kit's tokens: every name, and the light value of each written as a hex.
|
|
38
|
+
function loadTokens() {
|
|
39
|
+
const css = readFileSync(join(here, '../tokens/tokens.root.css'), 'utf8');
|
|
40
|
+
const names = new Set();
|
|
41
|
+
const byHex = new Map();
|
|
42
|
+
for (const m of css.matchAll(/(--[a-z0-9-]+)\s*:\s*([^;]+);/gi)) {
|
|
43
|
+
names.add(m[1]);
|
|
44
|
+
const hex = m[2].trim().match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
|
|
45
|
+
if (hex && !byHex.has(norm(hex[0]))) byHex.set(norm(hex[0]), m[1]);
|
|
46
|
+
}
|
|
47
|
+
return { names, byHex };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function norm(hex) {
|
|
51
|
+
let h = hex.slice(1).toLowerCase();
|
|
52
|
+
if (h.length === 3 || h.length === 4) h = [...h].map((c) => c + c).join('');
|
|
53
|
+
return `#${h.slice(0, 6)}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const rgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
|
57
|
+
|
|
58
|
+
/** "matches --x", "close to --x (#abc)" (no channel more than 12 off), or "no token". */
|
|
59
|
+
function classify(hex, byHex) {
|
|
60
|
+
const h = norm(hex);
|
|
61
|
+
if (byHex.has(h)) return `matches ${byHex.get(h)}`;
|
|
62
|
+
const [r, g, b] = rgb(h);
|
|
63
|
+
let best = null;
|
|
64
|
+
for (const [v, name] of byHex) {
|
|
65
|
+
const [r2, g2, b2] = rgb(v);
|
|
66
|
+
const d = Math.max(Math.abs(r - r2), Math.abs(g - g2), Math.abs(b - b2));
|
|
67
|
+
if (d <= 12 && (!best || d < best.d)) best = { d, name, v };
|
|
68
|
+
}
|
|
69
|
+
return best ? `close to ${best.name} (${best.v})` : 'no token';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---- What to read.
|
|
73
|
+
const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', 'out', '.next', '.nuxt', '.svelte-kit', '.turbo', '.vercel',
|
|
74
|
+
'.git', '.claude', 'coverage', 'storybook-static', '.output']);
|
|
75
|
+
const EXT = new Set(['.css', '.scss', '.sass', '.less', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.html', '.vue', '.svelte', '.astro']);
|
|
76
|
+
const STYLE = new Set(['.css', '.scss', '.sass', '.less']);
|
|
77
|
+
const MARKUP = new Set(['.tsx', '.jsx', '.html', '.vue', '.svelte', '.astro']);
|
|
78
|
+
const SKIP_FILE = /\.(d\.ts|test\.[jt]sx?|spec\.[jt]sx?|stories\.[jt]sx?)$|\.min\.(css|js)$/;
|
|
79
|
+
// A vendored copy of the kit (fello-css.mjs output, the brand-kit's own files) is the kit, not drift.
|
|
80
|
+
const VENDORED = /Generated by scripts\/fello-css\.mjs|generated by scripts\/build-bundle\.mjs/;
|
|
81
|
+
|
|
82
|
+
function* walk(dir) {
|
|
83
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
84
|
+
if (e.isDirectory()) {
|
|
85
|
+
if (!SKIP_DIRS.has(e.name)) yield* walk(join(dir, e.name));
|
|
86
|
+
} else if (EXT.has(extname(e.name)) && !SKIP_FILE.test(e.name)) yield join(dir, e.name);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---- The rules. Each returns [{ col, value, note }] for one line.
|
|
91
|
+
const TW_HUES = 'slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose';
|
|
92
|
+
const TW_PREFIX = 'bg|text|border|ring|fill|stroke|from|via|to|outline|divide|placeholder|accent|caret|decoration|shadow';
|
|
93
|
+
const KIT_UI = new Set(['button', 'badge', 'card', 'input', 'label', 'skeleton', 'tabs', 'toast', 'toaster', 'checkbox',
|
|
94
|
+
'radio', 'radio-group', 'switch', 'select', 'textarea', 'dialog', 'tooltip', 'popover', 'avatar', 'table', 'menu',
|
|
95
|
+
'dropdown-menu', 'slider', 'pagination', 'breadcrumb', 'stepper', 'combobox']);
|
|
96
|
+
|
|
97
|
+
const all = (re, line, fn) => [...line.matchAll(re)].flatMap((m) => fn(m) ?? []);
|
|
98
|
+
|
|
99
|
+
function rules(tokens) {
|
|
100
|
+
return {
|
|
101
|
+
colour: (line, ext) => [
|
|
102
|
+
...all(/(^|[\s'"`(:,[=])(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3,4}))(?![0-9a-z_-])/gi, line, (m) => {
|
|
103
|
+
if (/(href|to|id)=["'`{]*$/.test(line.slice(0, m.index + m[1].length))) return null;
|
|
104
|
+
if (!STYLE.has(ext) && /^#[0-9a-f]{3,4}$/i.test(m[2]) && !/color|colour|fill|stroke|background|border|#[0-9a-f]{3,4}['"`;)]/i.test(line)) return null;
|
|
105
|
+
return { col: m.index + m[1].length, value: m[2], note: classify(m[2], tokens.byHex) };
|
|
106
|
+
}),
|
|
107
|
+
...all(/\b(rgba?|hsla?|oklch|oklab|lab|lch)\(([^)]*)\)/g, line, (m) =>
|
|
108
|
+
/var\(|from\s/.test(m[2]) ? null : { col: m.index, value: m[0], note: 'no token' }),
|
|
109
|
+
...all(new RegExp(`(?<![\\w-])(?:[a-z-]+:)*(?:${TW_PREFIX})-(?:${TW_HUES})-\\d{2,3}(?:\\/\\d+)?\\b`, 'g'), line, (m) =>
|
|
110
|
+
({ col: m.index, value: m[0], note: 'Tailwind palette, not a kit token' })),
|
|
111
|
+
],
|
|
112
|
+
tokens: (line) => [
|
|
113
|
+
...all(/(?<![\w(-])(--(?:ws|color|font|space|radius|shadow|text|size|brand)-[a-z0-9-]+)\s*:/g, line, (m) =>
|
|
114
|
+
tokens.names.has(m[1]) ? { col: m.index, value: m[1], note: 'redefines a kit token (a palette, 20-palettes.md, sets colours)' } : null),
|
|
115
|
+
...all(/(^|[\s,}>~+])(\.ws-[a-z0-9_-]+)[^{};]*\{/g, line, (m) =>
|
|
116
|
+
({ col: m.index + m[1].length, value: m[2], note: 'restyles a kit class' })),
|
|
117
|
+
],
|
|
118
|
+
type: (line) => [
|
|
119
|
+
...all(/font-size\s*:\s*[\d.]+px/g, line, (m) => ({ col: m.index, value: m[0], note: 'px font size' })),
|
|
120
|
+
...all(/fontSize\s*:\s*['"]?[\d.]+(px)?['"]?\s*[,}]/g, line, (m) => ({ col: m.index, value: m[0], note: 'px font size' })),
|
|
121
|
+
...all(/\btext-\[[\d.]+(px|rem)\]/g, line, (m) => ({ col: m.index, value: m[0], note: 'arbitrary font size' })),
|
|
122
|
+
...all(/font-family\s*:\s*([^;}]+)/g, line, (m) =>
|
|
123
|
+
/var\(|instrument sans|inherit|monospace|ui-monospace/i.test(m[1]) ? null : { col: m.index, value: m[0].trim(), note: 'not the kit face' }),
|
|
124
|
+
...all(/\bfont-\[['"]?[A-Za-z]/g, line, (m) => ({ col: m.index, value: m[0], note: 'not the kit face' })),
|
|
125
|
+
],
|
|
126
|
+
radius: (line) => [
|
|
127
|
+
...all(/border-radius\s*:\s*(?!var|0[;\s]|inherit|50%)[\d.]+(px|rem|em)/g, line, (m) => ({ col: m.index, value: m[0], note: 'raw radius' })),
|
|
128
|
+
...all(/borderRadius\s*:\s*['"]?\d/g, line, (m) => ({ col: m.index, value: m[0], note: 'raw radius' })),
|
|
129
|
+
...all(/\brounded(-[a-z]{1,2})?-\[(?!var)[^\]]+\]/g, line, (m) => ({ col: m.index, value: m[0], note: 'raw radius' })),
|
|
130
|
+
],
|
|
131
|
+
shadow: (line) => [
|
|
132
|
+
...all(/box-shadow\s*:\s*(?!var|none|inherit|0\s*0\s*0\s*0)([^;}]+)/g, line, (m) => ({ col: m.index, value: m[0].trim(), note: 'raw shadow' })),
|
|
133
|
+
...all(/boxShadow\s*:\s*['"`](?!var|none)/g, line, (m) => ({ col: m.index, value: m[0], note: 'raw shadow' })),
|
|
134
|
+
...all(/\bshadow-\[(?!var)[^\]]+\]/g, line, (m) => ({ col: m.index, value: m[0], note: 'raw shadow' })),
|
|
135
|
+
],
|
|
136
|
+
trap: (line) => all(/\btext-\[var\(--/g, line, (m) => ({ col: m.index, value: m[0], note: 'write text-[color:var(--x)] or text-[length:var(--x)]' })),
|
|
137
|
+
motion: (line, ext) => [
|
|
138
|
+
...all(/(transition|animation)(-duration)?\s*:\s*(?![^;}]*var\()[^;}]*\b\d*\.?\d+m?s\b[^;}]*/g, line, (m) => ({ col: m.index, value: m[0].trim(), note: 'raw duration (--ws-spring-*, --ws-duration-*)' })),
|
|
139
|
+
...all(/cubic-bezier\(/g, line, (m) => ({ col: m.index, value: m[0], note: 'raw curve (--ws-ease-*)' })),
|
|
140
|
+
...(STYLE.has(ext) ? all(/@keyframes\s+[\w-]+/g, line, (m) => ({ col: m.index, value: m[0], note: 'hand-rolled animation' })) : []),
|
|
141
|
+
...all(/(?<![\w-])(transition-all|duration-\d+|ease-(in|out|in-out|linear)|animate-(spin|ping|pulse|bounce))(?![\w-])/g, line, (m) =>
|
|
142
|
+
({ col: m.index, value: m[0], note: 'Tailwind motion, not the kit springs' })),
|
|
143
|
+
...all(/\bhover:(-?translate-[xy]-[\w.[\]-]+|-?scale-[\w.[\]-]+|shadow(-[\w[\]-]+)?)/g, line, (m) =>
|
|
144
|
+
({ col: m.index, value: m[0], note: 'hover lift (hover is a tint)' })),
|
|
145
|
+
],
|
|
146
|
+
underline: (line) => /class(Name)?\s*=|\bcn\(|clsx\(/.test(line) ? [
|
|
147
|
+
// A dotted underline is the kit's mark for a cell whose full words show on hover (04-type), not a link.
|
|
148
|
+
...(/decoration-dotted/.test(line) ? [] : all(/(?<![\w:-])underline(?![\w-])/g, line, (m) => ({ col: m.index, value: 'underline', note: 'underlined at rest' }))),
|
|
149
|
+
...all(/\bhover:no-underline\b/g, line, (m) => ({ col: m.index, value: m[0], note: 'hover underline taken away' })),
|
|
150
|
+
] : [],
|
|
151
|
+
words: (line, ext) => (MARKUP.has(ext)
|
|
152
|
+
? all(/—/g, line, (m) => ({ col: m.index, value: '—', note: 'em dash in UI words' })) : []),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The lines with comments (/* *\/ across lines, // to the end, <!-- -->) blanked to spaces, so columns hold and a
|
|
157
|
+
* colour or a dash in a comment is not drift. `//` after a colon (a URL) is not a comment. */
|
|
158
|
+
function uncomment(lines) {
|
|
159
|
+
let open = false;
|
|
160
|
+
return lines.map((line) => {
|
|
161
|
+
let out = '';
|
|
162
|
+
let i = 0;
|
|
163
|
+
while (i < line.length) {
|
|
164
|
+
if (open) {
|
|
165
|
+
const end = line.indexOf('*/', i);
|
|
166
|
+
const stop = end < 0 ? line.length : end + 2;
|
|
167
|
+
out += ' '.repeat(stop - i);
|
|
168
|
+
i = stop;
|
|
169
|
+
if (end >= 0) open = false;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const rest = line.slice(i);
|
|
173
|
+
if (rest.startsWith('/*')) { open = true; continue; }
|
|
174
|
+
if (rest.startsWith('<!--')) {
|
|
175
|
+
const end = line.indexOf('-->', i);
|
|
176
|
+
const stop = end < 0 ? line.length : end + 3;
|
|
177
|
+
out += ' '.repeat(stop - i);
|
|
178
|
+
i = stop;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (rest.startsWith('//') && (i === 0 || /[\s;{}(),]/.test(line[i - 1]))) { out += ' '.repeat(line.length - i); break; }
|
|
182
|
+
out += line[i++];
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function scan(dir, tokens) {
|
|
189
|
+
const R = rules(tokens);
|
|
190
|
+
const findings = [];
|
|
191
|
+
for (const file of walk(dir)) {
|
|
192
|
+
const text = readFileSync(file, 'utf8');
|
|
193
|
+
if (VENDORED.test(text.slice(0, 2000))) continue;
|
|
194
|
+
const rel = relative(dir, file) || basename(file);
|
|
195
|
+
const ext = extname(file);
|
|
196
|
+
const lines = text.split('\n');
|
|
197
|
+
const code = uncomment(lines);
|
|
198
|
+
lines.forEach((raw, i) => {
|
|
199
|
+
if (/brand-kit-ignore/.test(raw) || /brand-kit-ignore/.test(lines[i - 1] ?? '')) return;
|
|
200
|
+
if (raw.length > 2000) return; // a minified or inlined blob
|
|
201
|
+
const line = code[i];
|
|
202
|
+
for (const [rule, fn] of Object.entries(R)) {
|
|
203
|
+
for (const f of fn(line, ext)) findings.push({ file: rel, line: i + 1, col: f.col + 1, rule, value: f.value, note: f.note });
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
const name = basename(file, ext).toLowerCase();
|
|
207
|
+
if (/components\/ui\//.test(file) && KIT_UI.has(name) && !/ws-|@felloinnovations\/brand-kit/.test(text)) {
|
|
208
|
+
findings.push({ file: rel, line: 1, col: 1, rule: 'copy', value: basename(file), note: 'a local component the kit has; use the kit\'s class or component' });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return findings;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const key = (f) => `${f.file}::${f.rule}`;
|
|
215
|
+
const countBy = (fs) => fs.reduce((m, f) => m.set(key(f), (m.get(key(f)) ?? 0) + 1), new Map());
|
|
216
|
+
|
|
217
|
+
function report(findings, out = console.log) {
|
|
218
|
+
if (!findings.length) return out('brand-kit drift: nothing found.');
|
|
219
|
+
const files = new Set(findings.map((f) => f.file));
|
|
220
|
+
out(`brand-kit drift: ${findings.length} finding${findings.length === 1 ? '' : 's'} in ${files.size} file${files.size === 1 ? '' : 's'}`);
|
|
221
|
+
const byRule = new Map();
|
|
222
|
+
for (const f of findings) byRule.set(f.rule, (byRule.get(f.rule) ?? 0) + 1);
|
|
223
|
+
const colours = findings.filter((f) => f.rule === 'colour');
|
|
224
|
+
const parts = ['matches', 'close to', 'no token'].map((p) => `${colours.filter((f) => f.note.startsWith(p)).length} ${p === 'matches' ? 'match a token' : p}`);
|
|
225
|
+
for (const [rule, n] of [...byRule].sort((a, b) => b[1] - a[1])) out(` ${rule.padEnd(10)}${String(n).padStart(5)}${rule === 'colour' ? ` (${parts.join(', ')})` : ''}`);
|
|
226
|
+
for (const file of files) {
|
|
227
|
+
out(`\n${file}`);
|
|
228
|
+
for (const f of findings.filter((x) => x.file === file)) out(` ${`${f.line}:${f.col}`.padEnd(9)}${f.rule.padEnd(10)}${f.value} ${f.note}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function selftest() {
|
|
233
|
+
const dir = mkdtempSync(join(tmpdir(), 'brand-drift-'));
|
|
234
|
+
try {
|
|
235
|
+
mkdirSync(join(dir, 'components/ui'), { recursive: true });
|
|
236
|
+
mkdirSync(join(dir, 'node_modules/x'), { recursive: true });
|
|
237
|
+
writeFileSync(join(dir, 'node_modules/x/a.css'), 'a { color: #123456; }');
|
|
238
|
+
writeFileSync(join(dir, 'vendored.ts'), '/* Generated by scripts/fello-css.mjs */\nexport const css = `.ws-btn { color: #f00 }`;');
|
|
239
|
+
writeFileSync(join(dir, 'app.css'), [
|
|
240
|
+
'.a { color: #093555; }', // matches --color-brand-navy
|
|
241
|
+
'.b { color: #0b3757; }', // close to it
|
|
242
|
+
'.c { color: #12ab34; }', // no token
|
|
243
|
+
'.d { font-size: 13px; border-radius: 6px; box-shadow: 0 1px 2px rgba(0,0,0,.1); }',
|
|
244
|
+
'.e { transition: opacity 200ms ease; }',
|
|
245
|
+
'@keyframes spin { to { transform: rotate(1turn) } }',
|
|
246
|
+
':root { --ws-ink: red; --my-own: 1px; }',
|
|
247
|
+
'.ws-btn { padding: 0 }',
|
|
248
|
+
'.f { color: #abcdef; } /* brand-kit-ignore: a third-party embed */',
|
|
249
|
+
'.g { color: var(--ws-ink); transition: opacity var(--ws-duration-reveal); }',
|
|
250
|
+
].join('\n'));
|
|
251
|
+
writeFileSync(join(dir, 'page.tsx'), [
|
|
252
|
+
'export const P = () => <a href="#add" className="text-blue-500 underline hover:-translate-y-1 text-[var(--ws-ink)] duration-300">Hi — there</a>;',
|
|
253
|
+
'// a comment with — is fine',
|
|
254
|
+
'const ok = <div className="hover:underline text-[color:var(--ws-ink)]" />;',
|
|
255
|
+
'{/* a block comment that runs on',
|
|
256
|
+
' with a — and #ff0000 inside */}',
|
|
257
|
+
'const dotted = <span className="underline decoration-dotted" />;',
|
|
258
|
+
'const url = "https://example.com/a"; // #ff0000 in a line comment',
|
|
259
|
+
].join('\n'));
|
|
260
|
+
writeFileSync(join(dir, 'components/ui/button.tsx'), 'export const Button = () => <button className="px-4" />;');
|
|
261
|
+
writeFileSync(join(dir, 'components/ui/card.tsx'), 'export const Card = () => <div className="ws-card" />;');
|
|
262
|
+
const fs = scan(dir, loadTokens());
|
|
263
|
+
const n = (rule, pred = () => true) => fs.filter((f) => f.rule === rule && pred(f)).length;
|
|
264
|
+
const expect = [
|
|
265
|
+
['colour matches a token', n('colour', (f) => f.note === 'matches --color-brand-navy'), 1],
|
|
266
|
+
['colour close to a token', n('colour', (f) => f.note.startsWith('close to --color-brand-navy')), 1],
|
|
267
|
+
['colour with no token (hex, rgba)', n('colour', (f) => f.note === 'no token'), 2],
|
|
268
|
+
['Tailwind palette class', n('colour', (f) => f.value === 'text-blue-500'), 1],
|
|
269
|
+
['#add in an href is not a colour', n('colour', (f) => f.value === '#add'), 0],
|
|
270
|
+
['type', n('type'), 1],
|
|
271
|
+
['radius', n('radius'), 1],
|
|
272
|
+
['shadow', n('shadow'), 1],
|
|
273
|
+
['motion (raw duration, keyframes, duration-300, hover lift)', n('motion'), 4],
|
|
274
|
+
['a kit token redefined, an own token left alone', n('tokens', (f) => f.value === '--ws-ink'), 1],
|
|
275
|
+
['a kit class restyled', n('tokens', (f) => f.value === '.ws-btn'), 1],
|
|
276
|
+
['the trap', n('trap'), 1],
|
|
277
|
+
['underlined at rest, hover underline fine', n('underline'), 1],
|
|
278
|
+
['em dash in markup, not in a comment', n('words'), 1],
|
|
279
|
+
['a local kit component copy, not one that wears ws-', n('copy'), 1],
|
|
280
|
+
['node_modules, vendored kit and ignored lines skipped', fs.filter((f) => /node_modules|vendored/.test(f.file) || f.value === '#abcdef').length, 0],
|
|
281
|
+
];
|
|
282
|
+
let bad = 0;
|
|
283
|
+
for (const [what, got, want] of expect) {
|
|
284
|
+
const ok = got === want;
|
|
285
|
+
if (!ok) bad++;
|
|
286
|
+
console.log(`${ok ? 'ok ' : 'FAIL'} ${what}: ${got}${ok ? '' : ` (want ${want})`}`);
|
|
287
|
+
}
|
|
288
|
+
if (bad) { report(fs); process.exit(1); }
|
|
289
|
+
console.log('check-drift selftest: every planted drift caught.');
|
|
290
|
+
} finally {
|
|
291
|
+
rmSync(dir, { recursive: true, force: true });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ---- Run.
|
|
296
|
+
const args = process.argv.slice(2);
|
|
297
|
+
const flag = (name) => { const i = args.indexOf(name); return i < 0 ? null : (args.splice(i, 2)[1] ?? ''); };
|
|
298
|
+
if (args.includes('--selftest')) selftest();
|
|
299
|
+
else run();
|
|
300
|
+
|
|
301
|
+
function run() {
|
|
302
|
+
const json = args.includes('--json') && args.splice(args.indexOf('--json'), 1);
|
|
303
|
+
const writeBase = flag('--write-baseline');
|
|
304
|
+
const base = flag('--baseline');
|
|
305
|
+
const dir = args[0] ?? '.';
|
|
306
|
+
if (!statSync(dir, { throwIfNoEntry: false })?.isDirectory()) { console.error(`check-drift: ${dir} is not a folder`); process.exit(2); }
|
|
307
|
+
|
|
308
|
+
let findings = scan(dir, loadTokens());
|
|
309
|
+
if (writeBase) {
|
|
310
|
+
writeFileSync(writeBase, `${JSON.stringify({ version: 1, counts: Object.fromEntries([...countBy(findings)].sort()) }, null, 2)}\n`);
|
|
311
|
+
console.log(`check-drift: baseline of ${findings.length} findings written to ${writeBase}`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (base) {
|
|
315
|
+
const was = JSON.parse(readFileSync(base, 'utf8')).counts ?? {};
|
|
316
|
+
const now = countBy(findings);
|
|
317
|
+
const grew = new Set([...now].filter(([k, v]) => v > (was[k] ?? 0)).map(([k]) => k));
|
|
318
|
+
findings = findings.filter((f) => grew.has(key(f)));
|
|
319
|
+
if (!json) console.log(grew.size ? `check-drift: ${grew.size} file and rule pair${grew.size === 1 ? '' : 's'} drifted past ${base}` : `check-drift: nothing new past ${base}.`);
|
|
320
|
+
}
|
|
321
|
+
if (json) console.log(JSON.stringify(findings, null, 2));
|
|
322
|
+
else if (!base || findings.length) report(findings);
|
|
323
|
+
// exitCode, not exit(): a piped stdout is written after this line returns.
|
|
324
|
+
process.exitCode = findings.length ? 1 : 0;
|
|
325
|
+
}
|
package/scripts/token-model.mjs
CHANGED
|
@@ -303,7 +303,7 @@ export const GROUNDS = {
|
|
|
303
303
|
'--color-surface-stripe': 'stripe',
|
|
304
304
|
};
|
|
305
305
|
// Reported, not gated: grounds that carry only their own words (a PAIR below), or a frame under the pointer.
|
|
306
|
-
export const REPORT = { '--ws-tint-needs-work': 'needs-work tint', '--color-feedback-success-bg': 'success', '--ws-well-hover': 'well hover' };
|
|
306
|
+
export const REPORT = { '--ws-tint-needs-work': 'needs-work tint', '--ws-tint-alert': 'alert tint', '--color-feedback-success-bg': 'success', '--ws-well-hover': 'well hover' };
|
|
307
307
|
export const WORDS = [
|
|
308
308
|
'--color-text-heading', '--color-text-body', '--color-text-muted', '--color-text-interactive',
|
|
309
309
|
'--color-text-brand-strong', '--color-text-error-strong', '--color-text-success-strong',
|
|
@@ -312,6 +312,7 @@ export const WORDS = [
|
|
|
312
312
|
export const PAIRS = [
|
|
313
313
|
['--color-text-placeholder', '--ws-field'], ['--ws-field-ink', '--ws-field'], ['--ws-on-act', '--color-text-heading'],
|
|
314
314
|
['--ws-word-needs', '--ws-tint-needs'], ['--ws-word-positive', '--ws-tint-live'], ['--ws-word-needs-work', '--ws-tint-needs-work'],
|
|
315
|
+
['--ws-word-alert', '--ws-tint-alert'],
|
|
315
316
|
['--color-text-success-strong', '--color-feedback-success-bg'], ['--ws-on-act', '--ws-agent-tally'],
|
|
316
317
|
];
|
|
317
318
|
|
package/tokens/_dark.css
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
--ws-tint-accent: var(--color-surface);
|
|
36
36
|
--ws-tint-needs: color-mix(in srgb, var(--ws-chart-under-half) 13.05%, var(--color-bg-page)); /* #2E252A */
|
|
37
37
|
--ws-tint-live: color-mix(in srgb, var(--ws-chart-finished) 12.1%, var(--color-bg-page)); /* was #0E2931 */
|
|
38
|
+
--ws-tint-alert: color-mix(in srgb, var(--ws-chart-alert) 13.05%, var(--color-bg-page)); /* derived (#15), the night tint-needs formula */
|
|
38
39
|
--ws-card-tint-blue: color-mix(in srgb, var(--color-bg-page) 90%, #879bf4); /* #1B2637: the page toward the mid blue */
|
|
39
40
|
--ws-card-tint-done: color-mix(in srgb, var(--ws-chart-finished) 10.4%, var(--color-bg-page)); /* was #10262B */
|
|
40
41
|
--ws-track: color-mix(in srgb, var(--ws-tray) 80%, #0a1215); /* #1A2631: the tray, darker */
|
|
@@ -109,7 +110,6 @@
|
|
|
109
110
|
--ws-chart-alert: #FF5470; /* was #FF5A5A (gtm --color-alert dark), CIEDE2000 6.1 from salmon: a chart could not tell the two apart. A step toward crimson, 12.3 from salmon and 11.9 from the pink; words 4.63 on press (D15) */
|
|
110
111
|
--ws-word-needs-work: var(--ws-chart-needs-work); /* derived: Seth's pink itself clears AA on every night ground (5.26 on press) */
|
|
111
112
|
--ws-tint-needs-work: color-mix(in srgb, var(--ws-chart-needs-work) 18%, var(--color-bg-page)); /* derived, the tint-needs formula */
|
|
112
|
-
--ws-shimmer: linear-gradient(100deg, var(--ws-track) 0 34%, color-mix(in srgb, var(--ws-chart-under-half) 14%, var(--ws-track)) 44%, color-mix(in srgb, var(--ws-chart-past-half) 14%, var(--ws-track)) 50%, color-mix(in srgb, var(--color-stat-positive) 14%, var(--ws-track)) 56%, var(--ws-track) 66% 100%); /* derived: the chart colours at 14% over the dark track; still frames plain, as in light */
|
|
113
113
|
/* Promoted at integration (0.5.0): the packages' scoped dark values, now global. */
|
|
114
114
|
--ws-rating-middle: var(--color-text-interactive); /* the mid blue was 2.5:1 on night, under 3:1 for a mark; its words' own dark step */
|
|
115
115
|
--ws-raised-sunk: var(--ws-surface-press); /* the page's sunk is 1/255 off raised in dark */
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
--ws-agent-tally: var(--color-text-heading); /* the act itself in dark (WP-controls) */
|
|
120
120
|
--ws-mark-muted: color-mix(in srgb, var(--color-text-muted) 79.8%, var(--color-bg-page)); /* derived: the muted words toward the page, 3.3:1 on the tray, 4.0 on night, a mark (was #6B7A8A) */
|
|
121
121
|
--ws-control-off: color-mix(in srgb, var(--color-text-muted) 50%, #7a6a59); /* #7F7F7F, the off track as in light */
|
|
122
|
-
--ws-shimmer-ground
|
|
122
|
+
--ws-shimmer: linear-gradient(100deg in srgb, var(--ws-shimmer-ground) 0 34%, color-mix(in srgb, var(--ws-shimmer-ground) 93%, var(--ws-ink)) 50%, var(--ws-shimmer-ground) 66% 100%); /* derived (0.5.1): the ghost (the night's #273544, 1.42:1 on the page, 1.15:1 on the tray) with a lighter pass of 7% ink, #354250; on the navy ground #364351 */
|
|
123
123
|
--ws-inverse: var(--color-bg-dark); /* a brand pair in dark: the navy message keeps the navy and white (D12) */
|
|
124
124
|
--ws-on-inverse: var(--color-white);
|
|
125
125
|
--ws-chart-rest: var(--ws-edge-card);
|
|
@@ -144,6 +144,7 @@
|
|
|
144
144
|
--ws-tint-accent: color-mix(in srgb, var(--color-bg-page) 94%, var(--color-white));
|
|
145
145
|
--ws-tint-needs: color-mix(in srgb, var(--ws-chart-under-half) 18%, var(--color-bg-page));
|
|
146
146
|
--ws-tint-live: color-mix(in srgb, var(--ws-chart-finished) 13.1%, var(--color-bg-page)); /* was the light teal 18% (#032D41) */
|
|
147
|
+
--ws-tint-alert: color-mix(in srgb, var(--ws-chart-alert) 18%, var(--color-bg-page)); /* derived (#15), the navy tint-needs formula */
|
|
147
148
|
--ws-card-tint-blue: color-mix(in srgb, var(--color-brand-mid-blue) 25%, var(--color-bg-page));
|
|
148
149
|
--ws-card-tint-done: var(--ws-tint-live);
|
|
149
150
|
--ws-track: color-mix(in srgb, var(--color-bg-page) 88%, var(--color-white));
|