@zambon-dev/ui 1.0.0 → 1.1.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,3 +1,6 @@
1
+ import { ButtonComponent as LibButtonComponent } from "@zambon-dev/ui/library";
2
+ import { ButtonComponent } from "@zambon-dev/ui/framework";
1
3
  export * from "@zambon-dev/ui/library";
2
4
  export * from "@zambon-dev/ui/framework";
3
5
  export * from "@zambon-dev/ui/shared";
6
+ export { ButtonComponent, LibButtonComponent };
@@ -1,4 +1,5 @@
1
1
  {
2
+ "Login-Copyright": "© All rights reserved.",
2
3
  "Login-DesignBy": "Design by Ricardo Zambon",
3
4
  "Login-Errors-InternalServerError": "Occurred some error with the server, please try again.",
4
5
  "Login-Errors-Invalid": "The username or password you entered is incorrect.",
@@ -7,6 +8,7 @@
7
8
  "Login-RememberMe": "Remember me",
8
9
  "Login-SelectLanguage": "Select language",
9
10
  "Login-SignIn": "Sign In",
11
+ "Login-Title": "Welcome back",
10
12
  "Login-Username-Required": "Please enter the username",
11
13
  "Login-Username": "Type your username",
12
14
 
@@ -1,4 +1,5 @@
1
1
  {
2
+ "Login-Copyright": "© Todos os direitos reservados.",
2
3
  "Login-DesignBy": "Design por Ricardo Zambon",
3
4
  "Login-Errors-InternalServerError": "Ocorreu algum erro no servidor, por favor tente novamente.",
4
5
  "Login-Errors-Invalid": "O usuário ou senha informados são inválidos",
@@ -7,6 +8,7 @@
7
8
  "Login-RememberMe": "Lembrar de mim",
8
9
  "Login-SelectLanguage": "Selecionar idioma",
9
10
  "Login-SignIn": "Entrar",
11
+ "Login-Title": "Bem-vindo de volta",
10
12
  "Login-Username-Required": "Por favor informe o usuário",
11
13
  "Login-Username": "Digite o seu usuário",
12
14
 
@@ -2,6 +2,9 @@
2
2
  "TopBar-Notifications-Title": "Notifications",
3
3
  "TopBar-Notifications-Empty": "No notifications",
4
4
  "TopBar-Notifications-MarkAllRead": "Mark all as read",
5
+ "TopBar-Theme-Light": "Light theme",
6
+ "TopBar-Theme-Dark": "Dark theme",
7
+ "TopBar-Theme-System": "Theme follows the system",
5
8
 
6
9
  "": ""
7
10
  }
@@ -2,6 +2,9 @@
2
2
  "TopBar-Notifications-Title": "Notificações",
3
3
  "TopBar-Notifications-Empty": "Nenhuma notificação",
4
4
  "TopBar-Notifications-MarkAllRead": "Marcar todas como lidas",
5
+ "TopBar-Theme-Light": "Tema claro",
6
+ "TopBar-Theme-Dark": "Tema escuro",
7
+ "TopBar-Theme-System": "Tema acompanha o sistema",
5
8
 
6
9
  "": ""
7
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zambon-dev/ui",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Angular UI components, application framework and shared application layer for Zambon applications.",
5
5
  "keywords": [
6
6
  "angular",
@@ -1,7 +1,6 @@
1
1
  @reference './theme.css';
2
2
 
3
3
  @import 'variables';
4
- @import 'components/button';
5
4
  @import 'components/card';
6
5
  @import 'components/dropdown-static';
7
6
  @import 'components/input';
@@ -1,10 +1,16 @@
1
1
  .card {
2
- @apply border rounded-lg relative mt-4 pt-3 px-2 pb-2;
3
- @apply bg-white/80 border-gray-300/90;
2
+ @apply border relative mt-4 pt-3 px-2 pb-2;
4
3
  @apply grid gap-3;
4
+ border-radius: var(--radius-control);
5
+ background-color: var(--color-surface-solid);
6
+ border-color: var(--color-line);
5
7
 
6
8
  .title {
7
- @apply text-lg font-semibold absolute -top-4 left-2 px-2 bg-white;
9
+ @apply absolute -top-4 left-2 px-2;
10
+ font-size: var(--text-title);
11
+ font-weight: var(--font-weight-emphasis);
12
+ background-color: var(--color-surface-solid);
13
+ color: var(--color-content-primary);
8
14
  }
9
15
 
10
16
  .input-group:not(:first-child) {
@@ -1,19 +1,6 @@
1
1
  .dropdown {
2
2
  @apply relative;
3
3
 
4
- .btn {
5
- @apply bg-neutral-600/60 text-white border border-neutral-900/40 backdrop-blur-md;
6
-
7
- @apply focus:ring-neutral-600/30;
8
- @apply focus:bg-neutral-600/70 hover:bg-neutral-600/70;
9
- @apply active:bg-neutral-600/80;
10
-
11
- &:after {
12
- @apply content-[''] inline-block ml-1 align-middle;
13
- @apply border-x-[.3em] border-t-[.3em] border-solid border-x-white/0;
14
- }
15
- }
16
-
17
4
  &.show ul {
18
5
  @apply block;
19
6
  }
@@ -23,13 +10,24 @@
23
10
  }
24
11
 
25
12
  ul {
26
- @apply absolute hidden py-1 rounded-sm shadow-sm;
27
- @apply bg-white/95 border border-neutral-400 backdrop-blur-md shadow-md;
13
+ @apply absolute hidden py-1 border;
14
+ border-radius: var(--radius-control);
15
+ border-color: var(--color-line);
16
+ background-color: var(--color-surface-solid);
17
+ box-shadow: var(--shadow-overlay);
28
18
 
29
19
  .dropdown-item {
30
20
  @apply block p-2 cursor-pointer whitespace-nowrap select-none;
31
- @apply focus:bg-neutral-200/80 hover:bg-neutral-200/80;
32
- @apply active:bg-slate-400 active:text-white;
21
+ color: var(--color-content-primary);
22
+
23
+ &:hover,
24
+ &:focus-visible {
25
+ background-color: var(--state-hover);
26
+ }
27
+
28
+ &:active {
29
+ background-color: var(--state-pressed);
30
+ }
33
31
  }
34
32
  }
35
33
  }
@@ -4,7 +4,9 @@
4
4
 
5
5
  .input-group-text {
6
6
  @apply py-1.5 px-3 rounded-l flex flex-row items-center whitespace-nowrap select-none;
7
- @apply bg-slate-500 text-gray-50 border-blue-400;
7
+ background-color: var(--color-surface-inset);
8
+ color: var(--color-content-secondary);
9
+ border-color: var(--color-line-strong);
8
10
  }
9
11
 
10
12
  input[type='text'].form-input,
@@ -14,7 +16,8 @@
14
16
 
15
17
  &.ng-touched.ng-invalid {
16
18
  ~ .input-group-text {
17
- @apply bg-red-600;
19
+ background-color: var(--color-danger);
20
+ color: var(--color-content-on-accent);
18
21
  }
19
22
  }
20
23
  }
@@ -39,6 +42,7 @@
39
42
 
40
43
  .validations {
41
44
  @apply hidden basis-full mt-1;
42
- @apply text-red-600 text-sm;
45
+ @apply text-sm;
46
+ color: var(--color-danger);
43
47
  }
44
48
  }
@@ -1,14 +1,22 @@
1
1
  .form-input {
2
- @apply border-gray-400;
3
- @apply focus:border-gray-400 focus:ring-4 focus:ring-primary-400/30;
2
+ border-color: var(--color-line-strong);
3
+
4
+ &:focus-visible {
5
+ @apply outline-hidden ring-[length:var(--focus-ring-width)];
6
+ --tw-ring-color: var(--color-focus-ring);
7
+ }
4
8
  @apply transition-colors transition-shadow;
5
9
 
6
- @apply invalid:border-red-400;
10
+ &:invalid {
11
+ border-color: var(--color-danger);
12
+ }
7
13
 
8
- @apply disabled:bg-slate-100;
14
+ &:disabled {
15
+ background-color: var(--color-surface-inset);
16
+ }
9
17
 
10
18
  &.ng-invalid.ng-touched {
11
- @apply border-red-600;
19
+ border-color: var(--color-danger);
12
20
  }
13
21
  }
14
22
 
@@ -17,11 +25,14 @@ input[type='number'],
17
25
  input[type='password'],
18
26
  textarea {
19
27
  &.form-input {
20
- @apply mt-1 block w-full rounded-md shadow-xs;
28
+ @apply mt-1 block w-full shadow-xs;
29
+ border-radius: var(--radius-field);
21
30
 
22
31
  &.ng-invalid.ng-touched {
23
32
  @apply pr-9;
24
- @apply focus:ring-red-400/30;
33
+ &:focus-visible {
34
+ --tw-ring-color: var(--color-danger);
35
+ }
25
36
  @apply bg-no-repeat bg-[right_0.5625rem_center] bg-[length:1.125rem_1.125rem];
26
37
  background-image: url("data:image/svg+xml,%3csvg class='text-white' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
27
38
  }
@@ -31,12 +42,17 @@ textarea {
31
42
  input[type='checkbox'].form-input {
32
43
  @apply p-0.5 h-5 w-5 rounded-sm relative;
33
44
  @apply inline-flex flex-row justify-end;
34
- @apply appearance-none bg-white;
45
+ @apply appearance-none;
46
+ background-color: var(--color-surface-solid);
35
47
 
36
48
  @apply disabled:pointer-events-none disabled:opacity-50;
37
49
 
38
50
  &:not(.switch) {
39
- @apply border-2 shadow-md focus:ring-3 focus:shadow-none;
51
+ @apply border-2 shadow-md;
52
+
53
+ &:focus-visible {
54
+ @apply shadow-none;
55
+ }
40
56
 
41
57
  &:before {
42
58
  @apply content-[''] rounded-sm;
@@ -47,11 +63,13 @@ input[type='checkbox'].form-input {
47
63
  }
48
64
 
49
65
  &:checked {
50
- @apply bg-none bg-primary-400 border-primary-400/80;
66
+ @apply bg-none;
67
+ background-color: var(--color-accent);
68
+ border-color: var(--color-accent);
51
69
 
52
70
  &:before {
53
71
  @apply content-['\f00c'];
54
- @apply text-white;
72
+ color: var(--color-content-on-accent);
55
73
  }
56
74
  }
57
75
 
@@ -68,11 +86,12 @@ input[type='checkbox'].form-input {
68
86
 
69
87
  input[type='checkbox'].form-input.switch {
70
88
  @apply w-10 rounded-[2rem];
71
- @apply appearance-none bg-white;
89
+ @apply appearance-none;
90
+ background-color: var(--color-surface-solid);
72
91
 
73
92
  &:before {
74
93
  @apply content-[''] h-full aspect-square rounded-full;
75
- @apply bg-slate-500/60;
94
+ background-color: var(--color-content-subtle);
76
95
  }
77
96
 
78
97
  &:after {
@@ -80,16 +99,18 @@ input[type='checkbox'].form-input.switch {
80
99
  transition: flex-grow 0.15s ease-in-out;
81
100
  }
82
101
 
83
- &:focus:before {
84
- @apply bg-slate-500/60;
102
+ &:focus-visible:before {
103
+ background-color: var(--color-accent);
85
104
  }
86
105
 
87
106
  &:checked {
88
- @apply bg-none bg-primary-400 border-primary-400/80;
107
+ @apply bg-none;
108
+ background-color: var(--color-accent);
109
+ border-color: var(--color-accent);
89
110
 
90
111
  &:before {
91
112
  @apply content-[''];
92
- @apply bg-white;
113
+ background-color: var(--color-surface-solid);
93
114
  }
94
115
 
95
116
  &:after {
@@ -1,3 +1,6 @@
1
1
  .form-label {
2
- @apply block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2;
2
+ @apply block mb-2;
3
+ font-size: var(--text-caption);
4
+ font-weight: var(--font-weight-emphasis);
5
+ color: var(--color-content-secondary);
3
6
  }
package/styles/theme.css CHANGED
@@ -30,6 +30,13 @@
30
30
 
31
31
  @plugin '@tailwindcss/forms';
32
32
 
33
+ /*
34
+ * Tailwind's `dark:` has to follow the same switch the tokens do. Left as the default it keys off
35
+ * the operating system, so the one `dark:bg-gray-700` in this repo already fires on a dark OS while
36
+ * nothing else does -- a skeleton that goes dark inside an otherwise light screen.
37
+ */
38
+ @custom-variant dark (&:where([data-theme='dark'], [data-theme='dark'] *));
39
+
33
40
  /*
34
41
  * Tailwind 4 changed the default border colour from `gray-200` to `currentColor`, so every element
35
42
  * with a border and no colour of its own inherits the text colour. Across the Storybooks that was
@@ -73,3 +80,228 @@
73
80
  --color-primary-800: #001b2f;
74
81
  --color-primary-900: #00070d;
75
82
  }
83
+
84
+ /*
85
+ * ---------------------------------------------------------------------------------------------
86
+ * The semantic core.
87
+ *
88
+ * Everything above this line is Tailwind's own theme: `--color-primary-*` is the raw brand ramp.
89
+ * Everything below names a *job* rather than a value -- a surface, a piece of content, a line, a
90
+ * state -- so that a component asks for the role it needs and a theme decides what that looks
91
+ * like. The component tokens that already shipped (`--sidebar-*`, `--tabs-*`) stay exactly where
92
+ * they are and derive from these; they are the published contract, this is the implementation.
93
+ *
94
+ * Every value here is the one those components already compute today. The core was read out of
95
+ * them, not invented, which is why adopting it changes no pixel.
96
+ *
97
+ * `static` is load-bearing, and not for the reason the `theme(static)` on the import above is.
98
+ * That option covers the theme Tailwind itself imports; a second `@theme` block is pruned on its
99
+ * own terms, and these tokens are referenced from other files, which the pruner cannot see. Built
100
+ * without it, every one of them is dropped and the dark block below is the only thing left --
101
+ * so the light theme resolves to nothing while the dark theme works. Nothing reports this.
102
+ * ---------------------------------------------------------------------------------------------
103
+ */
104
+ @theme static {
105
+ /*
106
+ * Surfaces name the plane, not the colour. The language is "translucent cards floating over the
107
+ * backdrop", and what separates one level from the next is how much of the backdrop it lets
108
+ * through. The 45/70/88 ladder is the same one the tabs use for rest/hover/active -- not a
109
+ * coincidence, and the reason it lives here: hovering a tab is literally climbing one surface.
110
+ */
111
+ --color-surface-brand: color-mix(in srgb, var(--color-primary-600) 82%, transparent);
112
+ --color-surface-glass-weak: color-mix(in srgb, white 45%, transparent);
113
+ --color-surface-glass: color-mix(in srgb, white 70%, transparent);
114
+ --color-surface-glass-strong: color-mix(in srgb, white 88%, transparent);
115
+ --color-surface-solid: var(--color-white);
116
+ --color-surface-inset: color-mix(in srgb, var(--color-gray-200) 40%, transparent);
117
+ --color-surface-scrim: color-mix(in srgb, black 50%, transparent);
118
+
119
+ /*
120
+ * "Content" and not "text": half the consumers are icons, `::before` glyphs and spinners.
121
+ * There is no `-disabled` here because disabled is an opacity on the element everywhere in
122
+ * this repo, never a colour -- see --opacity-disabled.
123
+ */
124
+ --color-content-primary: var(--color-gray-900);
125
+ --color-content-secondary: var(--color-gray-600);
126
+ --color-content-subtle: var(--color-gray-400);
127
+ --color-content-on-brand: var(--color-gray-100);
128
+ --color-content-on-brand-muted: color-mix(in srgb, var(--color-gray-100) 60%, transparent);
129
+ --color-content-on-accent: var(--color-white);
130
+
131
+ /* "Line" covers border, divider and rule in one word; half of these are 1px pseudo-elements. */
132
+ --color-line: var(--color-gray-200);
133
+ --color-line-strong: color-mix(in srgb, var(--color-gray-400) 90%, transparent);
134
+ --color-line-on-glass: color-mix(in srgb, white 60%, transparent);
135
+ --color-line-on-brand: color-mix(in srgb, white 80%, var(--color-primary-600));
136
+
137
+ /*
138
+ * "Accent" rather than "primary": `--color-primary-*` is already the raw ramp, so this names the
139
+ * rung we chose. Retuning the brand becomes an edit here instead of a find-and-replace by rung.
140
+ */
141
+ --color-accent: var(--color-primary-600);
142
+ --color-accent-strong: var(--color-primary-700);
143
+ --color-accent-weak: var(--color-primary-50);
144
+
145
+ /* Focus is its own family because it is the one state that has to survive every theme and every
146
+ surface, and because this repo currently has two generations of it to collapse. */
147
+ --color-focus-ring: var(--color-primary-400);
148
+
149
+ /* Two per role, because each appears exactly two ways: as ink (a validation message, a badge)
150
+ and as a 75% fill (the ribbon button's status). A third variant is a color-mix at the point
151
+ of use. */
152
+ --color-danger: var(--color-red-600);
153
+ --color-danger-surface: color-mix(in srgb, var(--color-red-600) 75%, transparent);
154
+ --color-warning: var(--color-yellow-600);
155
+ --color-warning-surface: color-mix(in srgb, var(--color-yellow-600) 75%, transparent);
156
+ --color-success: var(--color-green-600);
157
+ --color-success-surface: color-mix(in srgb, var(--color-green-600) 75%, transparent);
158
+ --color-info: var(--color-primary-400);
159
+ --color-info-surface: color-mix(in srgb, var(--color-primary-400) 75%, transparent);
160
+
161
+ /*
162
+ * Shapes, not a t-shirt scale: `sm/md/lg` invites copying a size instead of asking what kind of
163
+ * thing this is. --radius-nested keeps a child's corners parallel to its parent's, which is only
164
+ * true when the inner radius is the outer radius minus the gap between them.
165
+ */
166
+ --radius-surface: 1.25rem;
167
+ --radius-control: 10px;
168
+ --radius-field: 0.375rem;
169
+ --radius-nested: calc(var(--radius-surface) - 0.5rem - 1px);
170
+
171
+ /* --shadow-overlay is the two-layer pair currently written out by hand in four files. */
172
+ --shadow-float: 0 10px 40px -12px rgb(0 0 0 / 0.25);
173
+ --shadow-float-strong: 0 10px 40px -12px rgb(0 0 0 / 0.45);
174
+ --shadow-overlay: 0 0 4px 0 rgb(0 0 0 / 0.2), 0 3px 20px 0 rgb(0 0 0 / 0.19);
175
+
176
+ /* One value for glass is deliberate: two translucent surfaces only match when the blur beneath
177
+ them matches too. The popover blur exists because a menu shares its plane with nothing. */
178
+ --blur-glass: 16px;
179
+ --blur-popover: 12px;
180
+
181
+ --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
182
+ --ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
183
+
184
+ /*
185
+ * Roles, not a scale. There is no typographic scale here to preserve -- there are five jobs, and
186
+ * naming them is what stops `text-sm` vs `text-base` from being chosen by feel. These are new
187
+ * names inside Tailwind's `--text-*` namespace; overriding `--text-sm` would move the whole
188
+ * scale. No name may exist in both `--color-*` and `--text-*`, or `text-<name>` turns ambiguous.
189
+ */
190
+ --text-caption: 0.75rem;
191
+ --text-body: 0.875rem;
192
+ --text-ui: 1rem;
193
+ --text-title: 1.125rem;
194
+ --text-display: 1.5rem;
195
+ --font-weight-ui: 500;
196
+ --font-weight-emphasis: 600;
197
+ --font-weight-strong: 700;
198
+ }
199
+
200
+ /*
201
+ * The rest of the core. These have no Tailwind namespace to live in -- there is no `--state-*` or
202
+ * `--duration-*` in the framework -- so they are plain custom properties. A declaration in a bare
203
+ * `:root` is never pruned, so they need no help from theme(static).
204
+ */
205
+ :root {
206
+ /* State is a wash, expressed as a mix rather than a colour, because the established pattern is a
207
+ ladder of opacity over whatever is underneath -- not a different colour per state. */
208
+ --state-hover: color-mix(in srgb, var(--color-gray-900) 6%, transparent);
209
+ --state-pressed: color-mix(in srgb, var(--color-gray-900) 10%, transparent);
210
+ --state-selected: color-mix(in srgb, var(--color-primary-200) 25%, transparent);
211
+ --state-hover-on-brand: color-mix(in srgb, white 12%, transparent);
212
+ --state-selected-on-brand: color-mix(in srgb, white 22%, transparent);
213
+
214
+ --opacity-disabled: 0.6;
215
+
216
+ --focus-ring-width: 2px;
217
+ --focus-ring-offset: 0px;
218
+
219
+ /* Named for how they feel, not what they measure: a `--duration-200` is a number with extra
220
+ steps. Routing every transition through these is what makes the reduced-motion block below a
221
+ single rule instead of an audit. */
222
+ --duration-instant: 120ms;
223
+ --duration-short: 200ms;
224
+ --duration-long: 600ms;
225
+ }
226
+
227
+ /*
228
+ * One rule turns off the 600ms margin/width/rotate transitions across the whole repo, because every
229
+ * transition reads a duration token. This is the return on naming them.
230
+ */
231
+ @media (prefers-reduced-motion: reduce) {
232
+ :root {
233
+ --duration-instant: 1ms;
234
+ --duration-short: 1ms;
235
+ --duration-long: 1ms;
236
+ }
237
+ }
238
+
239
+ /*
240
+ * ---------------------------------------------------------------------------------------------
241
+ * The dark theme.
242
+ *
243
+ * An attribute on <html>, not `prefers-color-scheme`. A media query would take the choice away
244
+ * from both sides: a consuming application could not opt out of a dark theme, and a user could not
245
+ * choose light on a dark OS. "Follow the system" is still the behaviour we want, but it is read in
246
+ * TypeScript and written here as `dark` -- so this file holds one block instead of two that have
247
+ * to be kept identical. The CSS never sees "system".
248
+ *
249
+ * Unlayered, so it beats the `@layer theme` block Tailwind emits above without !important.
250
+ *
251
+ * It restates colour and shadow only -- radius, blur, motion and type do not move between themes,
252
+ * and that is verifiable rather than asserted. It touches *only* core tokens, never `--sidebar-*`
253
+ * or `--tabs-*`: an application that overrides one of those keeps its override in both themes
254
+ * without doing anything.
255
+ * ---------------------------------------------------------------------------------------------
256
+ */
257
+ :root {
258
+ color-scheme: light;
259
+ }
260
+
261
+ :root[data-theme='dark'] {
262
+ color-scheme: dark;
263
+
264
+ --app-backdrop: linear-gradient(160deg, var(--color-gray-900) 0%, var(--color-gray-800) 100%);
265
+
266
+ --color-surface-brand: color-mix(in srgb, var(--color-primary-800) 82%, transparent);
267
+ --color-surface-glass-weak: color-mix(in srgb, var(--color-gray-900) 45%, transparent);
268
+ --color-surface-glass: color-mix(in srgb, var(--color-gray-900) 70%, transparent);
269
+ --color-surface-glass-strong: color-mix(in srgb, var(--color-gray-900) 88%, transparent);
270
+ --color-surface-solid: var(--color-gray-900);
271
+ --color-surface-inset: color-mix(in srgb, var(--color-gray-700) 40%, transparent);
272
+ --color-surface-scrim: color-mix(in srgb, black 70%, transparent);
273
+
274
+ --color-content-primary: var(--color-gray-100);
275
+ --color-content-secondary: var(--color-gray-400);
276
+ --color-content-subtle: var(--color-gray-500);
277
+ /* The on-brand pair does not move: the brand surface is dark in both themes. */
278
+
279
+ --color-line: var(--color-gray-700);
280
+ --color-line-strong: var(--color-gray-600);
281
+ --color-line-on-glass: color-mix(in srgb, white 12%, transparent);
282
+ --color-line-on-brand: color-mix(in srgb, white 45%, var(--color-primary-800));
283
+
284
+ --color-accent: var(--color-primary-300);
285
+ --color-accent-strong: var(--color-primary-200);
286
+ --color-accent-weak: color-mix(in srgb, var(--color-primary-300) 18%, transparent);
287
+ --color-focus-ring: var(--color-primary-300);
288
+
289
+ /* The wash inverts from black to white; the on-brand rungs stay white in both themes. */
290
+ --state-hover: color-mix(in srgb, white 8%, transparent);
291
+ --state-pressed: color-mix(in srgb, white 14%, transparent);
292
+ --state-selected: color-mix(in srgb, var(--color-primary-300) 22%, transparent);
293
+
294
+ --color-danger: var(--color-red-400);
295
+ --color-danger-surface: color-mix(in srgb, var(--color-red-400) 75%, transparent);
296
+ --color-warning: var(--color-yellow-400);
297
+ --color-warning-surface: color-mix(in srgb, var(--color-yellow-400) 75%, transparent);
298
+ --color-success: var(--color-green-400);
299
+ --color-success-surface: color-mix(in srgb, var(--color-green-400) 75%, transparent);
300
+ --color-info: var(--color-primary-300);
301
+ --color-info-surface: color-mix(in srgb, var(--color-primary-300) 75%, transparent);
302
+
303
+ /* Shadows carry further on a dark backdrop before they read as depth. */
304
+ --shadow-float: 0 10px 40px -12px rgb(0 0 0 / 0.7);
305
+ --shadow-float-strong: 0 10px 40px -12px rgb(0 0 0 / 0.85);
306
+ --shadow-overlay: 0 0 4px 0 rgb(0 0 0 / 0.5), 0 3px 20px 0 rgb(0 0 0 / 0.6);
307
+ }