@ids-group-ltd/ids-design-system 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +32 -10
- package/fesm2022/ids-group-ltd-ids-design-system.mjs +241 -240
- package/fesm2022/ids-group-ltd-ids-design-system.mjs.map +1 -1
- package/package.json +2 -2
- package/styles/_dropdown-overlay.scss +117 -117
- package/styles/_fonts.scss +59 -59
- package/styles/_icon-base.scss +9 -9
- package/styles/_link.scss +77 -77
- package/styles/_reset.scss +17 -17
- package/styles/_scrollbar.scss +17 -17
- package/styles/_toast-overlay.scss +6 -6
- package/styles/_tokens-charts.scss +70 -70
- package/styles/_tokens.scss +614 -614
- package/styles/ds.scss +27 -27
- package/themes/README.md +96 -96
- package/themes/default/_palette.scss +159 -159
- package/themes/default/_theme.scss +274 -274
|
@@ -1,274 +1,274 @@
|
|
|
1
|
-
// =========================================================================
|
|
2
|
-
// Default theme — Tier 2 semantic role ASSIGNMENTS (light).
|
|
3
|
-
//
|
|
4
|
-
// The palette (themes/default/_palette.scss) DEFINES the concrete colours
|
|
5
|
-
// (--blue-*, --red-*, --neutral-*, … + HSL channels). THIS file SETS them
|
|
6
|
-
// onto semantic roles (--primary → --blue-600, --surface-canvas → --neutral-50,
|
|
7
|
-
// --error → --red-500, …). Components consume only these roles, never the
|
|
8
|
-
// palette directly — so swapping the palette, or re-mapping here, re-skins the
|
|
9
|
-
// whole system. A sibling theme (e.g. dark) ships its own palette + this map.
|
|
10
|
-
//
|
|
11
|
-
// Tier 3 (component-scoped tokens, geometry, typography, motion) lives in
|
|
12
|
-
// styles/_tokens.scss and is theme-independent.
|
|
13
|
-
// =========================================================================
|
|
14
|
-
|
|
15
|
-
:root {
|
|
16
|
-
/* Neutral family slot — semantic pointer at the active gray ramp from the
|
|
17
|
-
palette. Default: hand-drawn cool-gray. <html data-neutrals="brand">
|
|
18
|
-
(ds-docs tweaks panel) repoints it at the brand-derived brand-gray ramp,
|
|
19
|
-
where --primary-h re-tints every gray. Roles below consume --neutral-*
|
|
20
|
-
so the family switch never touches them. */
|
|
21
|
-
--neutral-0: var(--cool-gray-0);
|
|
22
|
-
--neutral-50: var(--cool-gray-50);
|
|
23
|
-
--neutral-100: var(--cool-gray-100);
|
|
24
|
-
--neutral-150: var(--cool-gray-150);
|
|
25
|
-
--neutral-200: var(--cool-gray-200);
|
|
26
|
-
--neutral-300: var(--cool-gray-300);
|
|
27
|
-
--neutral-400: var(--cool-gray-400);
|
|
28
|
-
--neutral-500: var(--cool-gray-500);
|
|
29
|
-
--neutral-600: var(--cool-gray-600);
|
|
30
|
-
--neutral-700: var(--cool-gray-700);
|
|
31
|
-
--neutral-800: var(--cool-gray-800);
|
|
32
|
-
--neutral-900: var(--cool-gray-900);
|
|
33
|
-
--neutral-950: var(--cool-gray-950);
|
|
34
|
-
|
|
35
|
-
/* Surfaces — six-stop scale.
|
|
36
|
-
· canvas : page background
|
|
37
|
-
· default : cards, sheets, toolbars (the “paper”)
|
|
38
|
-
· secondary : hover lanes, table heads, addons, code blocks
|
|
39
|
-
· overlay : modals, drawers, popovers (paired with scrim)
|
|
40
|
-
· tint : brand-tinted (active rows, sidenav active item)
|
|
41
|
-
· inverted : dark-on-light callouts, toasts
|
|
42
|
-
Conceptual elevation is communicated by SHADOW tokens,
|
|
43
|
-
not by surface tokens. Hover/pressed states use
|
|
44
|
-
--opacity-state-* overlays on top of the underlying surface. */
|
|
45
|
-
--surface-canvas: var(--neutral-50);
|
|
46
|
-
--surface-default: var(--neutral-0);
|
|
47
|
-
--surface-secondary: var(--neutral-100);
|
|
48
|
-
--surface-overlay: var(--neutral-0);
|
|
49
|
-
--surface-inverted: var(--neutral-900);
|
|
50
|
-
--surface-inverted-hover: var(--neutral-800);
|
|
51
|
-
--surface-tint: var(--blue-50);
|
|
52
|
-
--surface-disabled: var(--neutral-100);
|
|
53
|
-
--surface-muted: var(--neutral-150);
|
|
54
|
-
|
|
55
|
-
/* Scrim — modal/drawer backdrop. Derived from inverted surface
|
|
56
|
-
so dark-mode override flips automatically. */
|
|
57
|
-
--surface-scrim: hsla(var(--shadow-tint-h), var(--shadow-tint-s), var(--shadow-tint-l), .55);
|
|
58
|
-
|
|
59
|
-
/* Visible-on-coloured-fill divider. The split-button right-hand chevron
|
|
60
|
-
sits ON the brand fill, so a transparent white hairline reads correctly
|
|
61
|
-
across any brand hue. Override per theme if the brand is too light. */
|
|
62
|
-
--btn-split-divider: hsla(0, 0%, 100%, .25);
|
|
63
|
-
|
|
64
|
-
/* Visited link — derives from --primary-muted by default so it sympathetically
|
|
65
|
-
shifts with the brand hue. Override (e.g. for financial dashboards that
|
|
66
|
-
want neutral-grey visited links) by setting this token directly. */
|
|
67
|
-
--link-visited: hsl(var(--primary-h) calc(var(--primary-s) * 0.6) 45%);
|
|
68
|
-
|
|
69
|
-
/* Borders */
|
|
70
|
-
--border-default: var(--neutral-200);
|
|
71
|
-
--border-strong: var(--neutral-300);
|
|
72
|
-
--border-
|
|
73
|
-
--border-divider: var(--neutral-150);
|
|
74
|
-
--border-inverted: var(--neutral-700);
|
|
75
|
-
--border-disabled: var(--neutral-150);
|
|
76
|
-
|
|
77
|
-
/* Text */
|
|
78
|
-
--text-primary: var(--neutral-900);
|
|
79
|
-
--text-secondary: var(--neutral-700);
|
|
80
|
-
--text-tertiary: var(--neutral-500);
|
|
81
|
-
--text-muted: var(--neutral-400);
|
|
82
|
-
--text-disabled: var(--neutral-400);
|
|
83
|
-
--text-inverted: var(--neutral-0);
|
|
84
|
-
/* Text overlaid on arbitrary imagery (camera feeds, photos) — pairs a
|
|
85
|
-
guaranteed-light colour with a contrast shadow. */
|
|
86
|
-
--text-on-media: var(--neutral-0);
|
|
87
|
-
--text-on-media-shadow: 0 1px 3px hsla(0, 0%, 0%, .7);
|
|
88
|
-
--text-link: var(--blue-700);
|
|
89
|
-
--text-on-primary: var(--neutral-0);
|
|
90
|
-
--text-on-success: var(--neutral-0);
|
|
91
|
-
/* Warning fill is yellow (--yellow-400 #F4D43A) — white text on it is only
|
|
92
|
-
~1.5:1 (fails WCAG). On-warning text is the darkest neutral (~10:1). */
|
|
93
|
-
--text-on-warning: var(--neutral-900);
|
|
94
|
-
--text-on-error: var(--neutral-0);
|
|
95
|
-
|
|
96
|
-
/* Icons */
|
|
97
|
-
--icon-default: var(--neutral-500);
|
|
98
|
-
--icon-strong: var(--neutral-700);
|
|
99
|
-
--icon-muted: var(--neutral-400);
|
|
100
|
-
--icon-on-primary: var(--neutral-0);
|
|
101
|
-
|
|
102
|
-
/* Font families — a theme/brand choice (the size/weight/leading SCALE is
|
|
103
|
-
theme-independent and lives in styles/_tokens.scss). Display + heading
|
|
104
|
-
default to body sans; a brand can mix a serif/display family here without
|
|
105
|
-
touching component code. */
|
|
106
|
-
--font-sans: "Mulish", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
107
|
-
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
108
|
-
--font-display: var(--font-sans);
|
|
109
|
-
--font-heading: var(--font-sans);
|
|
110
|
-
|
|
111
|
-
/* Semantic → palette bridge. The lib knows colours by PURPOSE; the theme
|
|
112
|
-
(themes/default/_theme.scss) ships the concrete ramps + HSL channels. This
|
|
113
|
-
is the one place that maps semantic roles onto a concrete palette. A brand
|
|
114
|
-
re-skins by overriding either the palette (--blue-*, --red-*) or these
|
|
115
|
-
role tokens directly.
|
|
116
|
-
Channel mappings let hsla() halos/tints compose from the same hue source. */
|
|
117
|
-
--primary-h: var(--blue-h);
|
|
118
|
-
--primary-s: var(--blue-s);
|
|
119
|
-
--primary-l: var(--blue-l);
|
|
120
|
-
--error-h: var(--red-h);
|
|
121
|
-
--error-s: var(--red-s);
|
|
122
|
-
--error-l: var(--red-l);
|
|
123
|
-
|
|
124
|
-
/* Primary roles.
|
|
125
|
-
Hover / pressed / selected-hover are DERIVED from --primary at runtime via
|
|
126
|
-
color-mix() so a brand swap (overriding only --primary) propagates to every
|
|
127
|
-
interaction state automatically. A brand that wants a non-derived hover
|
|
128
|
-
hue can still override these tokens directly.
|
|
129
|
-
Step sizes (12% / 24% darker) mirror the blue 600→700→800 ramp. */
|
|
130
|
-
--primary: var(--blue-600);
|
|
131
|
-
--primary-hover: color-mix(in srgb, var(--primary) 88%, black);
|
|
132
|
-
--primary-pressed: color-mix(in srgb, var(--primary) 76%, black);
|
|
133
|
-
--primary-selected-hover: color-mix(in srgb, var(--primary) 88%, black);
|
|
134
|
-
--primary-
|
|
135
|
-
--primary-muted: var(--blue-100);
|
|
136
|
-
--primary-muted-strong: var(--blue-200);
|
|
137
|
-
--primary-strong: var(--blue-700);
|
|
138
|
-
--primary-on: var(--neutral-0);
|
|
139
|
-
|
|
140
|
-
/* Accent / secondary role — composed from --secondary-h/s/l channels.
|
|
141
|
-
Channels default to brand values so unbranded apps render identical to
|
|
142
|
-
--primary. Override --secondary-h/s/l per-brand (or via the tweaks panel)
|
|
143
|
-
to introduce a true secondary hue — --secondary recomposes automatically.
|
|
144
|
-
Hover/pressed derive from --secondary so any hue change propagates. */
|
|
145
|
-
--secondary-h: var(--primary-h);
|
|
146
|
-
--secondary-s: var(--primary-s);
|
|
147
|
-
--secondary-l: var(--primary-l);
|
|
148
|
-
|
|
149
|
-
--secondary: hsl(var(--secondary-h), var(--secondary-s), var(--secondary-l));
|
|
150
|
-
--secondary-hover: color-mix(in srgb, var(--secondary) 88%, black);
|
|
151
|
-
--secondary-pressed: color-mix(in srgb, var(--secondary) 76%, black);
|
|
152
|
-
--secondary-strong: hsl(var(--secondary-h), var(--secondary-s), calc(var(--secondary-l) - 11%));
|
|
153
|
-
--secondary-
|
|
154
|
-
--secondary-muted: hsl(var(--secondary-h), 95%, 94%);
|
|
155
|
-
--secondary-on: var(--primary-on);
|
|
156
|
-
|
|
157
|
-
/* Tertiary — third brand hue. Used by the tertiary button variant and
|
|
158
|
-
tonal accents (decorative chips, hierarchy tones, indicators). Mirrors
|
|
159
|
-
the --primary / --secondary token-family shape: HSL channels for halo
|
|
160
|
-
composition, hover/pressed via color-mix, subtle/muted ramps for tonal
|
|
161
|
-
surfaces. Defaults to --primary channels so single-hue themes render
|
|
162
|
-
identical to primary. Multi-hue brands override --tertiary-h/s/l (or
|
|
163
|
-
--tertiary directly for non-HSL values). */
|
|
164
|
-
--tertiary-h: var(--primary-h);
|
|
165
|
-
--tertiary-s: var(--primary-s);
|
|
166
|
-
--tertiary-l: var(--primary-l);
|
|
167
|
-
|
|
168
|
-
--tertiary: hsl(var(--tertiary-h), var(--tertiary-s), var(--tertiary-l));
|
|
169
|
-
--tertiary-hover: color-mix(in srgb, var(--tertiary) 88%, black);
|
|
170
|
-
--tertiary-pressed: color-mix(in srgb, var(--tertiary) 76%, black);
|
|
171
|
-
--tertiary-strong: hsl(var(--tertiary-h), var(--tertiary-s), calc(var(--tertiary-l) - 11%));
|
|
172
|
-
--tertiary-
|
|
173
|
-
--tertiary-muted: hsl(var(--tertiary-h), 95%, 94%);
|
|
174
|
-
--tertiary-on: var(--primary-on);
|
|
175
|
-
|
|
176
|
-
/* Status roles.
|
|
177
|
-
-hover / -pressed are derived from the base role via the same color-mix()
|
|
178
|
-
rule as --primary-hover. Override per role if a brand needs a non-derived
|
|
179
|
-
hue. -text / -border / -subtle / -muted stay as ramp picks because their
|
|
180
|
-
contrast targets are tuned, not procedural. */
|
|
181
|
-
--success: var(--green-500);
|
|
182
|
-
--success-hover: color-mix(in srgb, var(--success) 88%, black);
|
|
183
|
-
--success-
|
|
184
|
-
--success-muted: var(--green-100);
|
|
185
|
-
--success-border: var(--green-200);
|
|
186
|
-
--success-text: var(--green-700);
|
|
187
|
-
|
|
188
|
-
--warning: var(--yellow-400);
|
|
189
|
-
--warning-hover: color-mix(in srgb, var(--warning) 88%, black);
|
|
190
|
-
--warning-
|
|
191
|
-
--warning-muted: var(--yellow-100);
|
|
192
|
-
--warning-border: var(--yellow-200);
|
|
193
|
-
--warning-text: var(--yellow-700);
|
|
194
|
-
|
|
195
|
-
--error: var(--red-500);
|
|
196
|
-
--error-hover: color-mix(in srgb, var(--error) 88%, black);
|
|
197
|
-
--error-pressed: color-mix(in srgb, var(--error) 76%, black);
|
|
198
|
-
--error-
|
|
199
|
-
--error-muted: var(--red-100);
|
|
200
|
-
--error-border: var(--red-200);
|
|
201
|
-
--error-text: var(--red-600);
|
|
202
|
-
|
|
203
|
-
--info: var(--sky-500);
|
|
204
|
-
--info-hover: color-mix(in srgb, var(--info) 88%, black);
|
|
205
|
-
--info-
|
|
206
|
-
--info-muted: var(--sky-100);
|
|
207
|
-
--info-border: var(--sky-200);
|
|
208
|
-
--info-text: var(--sky-700);
|
|
209
|
-
|
|
210
|
-
/* Status pills (event lifecycle).
|
|
211
|
-
DECISION: --status-confirmed-* chains through --primary-* so a
|
|
212
|
-
brand re-skin moves Confirmed with it. The other lifecycle
|
|
213
|
-
stages are deliberately neutral/green/dark to keep semantic
|
|
214
|
-
distance from brand. */
|
|
215
|
-
/* Status pills — canonical (single system).
|
|
216
|
-
Legacy classes `.badge--active` (single) and ad-hoc semantic
|
|
217
|
-
pills are unified in the Badge component to consume these tokens. */
|
|
218
|
-
--status-planning-bg: var(--neutral-150);
|
|
219
|
-
--status-planning-fg: var(--neutral-700);
|
|
220
|
-
--status-confirmed-bg: var(--primary-
|
|
221
|
-
--status-confirmed-fg: var(--primary-strong);
|
|
222
|
-
--status-active-bg: var(--green-50);
|
|
223
|
-
--status-active-fg: var(--green-700);
|
|
224
|
-
--status-completed-bg: var(--neutral-900);
|
|
225
|
-
--status-completed-fg: var(--neutral-0);
|
|
226
|
-
|
|
227
|
-
/* Focus rings.
|
|
228
|
-
Outer halos use hsla() composed from brand/danger HSL channels
|
|
229
|
-
so re-skinning --primary-h propagates into focus state.
|
|
230
|
-
The inner halo reads --surface-canvas so the ring stays visible
|
|
231
|
-
in both light and dark themes (canvas flips, ring adapts). */
|
|
232
|
-
--focus-ring-inner: var(--surface-canvas);
|
|
233
|
-
--focus-ring-outer-h: var(--primary-h);
|
|
234
|
-
--focus-ring-outer-s: var(--primary-s);
|
|
235
|
-
--focus-ring-outer-l: var(--primary-l);
|
|
236
|
-
--focus-ring-outer-alpha: 1;
|
|
237
|
-
|
|
238
|
-
--focus-ring:
|
|
239
|
-
0 0 0 2px var(--focus-ring-inner),
|
|
240
|
-
0 0 0 4px hsla(var(--focus-ring-outer-h), var(--focus-ring-outer-s), var(--focus-ring-outer-l), var(--focus-ring-outer-alpha));
|
|
241
|
-
--focus-ring-inverted:
|
|
242
|
-
0 0 0 2px var(--neutral-900),
|
|
243
|
-
0 0 0 4px var(--blue-300);
|
|
244
|
-
--focus-ring-error:
|
|
245
|
-
0 0 0 2px var(--focus-ring-inner),
|
|
246
|
-
0 0 0 4px hsla(var(--error-h), var(--error-s), var(--error-l), 1);
|
|
247
|
-
|
|
248
|
-
/* Field focus — coloured halo only (no double-ring). Derived from
|
|
249
|
-
brand HSL so it tracks rebrand. Alpha is a token so dark themes
|
|
250
|
-
can boost it for legibility. */
|
|
251
|
-
--focus-field-alpha: .22;
|
|
252
|
-
--focus-field:
|
|
253
|
-
0 0 0 3px hsla(var(--primary-h), var(--primary-s), var(--primary-l), var(--focus-field-alpha));
|
|
254
|
-
--focus-field-error:
|
|
255
|
-
0 0 0 3px hsla(var(--error-h), var(--error-s), var(--error-l), var(--focus-field-alpha));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/* Brand-derived neutrals — opt-in family switch (ds-docs tweaks panel sets
|
|
259
|
-
the attribute). Repoints the --neutral-* slot at the brand-gray ramp. */
|
|
260
|
-
:root[data-neutrals='brand'] {
|
|
261
|
-
--neutral-0: var(--brand-gray-0);
|
|
262
|
-
--neutral-50: var(--brand-gray-50);
|
|
263
|
-
--neutral-100: var(--brand-gray-100);
|
|
264
|
-
--neutral-150: var(--brand-gray-150);
|
|
265
|
-
--neutral-200: var(--brand-gray-200);
|
|
266
|
-
--neutral-300: var(--brand-gray-300);
|
|
267
|
-
--neutral-400: var(--brand-gray-400);
|
|
268
|
-
--neutral-500: var(--brand-gray-500);
|
|
269
|
-
--neutral-600: var(--brand-gray-600);
|
|
270
|
-
--neutral-700: var(--brand-gray-700);
|
|
271
|
-
--neutral-800: var(--brand-gray-800);
|
|
272
|
-
--neutral-900: var(--brand-gray-900);
|
|
273
|
-
--neutral-950: var(--brand-gray-950);
|
|
274
|
-
}
|
|
1
|
+
// =========================================================================
|
|
2
|
+
// Default theme — Tier 2 semantic role ASSIGNMENTS (light).
|
|
3
|
+
//
|
|
4
|
+
// The palette (themes/default/_palette.scss) DEFINES the concrete colours
|
|
5
|
+
// (--blue-*, --red-*, --neutral-*, … + HSL channels). THIS file SETS them
|
|
6
|
+
// onto semantic roles (--primary → --blue-600, --surface-canvas → --neutral-50,
|
|
7
|
+
// --error → --red-500, …). Components consume only these roles, never the
|
|
8
|
+
// palette directly — so swapping the palette, or re-mapping here, re-skins the
|
|
9
|
+
// whole system. A sibling theme (e.g. dark) ships its own palette + this map.
|
|
10
|
+
//
|
|
11
|
+
// Tier 3 (component-scoped tokens, geometry, typography, motion) lives in
|
|
12
|
+
// styles/_tokens.scss and is theme-independent.
|
|
13
|
+
// =========================================================================
|
|
14
|
+
|
|
15
|
+
:root {
|
|
16
|
+
/* Neutral family slot — semantic pointer at the active gray ramp from the
|
|
17
|
+
palette. Default: hand-drawn cool-gray. <html data-neutrals="brand">
|
|
18
|
+
(ds-docs tweaks panel) repoints it at the brand-derived brand-gray ramp,
|
|
19
|
+
where --primary-h re-tints every gray. Roles below consume --neutral-*
|
|
20
|
+
so the family switch never touches them. */
|
|
21
|
+
--neutral-0: var(--cool-gray-0);
|
|
22
|
+
--neutral-50: var(--cool-gray-50);
|
|
23
|
+
--neutral-100: var(--cool-gray-100);
|
|
24
|
+
--neutral-150: var(--cool-gray-150);
|
|
25
|
+
--neutral-200: var(--cool-gray-200);
|
|
26
|
+
--neutral-300: var(--cool-gray-300);
|
|
27
|
+
--neutral-400: var(--cool-gray-400);
|
|
28
|
+
--neutral-500: var(--cool-gray-500);
|
|
29
|
+
--neutral-600: var(--cool-gray-600);
|
|
30
|
+
--neutral-700: var(--cool-gray-700);
|
|
31
|
+
--neutral-800: var(--cool-gray-800);
|
|
32
|
+
--neutral-900: var(--cool-gray-900);
|
|
33
|
+
--neutral-950: var(--cool-gray-950);
|
|
34
|
+
|
|
35
|
+
/* Surfaces — six-stop scale.
|
|
36
|
+
· canvas : page background
|
|
37
|
+
· default : cards, sheets, toolbars (the “paper”)
|
|
38
|
+
· secondary : hover lanes, table heads, addons, code blocks
|
|
39
|
+
· overlay : modals, drawers, popovers (paired with scrim)
|
|
40
|
+
· tint : brand-tinted (active rows, sidenav active item)
|
|
41
|
+
· inverted : dark-on-light callouts, toasts
|
|
42
|
+
Conceptual elevation is communicated by SHADOW tokens,
|
|
43
|
+
not by surface tokens. Hover/pressed states use
|
|
44
|
+
--opacity-state-* overlays on top of the underlying surface. */
|
|
45
|
+
--surface-canvas: var(--neutral-50);
|
|
46
|
+
--surface-default: var(--neutral-0);
|
|
47
|
+
--surface-secondary: var(--neutral-100);
|
|
48
|
+
--surface-overlay: var(--neutral-0);
|
|
49
|
+
--surface-inverted: var(--neutral-900);
|
|
50
|
+
--surface-inverted-hover: var(--neutral-800);
|
|
51
|
+
--surface-tint: var(--blue-50);
|
|
52
|
+
--surface-disabled: var(--neutral-100);
|
|
53
|
+
--surface-muted: var(--neutral-150);
|
|
54
|
+
|
|
55
|
+
/* Scrim — modal/drawer backdrop. Derived from inverted surface
|
|
56
|
+
so dark-mode override flips automatically. */
|
|
57
|
+
--surface-scrim: hsla(var(--shadow-tint-h), var(--shadow-tint-s), var(--shadow-tint-l), .55);
|
|
58
|
+
|
|
59
|
+
/* Visible-on-coloured-fill divider. The split-button right-hand chevron
|
|
60
|
+
sits ON the brand fill, so a transparent white hairline reads correctly
|
|
61
|
+
across any brand hue. Override per theme if the brand is too light. */
|
|
62
|
+
--btn-split-divider: hsla(0, 0%, 100%, .25);
|
|
63
|
+
|
|
64
|
+
/* Visited link — derives from --primary-muted by default so it sympathetically
|
|
65
|
+
shifts with the brand hue. Override (e.g. for financial dashboards that
|
|
66
|
+
want neutral-grey visited links) by setting this token directly. */
|
|
67
|
+
--link-visited: hsl(var(--primary-h) calc(var(--primary-s) * 0.6) 45%);
|
|
68
|
+
|
|
69
|
+
/* Borders */
|
|
70
|
+
--border-default: var(--neutral-200);
|
|
71
|
+
--border-strong: var(--neutral-300);
|
|
72
|
+
--border-subtitle: var(--neutral-150);
|
|
73
|
+
--border-divider: var(--neutral-150);
|
|
74
|
+
--border-inverted: var(--neutral-700);
|
|
75
|
+
--border-disabled: var(--neutral-150);
|
|
76
|
+
|
|
77
|
+
/* Text */
|
|
78
|
+
--text-primary: var(--neutral-900);
|
|
79
|
+
--text-secondary: var(--neutral-700);
|
|
80
|
+
--text-tertiary: var(--neutral-500);
|
|
81
|
+
--text-muted: var(--neutral-400);
|
|
82
|
+
--text-disabled: var(--neutral-400);
|
|
83
|
+
--text-inverted: var(--neutral-0);
|
|
84
|
+
/* Text overlaid on arbitrary imagery (camera feeds, photos) — pairs a
|
|
85
|
+
guaranteed-light colour with a contrast shadow. */
|
|
86
|
+
--text-on-media: var(--neutral-0);
|
|
87
|
+
--text-on-media-shadow: 0 1px 3px hsla(0, 0%, 0%, .7);
|
|
88
|
+
--text-link: var(--blue-700);
|
|
89
|
+
--text-on-primary: var(--neutral-0);
|
|
90
|
+
--text-on-success: var(--neutral-0);
|
|
91
|
+
/* Warning fill is yellow (--yellow-400 #F4D43A) — white text on it is only
|
|
92
|
+
~1.5:1 (fails WCAG). On-warning text is the darkest neutral (~10:1). */
|
|
93
|
+
--text-on-warning: var(--neutral-900);
|
|
94
|
+
--text-on-error: var(--neutral-0);
|
|
95
|
+
|
|
96
|
+
/* Icons */
|
|
97
|
+
--icon-default: var(--neutral-500);
|
|
98
|
+
--icon-strong: var(--neutral-700);
|
|
99
|
+
--icon-muted: var(--neutral-400);
|
|
100
|
+
--icon-on-primary: var(--neutral-0);
|
|
101
|
+
|
|
102
|
+
/* Font families — a theme/brand choice (the size/weight/leading SCALE is
|
|
103
|
+
theme-independent and lives in styles/_tokens.scss). Display + heading
|
|
104
|
+
default to body sans; a brand can mix a serif/display family here without
|
|
105
|
+
touching component code. */
|
|
106
|
+
--font-sans: "Mulish", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
107
|
+
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
108
|
+
--font-display: var(--font-sans);
|
|
109
|
+
--font-heading: var(--font-sans);
|
|
110
|
+
|
|
111
|
+
/* Semantic → palette bridge. The lib knows colours by PURPOSE; the theme
|
|
112
|
+
(themes/default/_theme.scss) ships the concrete ramps + HSL channels. This
|
|
113
|
+
is the one place that maps semantic roles onto a concrete palette. A brand
|
|
114
|
+
re-skins by overriding either the palette (--blue-*, --red-*) or these
|
|
115
|
+
role tokens directly.
|
|
116
|
+
Channel mappings let hsla() halos/tints compose from the same hue source. */
|
|
117
|
+
--primary-h: var(--blue-h);
|
|
118
|
+
--primary-s: var(--blue-s);
|
|
119
|
+
--primary-l: var(--blue-l);
|
|
120
|
+
--error-h: var(--red-h);
|
|
121
|
+
--error-s: var(--red-s);
|
|
122
|
+
--error-l: var(--red-l);
|
|
123
|
+
|
|
124
|
+
/* Primary roles.
|
|
125
|
+
Hover / pressed / selected-hover are DERIVED from --primary at runtime via
|
|
126
|
+
color-mix() so a brand swap (overriding only --primary) propagates to every
|
|
127
|
+
interaction state automatically. A brand that wants a non-derived hover
|
|
128
|
+
hue can still override these tokens directly.
|
|
129
|
+
Step sizes (12% / 24% darker) mirror the blue 600→700→800 ramp. */
|
|
130
|
+
--primary: var(--blue-600);
|
|
131
|
+
--primary-hover: color-mix(in srgb, var(--primary) 88%, black);
|
|
132
|
+
--primary-pressed: color-mix(in srgb, var(--primary) 76%, black);
|
|
133
|
+
--primary-selected-hover: color-mix(in srgb, var(--primary) 88%, black);
|
|
134
|
+
--primary-subtitle: var(--blue-50);
|
|
135
|
+
--primary-muted: var(--blue-100);
|
|
136
|
+
--primary-muted-strong: var(--blue-200);
|
|
137
|
+
--primary-strong: var(--blue-700);
|
|
138
|
+
--primary-on: var(--neutral-0);
|
|
139
|
+
|
|
140
|
+
/* Accent / secondary role — composed from --secondary-h/s/l channels.
|
|
141
|
+
Channels default to brand values so unbranded apps render identical to
|
|
142
|
+
--primary. Override --secondary-h/s/l per-brand (or via the tweaks panel)
|
|
143
|
+
to introduce a true secondary hue — --secondary recomposes automatically.
|
|
144
|
+
Hover/pressed derive from --secondary so any hue change propagates. */
|
|
145
|
+
--secondary-h: var(--primary-h);
|
|
146
|
+
--secondary-s: var(--primary-s);
|
|
147
|
+
--secondary-l: var(--primary-l);
|
|
148
|
+
|
|
149
|
+
--secondary: hsl(var(--secondary-h), var(--secondary-s), var(--secondary-l));
|
|
150
|
+
--secondary-hover: color-mix(in srgb, var(--secondary) 88%, black);
|
|
151
|
+
--secondary-pressed: color-mix(in srgb, var(--secondary) 76%, black);
|
|
152
|
+
--secondary-strong: hsl(var(--secondary-h), var(--secondary-s), calc(var(--secondary-l) - 11%));
|
|
153
|
+
--secondary-subtitle: hsl(var(--secondary-h), 100%, 97%);
|
|
154
|
+
--secondary-muted: hsl(var(--secondary-h), 95%, 94%);
|
|
155
|
+
--secondary-on: var(--primary-on);
|
|
156
|
+
|
|
157
|
+
/* Tertiary — third brand hue. Used by the tertiary button variant and
|
|
158
|
+
tonal accents (decorative chips, hierarchy tones, indicators). Mirrors
|
|
159
|
+
the --primary / --secondary token-family shape: HSL channels for halo
|
|
160
|
+
composition, hover/pressed via color-mix, subtle/muted ramps for tonal
|
|
161
|
+
surfaces. Defaults to --primary channels so single-hue themes render
|
|
162
|
+
identical to primary. Multi-hue brands override --tertiary-h/s/l (or
|
|
163
|
+
--tertiary directly for non-HSL values). */
|
|
164
|
+
--tertiary-h: var(--primary-h);
|
|
165
|
+
--tertiary-s: var(--primary-s);
|
|
166
|
+
--tertiary-l: var(--primary-l);
|
|
167
|
+
|
|
168
|
+
--tertiary: hsl(var(--tertiary-h), var(--tertiary-s), var(--tertiary-l));
|
|
169
|
+
--tertiary-hover: color-mix(in srgb, var(--tertiary) 88%, black);
|
|
170
|
+
--tertiary-pressed: color-mix(in srgb, var(--tertiary) 76%, black);
|
|
171
|
+
--tertiary-strong: hsl(var(--tertiary-h), var(--tertiary-s), calc(var(--tertiary-l) - 11%));
|
|
172
|
+
--tertiary-subtitle: hsl(var(--tertiary-h), 100%, 97%);
|
|
173
|
+
--tertiary-muted: hsl(var(--tertiary-h), 95%, 94%);
|
|
174
|
+
--tertiary-on: var(--primary-on);
|
|
175
|
+
|
|
176
|
+
/* Status roles.
|
|
177
|
+
-hover / -pressed are derived from the base role via the same color-mix()
|
|
178
|
+
rule as --primary-hover. Override per role if a brand needs a non-derived
|
|
179
|
+
hue. -text / -border / -subtle / -muted stay as ramp picks because their
|
|
180
|
+
contrast targets are tuned, not procedural. */
|
|
181
|
+
--success: var(--green-500);
|
|
182
|
+
--success-hover: color-mix(in srgb, var(--success) 88%, black);
|
|
183
|
+
--success-subtitle: var(--green-50);
|
|
184
|
+
--success-muted: var(--green-100);
|
|
185
|
+
--success-border: var(--green-200);
|
|
186
|
+
--success-text: var(--green-700);
|
|
187
|
+
|
|
188
|
+
--warning: var(--yellow-400);
|
|
189
|
+
--warning-hover: color-mix(in srgb, var(--warning) 88%, black);
|
|
190
|
+
--warning-subtitle: var(--yellow-50);
|
|
191
|
+
--warning-muted: var(--yellow-100);
|
|
192
|
+
--warning-border: var(--yellow-200);
|
|
193
|
+
--warning-text: var(--yellow-700);
|
|
194
|
+
|
|
195
|
+
--error: var(--red-500);
|
|
196
|
+
--error-hover: color-mix(in srgb, var(--error) 88%, black);
|
|
197
|
+
--error-pressed: color-mix(in srgb, var(--error) 76%, black);
|
|
198
|
+
--error-subtitle: var(--red-50);
|
|
199
|
+
--error-muted: var(--red-100);
|
|
200
|
+
--error-border: var(--red-200);
|
|
201
|
+
--error-text: var(--red-600);
|
|
202
|
+
|
|
203
|
+
--info: var(--sky-500);
|
|
204
|
+
--info-hover: color-mix(in srgb, var(--info) 88%, black);
|
|
205
|
+
--info-subtitle: var(--sky-50);
|
|
206
|
+
--info-muted: var(--sky-100);
|
|
207
|
+
--info-border: var(--sky-200);
|
|
208
|
+
--info-text: var(--sky-700);
|
|
209
|
+
|
|
210
|
+
/* Status pills (event lifecycle).
|
|
211
|
+
DECISION: --status-confirmed-* chains through --primary-* so a
|
|
212
|
+
brand re-skin moves Confirmed with it. The other lifecycle
|
|
213
|
+
stages are deliberately neutral/green/dark to keep semantic
|
|
214
|
+
distance from brand. */
|
|
215
|
+
/* Status pills — canonical (single system).
|
|
216
|
+
Legacy classes `.badge--active` (single) and ad-hoc semantic
|
|
217
|
+
pills are unified in the Badge component to consume these tokens. */
|
|
218
|
+
--status-planning-bg: var(--neutral-150);
|
|
219
|
+
--status-planning-fg: var(--neutral-700);
|
|
220
|
+
--status-confirmed-bg: var(--primary-subtitle);
|
|
221
|
+
--status-confirmed-fg: var(--primary-strong);
|
|
222
|
+
--status-active-bg: var(--green-50);
|
|
223
|
+
--status-active-fg: var(--green-700);
|
|
224
|
+
--status-completed-bg: var(--neutral-900);
|
|
225
|
+
--status-completed-fg: var(--neutral-0);
|
|
226
|
+
|
|
227
|
+
/* Focus rings.
|
|
228
|
+
Outer halos use hsla() composed from brand/danger HSL channels
|
|
229
|
+
so re-skinning --primary-h propagates into focus state.
|
|
230
|
+
The inner halo reads --surface-canvas so the ring stays visible
|
|
231
|
+
in both light and dark themes (canvas flips, ring adapts). */
|
|
232
|
+
--focus-ring-inner: var(--surface-canvas);
|
|
233
|
+
--focus-ring-outer-h: var(--primary-h);
|
|
234
|
+
--focus-ring-outer-s: var(--primary-s);
|
|
235
|
+
--focus-ring-outer-l: var(--primary-l);
|
|
236
|
+
--focus-ring-outer-alpha: 1;
|
|
237
|
+
|
|
238
|
+
--focus-ring:
|
|
239
|
+
0 0 0 2px var(--focus-ring-inner),
|
|
240
|
+
0 0 0 4px hsla(var(--focus-ring-outer-h), var(--focus-ring-outer-s), var(--focus-ring-outer-l), var(--focus-ring-outer-alpha));
|
|
241
|
+
--focus-ring-inverted:
|
|
242
|
+
0 0 0 2px var(--neutral-900),
|
|
243
|
+
0 0 0 4px var(--blue-300);
|
|
244
|
+
--focus-ring-error:
|
|
245
|
+
0 0 0 2px var(--focus-ring-inner),
|
|
246
|
+
0 0 0 4px hsla(var(--error-h), var(--error-s), var(--error-l), 1);
|
|
247
|
+
|
|
248
|
+
/* Field focus — coloured halo only (no double-ring). Derived from
|
|
249
|
+
brand HSL so it tracks rebrand. Alpha is a token so dark themes
|
|
250
|
+
can boost it for legibility. */
|
|
251
|
+
--focus-field-alpha: .22;
|
|
252
|
+
--focus-field:
|
|
253
|
+
0 0 0 3px hsla(var(--primary-h), var(--primary-s), var(--primary-l), var(--focus-field-alpha));
|
|
254
|
+
--focus-field-error:
|
|
255
|
+
0 0 0 3px hsla(var(--error-h), var(--error-s), var(--error-l), var(--focus-field-alpha));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* Brand-derived neutrals — opt-in family switch (ds-docs tweaks panel sets
|
|
259
|
+
the attribute). Repoints the --neutral-* slot at the brand-gray ramp. */
|
|
260
|
+
:root[data-neutrals='brand'] {
|
|
261
|
+
--neutral-0: var(--brand-gray-0);
|
|
262
|
+
--neutral-50: var(--brand-gray-50);
|
|
263
|
+
--neutral-100: var(--brand-gray-100);
|
|
264
|
+
--neutral-150: var(--brand-gray-150);
|
|
265
|
+
--neutral-200: var(--brand-gray-200);
|
|
266
|
+
--neutral-300: var(--brand-gray-300);
|
|
267
|
+
--neutral-400: var(--brand-gray-400);
|
|
268
|
+
--neutral-500: var(--brand-gray-500);
|
|
269
|
+
--neutral-600: var(--brand-gray-600);
|
|
270
|
+
--neutral-700: var(--brand-gray-700);
|
|
271
|
+
--neutral-800: var(--brand-gray-800);
|
|
272
|
+
--neutral-900: var(--brand-gray-900);
|
|
273
|
+
--neutral-950: var(--brand-gray-950);
|
|
274
|
+
}
|