@orange-halo/tokens 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ {
2
+ "$description": "Token pairs that must meet WCAG contrast. Used by `pnpm check:contrast` and the Storybook Tokens page. Paths are token paths in dist/tokens.json. `over` composites a translucent background onto a solid one. `knownIssue` reports a failure as a warning until it's resolved.",
3
+ "pairs": [
4
+ { "fg": "color.text.primary", "bg": "color.surface.base", "min": 4.5, "use": "Body text on page" },
5
+ { "fg": "color.text.primary", "bg": "color.surface.raised", "min": 4.5, "use": "Body text on cards" },
6
+ { "fg": "color.text.secondary", "bg": "color.surface.base", "min": 4.5, "use": "Secondary text on page" },
7
+ { "fg": "color.text.secondary", "bg": "color.surface.inset", "min": 4.5, "use": "Placeholder / helper text in wells" },
8
+ { "fg": "color.text.primary", "bg": "color.surface.highlight", "min": 4.5, "use": "Text in a focused input" },
9
+ { "fg": "color.text.link", "bg": "color.surface.base", "min": 4.5, "use": "Links on page" },
10
+ { "fg": "color.text.linkRaised", "bg": "color.surface.raised", "min": 4.5, "use": "Links on raised surfaces (cards, skip link)" },
11
+ { "fg": "color.text.onBrand", "bg": "color.brand.primary", "min": 4.5, "use": "Primary button, hero text" },
12
+ { "fg": "color.text.onBrand", "bg": "color.brand.primaryHover", "min": 4.5, "use": "Primary button hover" },
13
+ { "fg": "color.text.onAccent", "bg": "color.accent.default", "min": 4.5, "use": "Accent button, footer text" },
14
+ { "fg": "color.text.onAccent", "bg": "color.accent.hover", "min": 4.5, "use": "Accent button hover" },
15
+ { "fg": "color.brand.primary", "bg": "color.brand.primarySubtle", "min": 4.5, "use": "Brand badge" },
16
+ { "fg": "color.accent.default", "bg": "color.accent.subtle", "min": 4.5, "use": "Accent badge" },
17
+ { "fg": "color.feedback.success", "bg": "color.feedback.successSubtle", "min": 4.5, "use": "Success badge / alert text" },
18
+ { "fg": "color.feedback.error", "bg": "color.feedback.errorSubtle", "min": 4.5, "use": "Error text on error background" },
19
+ { "fg": "color.feedback.error", "bg": "color.surface.base", "min": 4.5, "use": "Error messages" },
20
+ { "fg": "color.feedback.warning", "bg": "color.feedback.warningSubtle", "min": 4.5, "use": "Warning badge / alert text" },
21
+ { "fg": "color.feedback.info", "bg": "color.feedback.infoSubtle", "min": 4.5, "use": "Info badge / alert text" },
22
+ { "fg": "color.text.onSuccess", "bg": "color.feedback.success", "min": 4.5, "use": "Filled success badge" },
23
+ { "fg": "color.text.onError", "bg": "color.feedback.error", "min": 4.5, "use": "Filled error badge" },
24
+ { "fg": "color.text.onWarning", "bg": "color.feedback.warning", "min": 4.5, "use": "Filled warning badge" },
25
+ { "fg": "color.text.onInfo", "bg": "color.feedback.info", "min": 4.5, "use": "Filled info badge" },
26
+ { "fg": "color.border.emphasis", "bg": "color.surface.base", "min": 3, "use": "Control borders at rest" },
27
+ { "fg": "color.border.hover", "bg": "color.surface.base", "min": 3, "use": "Control borders on hover" },
28
+ { "fg": "focus.ringColor", "bg": "color.surface.highlight", "min": 3, "use": "Focused input border" },
29
+ { "fg": "focus.ringColor", "bg": "color.surface.base", "min": 3, "use": "Focus outline" },
30
+ { "fg": "color.brand.primary", "bg": "color.surface.base", "min": 3, "use": "Checked controls, link underlines" },
31
+ { "fg": "color.feedback.success", "bg": "color.surface.inset", "min": 3, "use": "Progress fill (success)" },
32
+ { "fg": "color.brand.primary", "bg": "color.surface.inset", "min": 3, "use": "Progress fill (brand)" },
33
+ { "fg": "progress.inverse.fill", "bg": "progress.inverse.trackBackground", "over": "color.brand.primary", "min": 3, "use": "Inverse progress fill in the hero" }
34
+ ]
35
+ }
package/contrast.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * WCAG 2.x contrast helpers, shared by scripts/check-contrast.mjs and the
3
+ * Storybook Tokens page. No dependencies.
4
+ */
5
+
6
+ /** Parse #rgb, #rrggbb, #rrggbbaa or rgb()/rgba() into [r, g, b, a]. */
7
+ export function parseColor(value) {
8
+ const v = String(value).trim().toLowerCase();
9
+ let m = v.match(/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/);
10
+ if (m) {
11
+ const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
12
+ const [r, g, b] = [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
13
+ const a = h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1;
14
+ return [r, g, b, a];
15
+ }
16
+ m = v.match(/^rgba?\(([^)]+)\)$/);
17
+ if (m) {
18
+ const [r, g, b, a = "1"] = m[1].split(/[\s,/]+/).filter(Boolean);
19
+ return [Number(r), Number(g), Number(b), Number(a)];
20
+ }
21
+ throw new Error(`Can't parse colour: ${value}`);
22
+ }
23
+
24
+ /** Alpha-composite a colour onto an opaque background. */
25
+ export function composite(fg, bg) {
26
+ const [r, g, b, a] = fg;
27
+ return [r * a + bg[0] * (1 - a), g * a + bg[1] * (1 - a), b * a + bg[2] * (1 - a), 1];
28
+ }
29
+
30
+ function luminance([r, g, b]) {
31
+ const lin = (c) => {
32
+ const s = c / 255;
33
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
34
+ };
35
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
36
+ }
37
+
38
+ /** Contrast ratio between two opaque colours. */
39
+ export function ratio(a, b) {
40
+ const [l1, l2] = [luminance(a), luminance(b)].sort((x, y) => y - x);
41
+ return (l1 + 0.05) / (l2 + 0.05);
42
+ }
43
+
44
+ /** Look up a dotted token path (e.g. "color.brand.primary") in dist/tokens.json. */
45
+ export function resolve(tokens, path) {
46
+ const value = path.split(".").reduce((node, key) => node?.[key], tokens);
47
+ if (typeof value !== "string") throw new Error(`Token not found: ${path}`);
48
+ return value;
49
+ }
50
+
51
+ /** Evaluate one pair from contrast-pairs.json. */
52
+ export function checkPair(tokens, pair) {
53
+ let bg = parseColor(resolve(tokens, pair.bg));
54
+ if (pair.over) bg = composite(bg, parseColor(resolve(tokens, pair.over)));
55
+ let fg = parseColor(resolve(tokens, pair.fg));
56
+ if (fg[3] < 1) fg = composite(fg, bg);
57
+ const value = ratio(fg, bg);
58
+ return { ...pair, ratio: Math.round(value * 100) / 100, pass: value >= pair.min };
59
+ }
@@ -1,127 +1,67 @@
1
1
  /**
2
- * Orange Halo Dark Theme
3
- * Auto-generated - do not edit directly
4
- * WCAG AA compliant color contrast ratios
2
+ * Do not edit directly, this file was auto-generated.
5
3
  */
6
4
 
7
5
  [data-theme='dark'] {
8
- /* Honey gold (5.2:1 on walnut) */
9
- --oh-color-brand-primary: #e8a838;
10
- --oh-color-brand-primary-hover: #f0b848;
11
- --oh-color-brand-primary-active: #d4922c;
12
- --oh-color-brand-primary-subtle: #3d3229;
13
- --oh-color-brand-primary-muted: #4a3d32;
14
- --oh-color-brand-highlight: #f0c860;
15
- --oh-color-brand-highlight-subtle: #3d3829;
16
- /* Light teal (4.8:1) */
17
- --oh-color-accent-default: #7aa3a4;
18
- --oh-color-accent-hover: #8fb4b5;
19
- --oh-color-accent-subtle: #2a3636;
20
- --oh-color-accent-muted: #344242;
21
- /* Light green (5.0:1) */
22
- --oh-color-feedback-success: #7ab87c;
23
- --oh-color-feedback-success-subtle: #2a3629;
24
- /* Light red (5.0:1 on base, 5.0:1 on raised) */
25
- --oh-color-feedback-error: #e08f8d;
26
- --oh-color-feedback-error-subtle: #3d2a29;
27
- /* Light amber (5.4:1) */
28
- --oh-color-feedback-warning: #e8b050;
29
- --oh-color-feedback-warning-subtle: #3d3529;
30
- --oh-color-feedback-info: #7aa3a4;
31
- --oh-color-feedback-info-subtle: #2a3636;
32
- /* Deep walnut */
33
- --oh-color-surface-base: #2a2420;
34
- /* Warm dark card */
35
- --oh-color-surface-raised: #3d3229;
36
- /* Slightly warmer than raised — use with gradient overlay */
37
- --oh-color-surface-sunlit: #44382c;
38
- /* Darker inset */
39
- --oh-color-surface-inset: #1f1a17;
40
- /* Flips to warm white in dark */
41
- --oh-color-surface-white: #f5f0e8;
42
6
  --oh-color-surface-overlay: rgba(15, 12, 10, 0.7);
43
- /* Distinct hover — lighter than raised */
44
- --oh-color-surface-hover: #4a3f33;
45
- --oh-color-surface-active-pressed: #352e28;
46
- --oh-color-surface-disabled: #352e28;
47
- /* 3.7:1 on base, 3.0:1 on raised — passes WCAG 1.4.11 */
48
- --oh-color-border-default: #8a7a68;
49
- /* Decorative dividers only — below 3:1, not for UI component boundaries */
50
- --oh-color-border-subtle: #6b5d4d;
51
- /* 4.9:1 on base, 4.0:1 on raised — inputs, secondary buttons */
52
- --oh-color-border-emphasis: #a09080;
53
- --oh-color-border-focus: #e8a838;
54
7
  --oh-color-border-brand: rgba(232, 168, 56, 0.3);
55
- /* Warm white (12.4:1) */
56
- --oh-color-text-primary: #f5f0e8;
57
- /* Dune (6.8:1) */
58
- --oh-color-text-secondary: #c9c1b4;
59
- --oh-color-text-disabled: #6b5d4d;
60
- --oh-color-text-inverse: #2a2420;
61
- --oh-color-text-link: #e8a838;
62
- --oh-color-text-link-hover: #f0b848;
63
- /* Dark text on light brand bg (7.5:1) */
64
- --oh-color-text-on-brand: #2a2420;
65
- /* Dark text on light accent bg (5.8:1) */
66
- --oh-color-text-on-accent: #2a2420;
67
- --oh-focus-ring-color: rgba(232, 168, 56, 0.4);
8
+ --oh-elevation-soft: 0 2px 8px rgba(0, 0, 0, 0.25);
9
+ --oh-elevation-medium: 0 4px 16px rgba(0, 0, 0, 0.3);
10
+ --oh-elevation-strong: 0 8px 24px rgba(0, 0, 0, 0.4);
11
+ --oh-elevation-inset: inset 0 1px 3px rgba(0, 0, 0, 0.3);
68
12
  --oh-focus-glow-color: rgba(232, 168, 56, 0.2);
69
- --oh-shadow-inset: inset 0 1px 3px rgba(0, 0, 0, 0.3);
70
- --oh-shadow-soft: 0 2px 8px rgba(0, 0, 0, 0.25);
71
- --oh-shadow-medium: 0 4px 16px rgba(0, 0, 0, 0.3);
72
- /* Chunky input shadow */
73
- --oh-shadow-chunky-inset: inset 3px 3px 0 0 rgba(0, 0, 0, 0.2);
74
- }
75
-
76
- /* Automatic dark mode based on system preference */
77
- @media (prefers-color-scheme: dark) {
78
- :root:not([data-theme='light']) {
79
- --oh-color-brand-primary: #e8a838;
80
- --oh-color-brand-primary-hover: #f0b848;
81
- --oh-color-brand-primary-active: #d4922c;
82
- --oh-color-brand-primary-subtle: #3d3229;
83
- --oh-color-brand-primary-muted: #4a3d32;
84
- --oh-color-brand-highlight: #f0c860;
85
- --oh-color-brand-highlight-subtle: #3d3829;
86
- --oh-color-accent-default: #7aa3a4;
87
- --oh-color-accent-hover: #8fb4b5;
88
- --oh-color-accent-subtle: #2a3636;
89
- --oh-color-accent-muted: #344242;
90
- --oh-color-feedback-success: #7ab87c;
91
- --oh-color-feedback-success-subtle: #2a3629;
92
- --oh-color-feedback-error: #e08f8d;
93
- --oh-color-feedback-error-subtle: #3d2a29;
94
- --oh-color-feedback-warning: #e8b050;
95
- --oh-color-feedback-warning-subtle: #3d3529;
96
- --oh-color-feedback-info: #7aa3a4;
97
- --oh-color-feedback-info-subtle: #2a3636;
98
- --oh-color-surface-base: #2a2420;
99
- --oh-color-surface-raised: #3d3229;
100
- --oh-color-surface-sunlit: #44382c;
101
- --oh-color-surface-inset: #1f1a17;
102
- --oh-color-surface-white: #f5f0e8;
103
- --oh-color-surface-overlay: rgba(15, 12, 10, 0.7);
104
- --oh-color-surface-hover: #4a3f33;
105
- --oh-color-surface-active-pressed: #352e28;
106
- --oh-color-surface-disabled: #352e28;
107
- --oh-color-border-default: #8a7a68;
108
- --oh-color-border-subtle: #6b5d4d;
109
- --oh-color-border-emphasis: #a09080;
110
- --oh-color-border-focus: #e8a838;
111
- --oh-color-border-brand: rgba(232, 168, 56, 0.3);
112
- --oh-color-text-primary: #f5f0e8;
113
- --oh-color-text-secondary: #c9c1b4;
114
- --oh-color-text-disabled: #6b5d4d;
115
- --oh-color-text-inverse: #2a2420;
116
- --oh-color-text-link: #e8a838;
117
- --oh-color-text-link-hover: #f0b848;
118
- --oh-color-text-on-brand: #2a2420;
119
- --oh-color-text-on-accent: #2a2420;
120
- --oh-focus-ring-color: rgba(232, 168, 56, 0.4);
121
- --oh-focus-glow-color: rgba(232, 168, 56, 0.2);
122
- --oh-shadow-inset: inset 0 1px 3px rgba(0, 0, 0, 0.3);
123
- --oh-shadow-soft: 0 2px 8px rgba(0, 0, 0, 0.25);
124
- --oh-shadow-medium: 0 4px 16px rgba(0, 0, 0, 0.3);
125
- --oh-shadow-chunky-inset: inset 3px 3px 0 0 rgba(0, 0, 0, 0.2);
126
- }
13
+ --oh-color-brand-primary: var(--oh-color-honey-400);
14
+ --oh-color-brand-primary-hover: var(--oh-color-honey-300);
15
+ --oh-color-brand-primary-active: var(--oh-color-honey-500);
16
+ --oh-color-brand-primary-subtle: var(--oh-color-warm-walnut);
17
+ --oh-color-brand-highlight: var(--oh-color-honey-300);
18
+ --oh-color-brand-highlight-subtle: var(--oh-color-honey-900);
19
+ --oh-color-accent-default: var(--oh-color-blue-200);
20
+ --oh-color-accent-hover: var(--oh-color-blue-100);
21
+ --oh-color-accent-active: var(--oh-color-blue-300);
22
+ --oh-color-accent-subtle: var(--oh-color-blue-800);
23
+ --oh-color-accent-shadow: var(--oh-color-blue-800);
24
+ --oh-color-feedback-success: var(--oh-color-green-300);
25
+ --oh-color-feedback-success-subtle: var(--oh-color-green-900);
26
+ --oh-color-feedback-error: var(--oh-color-red-300);
27
+ --oh-color-feedback-error-hover: var(--oh-color-red-100);
28
+ --oh-color-feedback-error-subtle: var(--oh-color-red-900);
29
+ --oh-color-feedback-error-shadow: var(--oh-color-red-900);
30
+ --oh-color-feedback-warning: var(--oh-color-honey-300);
31
+ --oh-color-feedback-warning-subtle: var(--oh-color-honey-900);
32
+ --oh-color-feedback-info: var(--oh-color-blue-200);
33
+ --oh-color-feedback-info-subtle: var(--oh-color-blue-800);
34
+ --oh-color-surface-base: var(--oh-color-warm-cocoa);
35
+ --oh-color-surface-raised: var(--oh-color-warm-walnut);
36
+ --oh-color-surface-sunlit: var(--oh-color-warm-chestnut);
37
+ --oh-color-surface-inset: var(--oh-color-warm-espresso);
38
+ --oh-color-surface-highlight: var(--oh-color-warm-hickory); /** Lighter than raised; text.primary stays readable on it */
39
+ --oh-color-surface-disabled: var(--oh-color-warm-mocha);
40
+ --oh-color-surface-hover: var(--oh-color-warm-hickory);
41
+ --oh-color-surface-active-pressed: var(--oh-color-warm-mocha);
42
+ --oh-color-border-default: var(--oh-color-warm-taupe);
43
+ --oh-color-border-subtle: var(--oh-color-warm-driftwood); /** Decorative dividers only — below 3:1 */
44
+ --oh-color-border-emphasis: var(--oh-color-warm-stone);
45
+ --oh-color-border-strong: var(--oh-color-warm-stone);
46
+ --oh-color-border-hover: var(--oh-color-warm-dune);
47
+ --oh-color-border-disabled: var(--oh-color-warm-hickory);
48
+ --oh-color-text-primary: var(--oh-color-warm-linen);
49
+ --oh-color-text-secondary: var(--oh-color-warm-dune);
50
+ --oh-color-text-disabled: var(--oh-color-warm-driftwood);
51
+ --oh-color-text-inverse: var(--oh-color-warm-cocoa);
52
+ --oh-color-text-link: var(--oh-color-honey-400);
53
+ --oh-color-text-link-hover: var(--oh-color-honey-300);
54
+ --oh-color-text-link-raised: var(--oh-color-honey-400);
55
+ --oh-color-text-brand: var(--oh-color-honey-400);
56
+ --oh-color-text-on-brand: var(--oh-color-warm-cocoa);
57
+ --oh-color-text-on-accent: var(--oh-color-warm-cocoa);
58
+ --oh-color-text-on-success: var(--oh-color-warm-cocoa);
59
+ --oh-color-text-on-error: var(--oh-color-warm-cocoa);
60
+ --oh-color-text-on-warning: var(--oh-color-warm-cocoa);
61
+ --oh-color-text-on-info: var(--oh-color-warm-cocoa);
62
+ --oh-shadow-primary: var(--oh-color-honey-800);
63
+ --oh-shadow-secondary: var(--oh-color-warm-espresso);
64
+ --oh-shadow-accent: var(--oh-color-blue-800);
65
+ --oh-shadow-error: var(--oh-color-red-900);
66
+ --oh-focus-ring-color: var(--oh-color-honey-400); /** Opaque, as in light */
127
67
  }