@ids-group-ltd/ids-design-system 0.3.0 → 0.4.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.
@@ -1,19 +1,14 @@
1
1
  // =========================================================================
2
- // Default palette — Tier 1 concrete colour primitives.
2
+ // Base palette — Tier 1 theme-independent primitives.
3
3
  //
4
- // This file DEFINES the colours: raw ramps (--cool-gray-*, --brand-gray-*,
5
- // --blue-*, --red-*, --green-*, --yellow-*, --sky-*, --orange-*) + the HSL
6
- // channels (--blue-h/s/l, --red-h/s/l, --shadow-tint-h/s/l) they compose from.
7
- // Tokens are named by the COLOUR itself — the palette does not know what a
8
- // colour is "for". The --neutral-* FAMILY SLOT (which gray ramp is active)
9
- // is semantic, so it lives in the theme (_theme.scss), not here.
4
+ // Emits at :root. Colour ramps and channels that are identical in every
5
+ // theme: hue ramps (--cool-gray-*, --brand-gray-*, --blue-*, --red-*, ),
6
+ // HSL channels (--blue-h/s/l, --red-h/s/l), shadow substrate h/s (NOT -l
7
+ // that is per-theme), static anchors, and font families.
10
8
  //
11
- // The companion theme (themes/default/_theme.scss) SETS these onto semantic
12
- // roles (--primary --blue-600, --error → --red-500, …). Components consume
13
- // only those roles, never this palette directly.
14
- //
15
- // A sibling palette (e.g. _palette-dark.scss) ships different ramp values; pair
16
- // it with a matching theme file. Default = "Blue + Slate baseline".
9
+ // The --neutral-* FAMILY SLOT (which gray ramp is active) is per-theme and
10
+ // lives in each theme's _palette.scss mixin. Components consume only semantic
11
+ // roles; they never reference this file directly.
17
12
  // =========================================================================
18
13
 
19
14
  :root {
@@ -95,6 +90,9 @@
95
90
  // -----------------------------------------------------------------------
96
91
  // Green — success
97
92
  // -----------------------------------------------------------------------
93
+ // HSL channels (additive — for focus-ring composition + ds-color). The ramp
94
+ // stops below stay hand-picked hex; these reproduce --green-500 ≈ #22A33A.
95
+ --green-h: 132; --green-s: 66%; --green-l: 39%;
98
96
  --green-50: #ECFAEE;
99
97
  --green-100: #D6F3DB;
100
98
  --green-200: #BBE8C2;
@@ -107,6 +105,9 @@
107
105
  // -----------------------------------------------------------------------
108
106
  // Yellow — attention / warning
109
107
  // -----------------------------------------------------------------------
108
+ // HSL channels (additive — for focus-ring composition + ds-color). Reproduce
109
+ // --yellow-400 ≈ #F4D43A; ramp stops below stay hand-picked hex.
110
+ --yellow-h: 47; --yellow-s: 89%; --yellow-l: 59%;
110
111
  --yellow-50: #FFFBEA;
111
112
  --yellow-100: #FEF8DC;
112
113
  --yellow-200: #FCEFA1;
@@ -148,12 +149,28 @@
148
149
  --sky-700: #1D4ED8;
149
150
 
150
151
  // -----------------------------------------------------------------------
151
- // Shadow tint — channel-split substrate. Light theme uses a cool
152
- // near-black slate (225 / 39% / 7%). A dark theme would override
153
- // --shadow-tint-l to ~95% so the same alphas render as soft light
154
- // glows over dark surfaces.
152
+ // Shadow tint — channel-split substrate h/s only. The lightness (-l) is
153
+ // per-theme: light uses 7%, dark uses 95% (so the same alphas read as soft
154
+ // glows over dark surfaces). Each theme's _palette.scss mixin sets -l.
155
155
  // -----------------------------------------------------------------------
156
156
  --shadow-tint-h: 225;
157
157
  --shadow-tint-s: 39%;
158
- --shadow-tint-l: 7%;
158
+
159
+ // -----------------------------------------------------------------------
160
+ // Theme-stable contrast anchors — never flip with the theme. On-fill text/
161
+ // icon roles point here so a dark theme keeps white-on-coloured-fill correct.
162
+ // -----------------------------------------------------------------------
163
+ --static-white: #FFF;
164
+ --static-ink: #161A28;
165
+
166
+ // -----------------------------------------------------------------------
167
+ // Font families — a theme/brand choice (the size/weight/leading SCALE is
168
+ // theme-independent and lives in styles/_tokens.scss). Display + heading
169
+ // default to body sans; a brand can mix a serif/display family here without
170
+ // touching component code.
171
+ // -----------------------------------------------------------------------
172
+ --font-sans: "Mulish", -apple-system, blinkmacsystemfont, "Segoe UI", roboto, sans-serif;
173
+ --font-mono: "JetBrains Mono", ui-monospace, sfmono-regular, menlo, monospace;
174
+ --font-display: var(--font-sans);
175
+ --font-heading: var(--font-sans);
159
176
  }
@@ -1,40 +1,21 @@
1
1
  // =========================================================================
2
- // Default theme — Tier 2 semantic role ASSIGNMENTS (light).
2
+ // Shared role structure — Tier 2 semantic role ASSIGNMENTS.
3
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.
4
+ // Exposes @mixin roles: the role map whose structure is identical across
5
+ // themes and which resolves through the per-theme palette (surfaces
6
+ // --neutral-*, text/icon/border --neutral-*, brand channels, status, focus,
7
+ // semanticpalette bridge). These are the LIGHT-DEFAULT values dark theme
8
+ // overrides only the deltas in its own _theme.scss mixin body.
10
9
  //
11
- // Tier 3 (component-scoped tokens, geometry, typography, motion) lives in
12
- // styles/_tokens.scss and is theme-independent.
10
+ // This mixin is an authoring-DRY device. At runtime, exactly one theme block
11
+ // matches (`:not([data-theme='dark'])` vs `[data-theme='dark']`), and that
12
+ // block declares the complete token set itself (palette + roles + overrides).
13
13
  // =========================================================================
14
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
-
15
+ @mixin roles {
35
16
  /* Surfaces — six-stop scale.
36
17
  · canvas : page background
37
- · default : cards, sheets, toolbars (the paper)
18
+ · default : cards, sheets, toolbars (the "paper")
38
19
  · secondary : hover lanes, table heads, addons, code blocks
39
20
  · overlay : modals, drawers, popovers (paired with scrim)
40
21
  · tint : brand-tinted (active rows, sidenav active item)
@@ -84,34 +65,25 @@
84
65
 
85
66
  /* Text overlaid on arbitrary imagery (camera feeds, photos) — pairs a
86
67
  guaranteed-light colour with a contrast shadow. */
87
- --text-on-media: var(--neutral-0);
68
+ --text-on-media: var(--static-white);
88
69
  --text-on-media-shadow: 0 1px 3px hsl(0deg 0% 0% / 70%);
89
70
  --text-link: var(--blue-700);
90
- --text-on-primary: var(--neutral-0);
91
- --text-on-success: var(--neutral-0);
71
+ --text-on-primary: var(--static-white);
72
+ --text-on-success: var(--static-white);
92
73
 
93
74
  /* Warning fill is yellow (--yellow-400 #F4D43A) — white text on it is only
94
75
  ~1.5:1 (fails WCAG). On-warning text is the darkest neutral (~10:1). */
95
- --text-on-warning: var(--neutral-900);
96
- --text-on-error: var(--neutral-0);
76
+ --text-on-warning: var(--static-ink);
77
+ --text-on-error: var(--static-white);
97
78
 
98
79
  /* Icons */
99
80
  --icon-default: var(--neutral-500);
100
81
  --icon-strong: var(--neutral-700);
101
82
  --icon-muted: var(--neutral-400);
102
- --icon-on-primary: var(--neutral-0);
103
-
104
- /* Font families — a theme/brand choice (the size/weight/leading SCALE is
105
- theme-independent and lives in styles/_tokens.scss). Display + heading
106
- default to body sans; a brand can mix a serif/display family here without
107
- touching component code. */
108
- --font-sans: "Mulish", -apple-system, blinkmacsystemfont, "Segoe UI", roboto, sans-serif;
109
- --font-mono: "JetBrains Mono", ui-monospace, sfmono-regular, menlo, monospace;
110
- --font-display: var(--font-sans);
111
- --font-heading: var(--font-sans);
83
+ --icon-on-primary: var(--static-white);
112
84
 
113
85
  /* Semantic → palette bridge. The lib knows colours by PURPOSE; the theme
114
- (themes/default/_theme.scss) ships the concrete ramps + HSL channels. This
86
+ (themes/light/_theme.scss) ships the concrete ramps + HSL channels. This
115
87
  is the one place that maps semantic roles onto a concrete palette. A brand
116
88
  re-skins by overriding either the palette (--blue-*, --red-*) or these
117
89
  role tokens directly.
@@ -122,6 +94,12 @@
122
94
  --error-h: var(--red-h);
123
95
  --error-s: var(--red-s);
124
96
  --error-l: var(--red-l);
97
+ --success-h: var(--green-h);
98
+ --success-s: var(--green-s);
99
+ --success-l: var(--green-l);
100
+ --warning-h: var(--yellow-h);
101
+ --warning-s: var(--yellow-s);
102
+ --warning-l: var(--yellow-l);
125
103
 
126
104
  /* Primary roles.
127
105
  Hover / pressed / selected-hover are DERIVED from --primary at runtime via
@@ -137,7 +115,7 @@
137
115
  --primary-muted: var(--blue-100);
138
116
  --primary-muted-strong: var(--blue-200);
139
117
  --primary-strong: var(--blue-700);
140
- --primary-on: var(--neutral-0);
118
+ --primary-on: var(--static-white);
141
119
 
142
120
  /* Accent / secondary role — composed from --secondary-h/s/l channels.
143
121
  Channels default to brand values so unbranded apps render identical to
@@ -154,6 +132,7 @@
154
132
  --secondary-strong: hsl(var(--secondary-h) var(--secondary-s) calc(var(--secondary-l) - 11%));
155
133
  --secondary-subtitle: hsl(var(--secondary-h) 100% 97%);
156
134
  --secondary-muted: hsl(var(--secondary-h) 95% 94%);
135
+ --secondary-muted-strong: hsl(var(--secondary-h) 95% 89%);
157
136
  --secondary-on: var(--primary-on);
158
137
 
159
138
  /* Tertiary — third brand hue. Used by the tertiary button variant and
@@ -173,6 +152,7 @@
173
152
  --tertiary-strong: hsl(var(--tertiary-h) var(--tertiary-s) calc(var(--tertiary-l) - 11%));
174
153
  --tertiary-subtitle: hsl(var(--tertiary-h) 100% 97%);
175
154
  --tertiary-muted: hsl(var(--tertiary-h) 95% 94%);
155
+ --tertiary-muted-strong: hsl(var(--tertiary-h) 95% 89%);
176
156
  --tertiary-on: var(--primary-on);
177
157
 
178
158
  /* Status roles.
@@ -184,6 +164,7 @@
184
164
  --success-hover: color-mix(in srgb, var(--success) 88%, black);
185
165
  --success-subtitle: var(--green-50);
186
166
  --success-muted: var(--green-100);
167
+ --success-muted-strong: var(--green-200);
187
168
  --success-border: var(--green-200);
188
169
  --success-text: var(--green-700);
189
170
 
@@ -191,6 +172,7 @@
191
172
  --warning-hover: color-mix(in srgb, var(--warning) 88%, black);
192
173
  --warning-subtitle: var(--yellow-50);
193
174
  --warning-muted: var(--yellow-100);
175
+ --warning-muted-strong: var(--yellow-200);
194
176
  --warning-border: var(--yellow-200);
195
177
  --warning-text: var(--yellow-700);
196
178
 
@@ -199,9 +181,18 @@
199
181
  --error-pressed: color-mix(in srgb, var(--error) 76%, black);
200
182
  --error-subtitle: var(--red-50);
201
183
  --error-muted: var(--red-100);
184
+ --error-muted-strong: var(--red-200);
202
185
  --error-border: var(--red-200);
203
186
  --error-text: var(--red-600);
204
187
 
188
+ /* -pressed (success/warning) + -on aliases — added for the --accent-* pointer
189
+ family so every ds-color family resolves --accent-pressed / --accent-on. */
190
+ --success-pressed: color-mix(in srgb, var(--success) 76%, black);
191
+ --warning-pressed: color-mix(in srgb, var(--warning) 76%, black);
192
+ --error-on: var(--text-on-error);
193
+ --success-on: var(--text-on-success);
194
+ --warning-on: var(--text-on-warning);
195
+
205
196
  --info: var(--sky-500);
206
197
  --info-hover: color-mix(in srgb, var(--info) 88%, black);
207
198
  --info-subtitle: var(--sky-50);
@@ -256,22 +247,44 @@
256
247
  0 0 0 3px hsl(var(--primary-h) var(--primary-s) var(--primary-l) / var(--focus-field-alpha));
257
248
  --focus-field-error:
258
249
  0 0 0 3px hsl(var(--error-h) var(--error-s) var(--error-l) / var(--focus-field-alpha));
259
- }
260
250
 
261
- /* Brand-derived neutralsopt-in family switch (ds-docs tweaks panel sets
262
- the attribute). Repoints the --neutral-* slot at the brand-gray ramp. */
263
- :root[data-neutrals='brand'] {
264
- --neutral-0: var(--brand-gray-0);
265
- --neutral-50: var(--brand-gray-50);
266
- --neutral-100: var(--brand-gray-100);
267
- --neutral-150: var(--brand-gray-150);
268
- --neutral-200: var(--brand-gray-200);
269
- --neutral-300: var(--brand-gray-300);
270
- --neutral-400: var(--brand-gray-400);
271
- --neutral-500: var(--brand-gray-500);
272
- --neutral-600: var(--brand-gray-600);
273
- --neutral-700: var(--brand-gray-700);
274
- --neutral-800: var(--brand-gray-800);
275
- --neutral-900: var(--brand-gray-900);
276
- --neutral-950: var(--brand-gray-950);
251
+ /* Per-family focus rings/fields pre-composed once from each family's
252
+ channels so ds-color flips a single --accent-focus-* pointer (no :root
253
+ composite re-declaration in the scope). --focus-ring-error exists above. */
254
+ --focus-ring-secondary:
255
+ 0 0 0 2px var(--focus-ring-inner),
256
+ 0 0 0 4px hsl(var(--secondary-h) var(--secondary-s) var(--secondary-l) / 100%);
257
+ --focus-ring-tertiary:
258
+ 0 0 0 2px var(--focus-ring-inner),
259
+ 0 0 0 4px hsl(var(--tertiary-h) var(--tertiary-s) var(--tertiary-l) / 100%);
260
+ --focus-ring-success:
261
+ 0 0 0 2px var(--focus-ring-inner),
262
+ 0 0 0 4px hsl(var(--success-h) var(--success-s) var(--success-l) / 100%);
263
+ --focus-ring-warning:
264
+ 0 0 0 2px var(--focus-ring-inner),
265
+ 0 0 0 4px hsl(var(--warning-h) var(--warning-s) var(--warning-l) / 100%);
266
+ --focus-field-secondary:
267
+ 0 0 0 3px hsl(var(--secondary-h) var(--secondary-s) var(--secondary-l) / var(--focus-field-alpha));
268
+ --focus-field-tertiary:
269
+ 0 0 0 3px hsl(var(--tertiary-h) var(--tertiary-s) var(--tertiary-l) / var(--focus-field-alpha));
270
+ --focus-field-success:
271
+ 0 0 0 3px hsl(var(--success-h) var(--success-s) var(--success-l) / var(--focus-field-alpha));
272
+ --focus-field-warning:
273
+ 0 0 0 3px hsl(var(--warning-h) var(--warning-s) var(--warning-l) / var(--focus-field-alpha));
274
+
275
+ /* Accent pointer layer — the single indirection ds-color flips. Defaults to
276
+ the primary family (+ existing focus/tint tokens) so the unbranded theme is
277
+ pixel-identical. Components read these for their accent instead of
278
+ --primary* / --focus-* / --surface-tint. */
279
+ --accent: var(--primary);
280
+ --accent-hover: var(--primary-hover);
281
+ --accent-pressed: var(--primary-pressed);
282
+ --accent-strong: var(--primary-strong);
283
+ --accent-subtitle: var(--primary-subtitle);
284
+ --accent-muted: var(--primary-muted);
285
+ --accent-muted-strong: var(--primary-muted-strong);
286
+ --accent-on: var(--primary-on);
287
+ --accent-focus-ring: var(--focus-ring);
288
+ --accent-focus-field: var(--focus-field);
289
+ --accent-surface-tint: var(--surface-tint);
277
290
  }
@@ -0,0 +1,39 @@
1
+ // =========================================================================
2
+ // Dark palette — Tier 1b per-theme primitives.
3
+ //
4
+ // Exposes @mixin palette with ONLY the dark-specific primitives:
5
+ // · --neutral-* slot: hand-tuned dark neutrals (NOT a linear inverse —
6
+ // note --neutral-0 is an ELEVATED card surface, LIGHTER than the
7
+ // --neutral-50 canvas — the opposite of light, where 0 (#FFF) is the
8
+ // brightest stop). Brand/status ramps are left to the role layer.
9
+ // · --shadow-tint-l: 95% (flipped so same alphas read as soft glows)
10
+ //
11
+ // Theme-independent ramps (--cool-gray-*, --brand-gray-*, --blue-*, etc.)
12
+ // and HSL channels live in themes/_base-palette.scss and emit at :root.
13
+ //
14
+ // KNOWN LIMITATION — data-neutrals="brand" + dark: this ramp overrides
15
+ // --neutral-* directly, so when both `[data-neutrals='brand']` and
16
+ // `[data-theme='dark']` are set (equal specificity) dark wins by source order —
17
+ // the brand-gray hue-tint is dropped and these fixed dark neutrals apply. Benign
18
+ // (renders correctly dark, just not brand-hue-tinted); a dark brand-gray ramp is
19
+ // a future follow-up, not v1.
20
+ // =========================================================================
21
+
22
+ @mixin palette {
23
+ --neutral-0: #171B24; // surface-default / overlay (cards) — elevated
24
+ --neutral-50: #0F131A; // surface-canvas — darkest (page bg)
25
+ --neutral-100: #1E2330; // surface-secondary / disabled
26
+ --neutral-150: #242A38; // surface-muted, border-subtitle/divider/disabled
27
+ --neutral-200: #2A3040; // border-default
28
+ --neutral-300: #3A4252; // border-strong
29
+ --neutral-400: #6B7388; // text-muted/disabled, icon-muted
30
+ --neutral-500: #8A93A6; // text-tertiary, icon-default
31
+ --neutral-600: #9BA3B4;
32
+ --neutral-700: #B7BFD0; // text-secondary, icon-strong, border-inverted
33
+ --neutral-800: #CDD3E0;
34
+ --neutral-900: #E7EAF2; // text-primary, surface-inverted
35
+ --neutral-950: #F4F6FA;
36
+
37
+ // Shadow substrate flips light so the same alphas read as soft glows on dark.
38
+ --shadow-tint-l: 95%;
39
+ }
@@ -0,0 +1,122 @@
1
+ // =========================================================================
2
+ // Dark theme — Tier 2 role overrides.
3
+ //
4
+ // Composes the dark palette (neutral ramp + shadow-l 95%) with the shared
5
+ // role structure, then overrides only the roles whose VALUES differ from
6
+ // the light defaults. The activation selector (:root[data-theme='dark'])
7
+ // lives in styles/_theme-activation.scss. Dark is opt-in — light is the
8
+ // default; there is no automatic prefers-color-scheme switch.
9
+ //
10
+ // Most roles follow the inverted --neutral-* ramp automatically (custom
11
+ // props resolve at use-time). This file overrides ONLY roles that can't
12
+ // follow the flip: tint/subtle surfaces (near-white *-50 picks → dark hue
13
+ // tints), on-tint text (*-text / *-strong → lighter stops), hover/pressed
14
+ // direction, scrim (must stay dark), focus halo alpha, and charts.
15
+ // On-fill text already points at theme-stable --static-white/--static-ink.
16
+ // =========================================================================
17
+
18
+ @use 'palette';
19
+ @use '../semantic';
20
+
21
+ @mixin theme {
22
+ @include palette.palette;
23
+ @include semantic.roles;
24
+
25
+ // Interaction states: on dark, hover/pressed must LIGHTEN. The default theme
26
+ // derives them as color-mix toward black (darkens) — wrong direction here.
27
+ // Mirror the light recipe's 88% / 76% steps, mixed toward white instead.
28
+ --primary-hover: color-mix(in srgb, var(--primary) 88%, white);
29
+ --primary-pressed: color-mix(in srgb, var(--primary) 76%, white);
30
+ --primary-selected-hover: color-mix(in srgb, var(--primary) 88%, white);
31
+ --secondary-hover: color-mix(in srgb, var(--secondary) 88%, white);
32
+ --secondary-pressed: color-mix(in srgb, var(--secondary) 76%, white);
33
+ --tertiary-hover: color-mix(in srgb, var(--tertiary) 88%, white);
34
+ --tertiary-pressed: color-mix(in srgb, var(--tertiary) 76%, white);
35
+ --success-hover: color-mix(in srgb, var(--success) 88%, white);
36
+ --warning-hover: color-mix(in srgb, var(--warning) 88%, white);
37
+ --error-hover: color-mix(in srgb, var(--error) 88%, white);
38
+ --error-pressed: color-mix(in srgb, var(--error) 76%, white);
39
+ --success-pressed: color-mix(in srgb, var(--success) 76%, white);
40
+ --warning-pressed: color-mix(in srgb, var(--warning) 76%, white);
41
+ --info-hover: color-mix(in srgb, var(--info) 88%, white);
42
+
43
+ // Brand tint surfaces + on-tint text (channel-derived → track rebrand).
44
+ --surface-tint: hsl(var(--primary-h) 45% 16%);
45
+ --primary-subtitle: hsl(var(--primary-h) 45% 18%);
46
+ --primary-muted: hsl(var(--primary-h) 40% 26%);
47
+ --primary-muted-strong: hsl(var(--primary-h) 42% 32%);
48
+ --primary-strong: hsl(var(--primary-h) 90% 72%);
49
+
50
+ --secondary-subtitle: hsl(var(--secondary-h) 45% 18%);
51
+ --secondary-muted: hsl(var(--secondary-h) 40% 26%);
52
+ --secondary-muted-strong: hsl(var(--secondary-h) 42% 32%);
53
+ --secondary-strong: hsl(var(--secondary-h) 90% 72%);
54
+
55
+ --tertiary-subtitle: hsl(var(--tertiary-h) 45% 18%);
56
+ --tertiary-muted: hsl(var(--tertiary-h) 40% 26%);
57
+ --tertiary-muted-strong: hsl(var(--tertiary-h) 42% 32%);
58
+ --tertiary-strong: hsl(var(--tertiary-h) 90% 72%);
59
+
60
+ --text-link: hsl(var(--primary-h) 90% 74%);
61
+ --link-visited: hsl(var(--primary-h) 50% 70%);
62
+
63
+ // Status families: dark tint bg + border, lighter text.
64
+ --success-subtitle: hsl(132deg 38% 14%);
65
+ --success-muted: hsl(132deg 34% 20%);
66
+ --success-muted-strong: hsl(132deg 30% 28%);
67
+ --success-border: hsl(132deg 30% 28%);
68
+ --success-text: hsl(132deg 55% 62%);
69
+
70
+ --warning-subtitle: hsl(45deg 42% 14%);
71
+ --warning-muted: hsl(45deg 38% 22%);
72
+ --warning-muted-strong: hsl(45deg 35% 30%);
73
+ --warning-border: hsl(45deg 35% 30%);
74
+ --warning-text: hsl(45deg 90% 62%);
75
+
76
+ --error-subtitle: hsl(0deg 45% 16%);
77
+ --error-muted: hsl(0deg 40% 24%);
78
+ --error-muted-strong: hsl(0deg 38% 32%);
79
+ --error-border: hsl(0deg 38% 32%);
80
+ --error-text: hsl(0deg 85% 74%);
81
+
82
+ --info-subtitle: hsl(217deg 45% 16%);
83
+ --info-muted: hsl(217deg 40% 24%);
84
+ --info-border: hsl(217deg 38% 32%);
85
+ --info-text: hsl(217deg 90% 74%);
86
+
87
+ // Status pills that referenced palette stops directly (green-50/700) — repoint
88
+ // to the dark status roles so the lifecycle pill reads on dark. (planning →
89
+ // neutral, confirmed → primary-*, completed → neutral all follow flips.)
90
+ --status-active-bg-color: var(--success-subtitle);
91
+ --status-active-text-color: var(--success-text);
92
+
93
+ // Scrim stays dark — must NOT follow --shadow-tint-l: 95%.
94
+ --surface-scrim: hsl(225deg 39% 4% / 70%);
95
+
96
+ // Field focus halo — boost alpha for legibility on dark.
97
+ --focus-field-alpha: .35;
98
+
99
+ // Chart / dataviz overrides. Axis/grid/label/title/cursor already flip via the
100
+ // --neutral-* / --text-* roles; only the light-bg-tuned bits move here.
101
+ --chart-hover-bg-color: hsl(0deg 0% 100% / 6%); // light wash (default dark wash is invisible on dark)
102
+ --chart-tooltip-bg-color: var(--surface-overlay); // native dark tooltip, not an inverted light box
103
+ --chart-tooltip-text-color: var(--text-primary);
104
+ --chart-tooltip-border: var(--border-strong);
105
+
106
+ // Sequential — flip so low → high reads dark → bright on a dark canvas.
107
+ --chart-seq-1: var(--blue-800);
108
+ --chart-seq-2: var(--blue-700);
109
+ --chart-seq-3: var(--blue-600);
110
+ --chart-seq-4: var(--blue-500);
111
+ --chart-seq-5: var(--blue-400);
112
+ --chart-seq-6: var(--blue-300);
113
+
114
+ // Divergent — bright red ← dark-neutral zero → bright green (|value| → brightness).
115
+ --chart-div--3: hsl(0deg 70% 62%);
116
+ --chart-div--2: hsl(0deg 55% 46%);
117
+ --chart-div--1: hsl(0deg 45% 32%);
118
+ --chart-div-0: var(--neutral-150);
119
+ --chart-div-1: hsl(140deg 40% 32%);
120
+ --chart-div-2: hsl(140deg 48% 46%);
121
+ --chart-div-3: hsl(140deg 55% 60%);
122
+ }
@@ -0,0 +1,34 @@
1
+ // =========================================================================
2
+ // Light palette — Tier 1b per-theme primitives.
3
+ //
4
+ // Exposes @mixin palette with ONLY the light-specific primitives:
5
+ // · --neutral-* slot pointed at the cool-gray ramp
6
+ // · --shadow-tint-l: 7% (light shadow substrate lightness)
7
+ //
8
+ // Theme-independent ramps (--cool-gray-*, --brand-gray-*, --blue-*, etc.)
9
+ // and HSL channels live in themes/_base-palette.scss and emit at :root.
10
+ // =========================================================================
11
+
12
+ @mixin palette {
13
+ /* Neutral family slot — semantic pointer at the active gray ramp from the
14
+ palette. Default: hand-drawn cool-gray. <html data-neutrals="brand">
15
+ (ds-docs tweaks panel) repoints it at the brand-derived brand-gray ramp,
16
+ where --primary-h re-tints every gray. Roles below consume --neutral-*
17
+ so the family switch never touches them. */
18
+ --neutral-0: var(--cool-gray-0);
19
+ --neutral-50: var(--cool-gray-50);
20
+ --neutral-100: var(--cool-gray-100);
21
+ --neutral-150: var(--cool-gray-150);
22
+ --neutral-200: var(--cool-gray-200);
23
+ --neutral-300: var(--cool-gray-300);
24
+ --neutral-400: var(--cool-gray-400);
25
+ --neutral-500: var(--cool-gray-500);
26
+ --neutral-600: var(--cool-gray-600);
27
+ --neutral-700: var(--cool-gray-700);
28
+ --neutral-800: var(--cool-gray-800);
29
+ --neutral-900: var(--cool-gray-900);
30
+ --neutral-950: var(--cool-gray-950);
31
+
32
+ // Shadow substrate lightness for light theme.
33
+ --shadow-tint-l: 7%;
34
+ }
@@ -0,0 +1,15 @@
1
+ // =========================================================================
2
+ // Light theme — Tier 2 role assignments (light).
3
+ //
4
+ // Composes the light-specific palette (neutral slot + shadow-l) with the
5
+ // shared role structure. Light needs no extra role overrides — _roles.scss
6
+ // already holds the light-default values.
7
+ // =========================================================================
8
+
9
+ @use 'palette';
10
+ @use '../semantic';
11
+
12
+ @mixin theme {
13
+ @include palette.palette;
14
+ @include semantic.roles;
15
+ }