@truenas/ui-components 0.5.3 → 0.6.2

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.
@@ -4,26 +4,112 @@
4
4
  :root {
5
5
  --tn-primary: #007db3; /* WCAG fix #007db3 */
6
6
  --tn-primary-txt: #ffffff;
7
+ /* --tn-primary DRAWN AS TEXT. Not to be confused with --tn-primary-txt above,
8
+ which is the opposite pairing: text drawn ON a --tn-primary fill.
9
+
10
+ --tn-primary is tuned for the 3:1 non-text minimum (fills, borders, focus
11
+ rings) and is only 3.64:1 on --tn-bg1 here, so a link or an accent label
12
+ reading it as `color:` inherits a failure. This is the same companion-token
13
+ pattern as --tn-error-text below, and every theme declares its own: the
14
+ value is a hue-preserving lightness shift of that theme's --tn-primary,
15
+ chosen as the smallest step that clears 4.5:1 on both --tn-bg1 and
16
+ --tn-bg2. >=4.5:1 against both --tn-bg1 (5.22:1) and --tn-bg2 (4.62:1). */
17
+ --tn-primary-text: #0099db;
7
18
  --tn-btn-default-bg: #545454;
8
19
  --tn-btn-default-txt: #ffffff;
9
20
  --tn-topbar: #111111;
10
- --tn-topbar-txt: rgba(255,255,255,0.85);
21
+ /* TEXT DRAWN ON THE --tn-topbar BAR, and on nothing else: tn-table's header
22
+ is the only thing in this library that reads it. That makes its one
23
+ pairing its whole job, so every palette's value clears 4.5:1 on that
24
+ palette's own bar and records the ratio — unlike --tn-fg1/--tn-fg2, which
25
+ answer to the page surfaces and cannot also answer to a coloured bar. Four
26
+ palettes did not, and #277 gave each its own value: .tn-blue inherited
27
+ this one onto a mid-blue bar at 3.75:1, .tn-dracula read 4.09:1,
28
+ .tn-solarized-dark 3.39:1 and .tn-midnight 3.12:1. The two that still
29
+ inherit are the two with dark bars — .tn-paper at 6.12:1 and
30
+ .tn-high-contrast at 14.84:1 — where this value is already the right one.
31
+ text-token-surface-contrast.spec.ts holds all nine to it. */
32
+ --tn-topbar-txt: rgba(255,255,255,0.85); /* 13.68:1 on --tn-topbar */
33
+ /* THE HOVERED --tn-topbar BAR: tn-table paints it when a sortable header is
34
+ under the pointer, and the label on it is still --tn-topbar-txt.
35
+
36
+ Its own token in every palette because the fill used to be DERIVED —
37
+ color-mix(in srgb, var(--tn-topbar) 85%, var(--tn-topbar-txt)) — and a mix
38
+ toward the label moves the surface toward the text, so the label's ratio
39
+ could only fall. It fell under 4.5:1 on the four mid-tone bars: .tn-blue
40
+ 3.59:1, .tn-dracula 3.54:1, .tn-solarized-dark 3.92:1 and .tn-midnight
41
+ 3.83:1 (#284).
42
+
43
+ Each value is a hue-preserving lightness shift of that palette's own
44
+ --tn-topbar — the same construction as --tn-primary-text above — taken as
45
+ the smallest whole-percent step whose contrast AGAINST the bar reaches
46
+ 1.30:1, so the hover stays visible. That floor is read off the derivation
47
+ it replaces rather than invented: the mix moved the bar by between 1.28:1
48
+ (.tn-blue) and 1.42:1 (.tn-nord), so 1.30:1 sits inside the band it
49
+ already produced and above its weakest palette.
50
+
51
+ THE DIRECTION IS NOT THE SAME IN EVERY PALETTE, and that is the whole
52
+ reason one derivation could not serve all nine. It is away from the label
53
+ where the bar has room to go there, and toward it only where the bar is
54
+ already near-black and darkening would not be visible at all: :root,
55
+ .tn-dark and .tn-high-contrast lighten, the other six darken.
56
+
57
+ Both ratios are recorded on every palette's value — the label on the hover
58
+ fill, which is the 4.5:1 claim, then the hover fill against the bar, which
59
+ is the affordance. text-token-surface-contrast.spec.ts holds all nine to
60
+ both, and to declaring this themselves: a palette inheriting this value
61
+ would be hovering its own bar to a colour shifted off :root's. */
62
+ --tn-topbar-hover: #2b2b2b; /* 10.64:1 under --tn-topbar-txt; 1.33:1 against --tn-topbar */
11
63
  --tn-accent: #545454;
64
+ /* TEXT DRAWN ON A --tn-accent FILL — the companion --tn-accent never had, and
65
+ the same arrangement as --tn-primary / --tn-primary-txt above.
66
+
67
+ --tn-accent is a fill colour and nothing constrains its luminance: it is
68
+ #545454 here and #DED142 in TN Dark. A foreground picked once for both
69
+ cannot work, and tn-chip's accent label picked --tn-fg1, which measured
70
+ 1.20:1 on the yellow and failed in six of the nine palettes (#238). Every
71
+ theme declares its own, preferring a colour already in that theme — its
72
+ own --tn-bg1, or the near-black its light surfaces use — and falling back
73
+ to plain white or black where the accent's luminance leaves the palette no
74
+ value that clears 4.5:1. The measured ratio is on each. */
75
+ --tn-accent-txt: #ffffff; /* 7.57:1 on --tn-accent */
12
76
  --tn-bg1: #1E1E1E;
13
77
  --tn-bg2: #282828;
14
78
  --tn-bg3: #333333;
15
79
  --tn-fg1: #ffffff;
16
80
  --tn-fg2: rgba(255,255,255,0.85);
17
- --tn-fg3: rgba(255,255,255,0.55);
18
- --tn-fg4: rgba(255,255,255,0.35);
81
+ /* NON-TEXT foregrounds. They were documented as "muted text" and "very
82
+ subdued text" and cannot carry that (#240): measured on the surfaces they
83
+ would sit on, --tn-fg4 cleared the 4.5:1 text minimum in NONE of the nine
84
+ palettes and --tn-fg3 in four. Retuning them to clear it was tried on
85
+ paper and is self-defeating — on a light palette the value that reaches
86
+ 4.5:1 lands within a shade of that theme's own --tn-fg2, so the muted step
87
+ stops being distinguishable from secondary text, which is the entire
88
+ reason these tokens exist. Muted TEXT is --tn-alt-fg1, which is tuned for
89
+ exactly that and says so above.
90
+
91
+ What they guarantee instead is the 3:1 WCAG 1.4.11 non-text minimum
92
+ against --tn-bg1 and --tn-bg2 — icon and glyph strokes, decorative marks,
93
+ inactive affordances — and nothing on any other surface. Every palette
94
+ declares both itself and records the pair it measures;
95
+ muted-fg-contrast.spec.ts holds all nine to it. */
96
+ --tn-fg3: rgba(255,255,255,0.55); /* 5.91:1 on --tn-bg1, 5.52:1 on --tn-bg2 */
97
+ --tn-fg4: rgba(255,255,255,0.35); /* 3.21:1 on --tn-bg1, 3.12:1 on --tn-bg2 */
19
98
  --tn-alt-bg1: #383838;
20
99
  --tn-alt-bg2: #545454;
21
- --tn-alt-fg1: rgba(194,194,194,0.5);
100
+ /* Muted body text (placeholders, group labels, breadcrumb separators). At 0.5
101
+ it composited to #707070 and read 3.37:1 on --tn-bg1; 0.8 gives >=4.5:1 on
102
+ every dark surface it is drawn on: --tn-bg1 6.47:1, --tn-bg2 5.86:1,
103
+ --tn-bg3 5.15:1, --tn-alt-bg1 4.84:1 (the stepper draws it there). */
104
+ --tn-alt-fg1: rgba(194,194,194,0.8);
22
105
  --tn-alt-fg2: #e1e1e1;
23
106
  --tn-lines: #383838;
24
107
  --tn-yellow: #DED142;
25
108
  --tn-orange: #E68D37;
26
109
  --tn-red: #CE2929;
110
+ /* >=4.5:1 against both --tn-bg1 (5.17:1) and --tn-bg2 (4.57:1); --tn-red itself
111
+ is only 3.15:1 on --tn-bg1, tuned for the 3:1 border/icon minimum. */
112
+ --tn-error-text: #de6d6d;
27
113
  --tn-magenta: #C006C7;
28
114
  --tn-violet: #7617D8;
29
115
  --tn-blue: #007db3;
@@ -35,6 +121,30 @@
35
121
  --tn-teal: #008080;
36
122
  --tn-slategray: #708090;
37
123
  --tn-salmon: #fa8072;
124
+ /* SEMANTIC STATUS COLOURS. Drawn as text — banner headings, toast and card
125
+ status labels, form error messages, the required asterisk — as well as
126
+ borders and icons, so each clears the 4.5:1 text minimum rather than the
127
+ 3:1 non-text one. Every theme declares its own: the value is a
128
+ hue-preserving lightness shift of THAT theme's own hue token (--tn-blue,
129
+ --tn-orange, --tn-red, --tn-green), the smallest step that clears 4.5:1 on
130
+ all three surfaces a status colour is painted on — --tn-alt-bg1 (the
131
+ banner), --tn-bg1 (the page, the file-picker popup) and --tn-bg2 (the card,
132
+ the toast) — and on the 10% wash of itself that tn-file-picker's inline
133
+ error paints behind it. Ratios below are on --tn-alt-bg1 / --tn-bg1 /
134
+ --tn-bg2.
135
+
136
+ --tn-error is NOT --tn-error-text. That one is --tn-red's companion and
137
+ guarantees 4.5:1 on --tn-bg1 and --tn-bg2 only; it measures 3.64:1 on
138
+ --tn-alt-bg1 here, where tn-banner draws an error heading.
139
+
140
+ A reader spells the whole chain — var(--tn-error, var(--tn-red, #bd2626)) —
141
+ as radio.component.scss does for --tn-error-text, so a consumer stylesheet
142
+ that predates these four keeps its own palette; semantic-status-contrast
143
+ .spec.ts pins that shape and the literals. */
144
+ --tn-info: #00adf8; /* 4.65:1 / 6.62:1 / 5.85:1; --tn-blue is 2.56:1 on --tn-alt-bg1 */
145
+ --tn-warning: var(--tn-orange); /* 4.60:1 / 6.53:1 / 5.78:1 — needs no shade of its own */
146
+ --tn-error: #e78888; /* 4.62:1 / 6.57:1 / 5.81:1; --tn-red is 2.22:1 on --tn-alt-bg1 */
147
+ --tn-success: var(--tn-green); /* 5.16:1 / 7.33:1 / 6.49:1 — needs no shade of its own */
38
148
  --tn-font-family-header: "Titillium Web", sans-serif;
39
149
  --tn-font-family-body: "IBM Plex Sans", "Roboto", "Helvetica Neue", sans-serif;
40
150
  --tn-font-family-monospace: "Droid Sans Mono", "Courier New", Courier, monospace;
@@ -65,24 +175,39 @@
65
175
  ================================ */
66
176
  .tn-dark {
67
177
  --tn-primary: var(--tn-blue);
178
+ --tn-primary-txt: #ffffff; /* 4.58:1 on --tn-primary */
179
+ /* >=4.5:1 against both --tn-bg1 (5.22:1) and --tn-bg2 (4.62:1); --tn-primary
180
+ itself is only 3.64:1 on --tn-bg1 — see the note in :root. */
181
+ --tn-primary-text: #0099db;
68
182
  --tn-topbar: #111111;
69
- --tn-topbar-txt: rgba(255,255,255,0.85);
183
+ --tn-topbar-txt: rgba(255,255,255,0.85); /* 13.68:1 on --tn-topbar */
184
+ /* Lightened, this bar being near-black — see the note in :root. */
185
+ --tn-topbar-hover: #2b2b2b; /* 10.64:1 under --tn-topbar-txt; 1.33:1 against --tn-topbar */
70
186
  --tn-accent: var(--tn-yellow);
187
+ /* The yellow this resolves to is the brightest accent of the nine, so the
188
+ text on it is this theme's own --tn-bg1 rather than a light shade. */
189
+ --tn-accent-txt: #1E1E1E; /* 10.56:1 on --tn-accent */
71
190
  --tn-bg1: #1E1E1E;
72
191
  --tn-bg2: #282828;
73
192
  --tn-bg3: #333333;
74
193
  --tn-fg1: #e0e0e0;
75
194
  --tn-fg2: rgba(255,255,255,0.85);
76
- --tn-fg3: rgba(255,255,255,0.55);
77
- --tn-fg4: rgba(255,255,255,0.35);
195
+ /* Non-text, 3:1 on --tn-bg1/--tn-bg2 — see the note in :root (#240). */
196
+ --tn-fg3: rgba(255,255,255,0.55); /* 5.91:1 on --tn-bg1, 5.52:1 on --tn-bg2 */
197
+ --tn-fg4: rgba(255,255,255,0.35); /* 3.21:1 on --tn-bg1, 3.12:1 on --tn-bg2 */
78
198
  --tn-alt-bg1: #383838;
79
199
  --tn-alt-bg2: #545454;
80
- --tn-alt-fg1: rgba(194,194,194,0.5);
200
+ /* Muted body text. 0.5 read 3.37:1 on --tn-bg1; 0.8 gives >=4.5:1 on every
201
+ dark surface it is drawn on — see the note in :root. */
202
+ --tn-alt-fg1: rgba(194,194,194,0.8);
81
203
  --tn-alt-fg2: #e1e1e1;
82
204
  --tn-lines: #4a4a4a;
83
205
  --tn-yellow: #DED142;
84
206
  --tn-orange: #E68D37;
85
207
  --tn-red: #CE2929;
208
+ /* >=4.5:1 against both --tn-bg1 (5.17:1) and --tn-bg2 (4.57:1); --tn-red itself
209
+ is only 3.15:1 on --tn-bg1. */
210
+ --tn-error-text: #de6d6d;
86
211
  --tn-magenta: #C006C7;
87
212
  --tn-violet: #7617D8;
88
213
  --tn-blue: #007db3;
@@ -94,6 +219,12 @@
94
219
  --tn-teal: #008080;
95
220
  --tn-slategray: #708090;
96
221
  --tn-salmon: #fa8072;
222
+ /* Semantic status colours — see the note in :root. On --tn-alt-bg1 /
223
+ --tn-bg1 / --tn-bg2. */
224
+ --tn-info: #00adf8; /* 4.65:1 / 6.62:1 / 5.85:1 */
225
+ --tn-warning: var(--tn-orange); /* 4.60:1 / 6.53:1 / 5.78:1 */
226
+ --tn-error: #e78888; /* 4.62:1 / 6.57:1 / 5.81:1 */
227
+ --tn-success: var(--tn-green); /* 5.16:1 / 7.33:1 / 6.49:1 */
97
228
  }
98
229
 
99
230
  /* ================================
@@ -102,15 +233,32 @@
102
233
  .tn-blue {
103
234
  --tn-primary: var(--tn-blue);
104
235
  --tn-primary-txt: #ffffff;
236
+ /* >=4.5:1 against both --tn-bg1 (4.62:1) and --tn-bg2 (5.18:1); --tn-primary
237
+ itself is only 4.09:1 on --tn-bg1. Darkened here rather than lightened —
238
+ this is a light theme. */
239
+ --tn-primary-text: #0074a7;
105
240
  --tn-topbar: #007db3;
241
+ /* Declared rather than inherited: :root's rgba(255,255,255,0.85) composites
242
+ to 3.75:1 on this bar. White is the whole of the remaining headroom and it
243
+ clears by 0.08 (#277) — a bar any lighter than #007db3 would leave this
244
+ palette with no legible label at all. */
245
+ --tn-topbar-txt: #ffffff; /* 4.58:1 on --tn-topbar */
246
+ /* Darkened — see the note in :root. This is the palette the old derivation
247
+ hurt most: white had 0.08 of headroom on the bar and the mix spent it. */
248
+ --tn-topbar-hover: #006894; /* 6.16:1 under --tn-topbar-txt; 1.35:1 against --tn-topbar */
106
249
  --tn-accent: #DED142;
250
+ --tn-accent-txt: #282a36; /* 9.02:1 on --tn-accent */
107
251
  --tn-bg1: #f2f2f2;
108
252
  --tn-bg2: #ffffff;
109
253
  --tn-bg3: #e8e8e8;
110
254
  --tn-fg1: #585858;
111
255
  --tn-fg2: #666666;
112
- --tn-fg3: #888888;
113
- --tn-fg4: #909090;
256
+ /* Non-text, 3:1 on --tn-bg1/--tn-bg2 — see the note in :root (#240). #888888
257
+ was 3.17:1 and #909090 2.85:1, so --tn-fg4 missed the minimum outright and
258
+ --tn-fg3 sat close enough to it to leave --tn-fg4 nowhere to go. Both step
259
+ one shade darker, which keeps the ~0.3 the ramp had between them. */
260
+ --tn-fg3: #7f7f7f; /* 3.58:1 on --tn-bg1, 4.00:1 on --tn-bg2 */
261
+ --tn-fg4: #868686; /* 3.25:1 on --tn-bg1, 3.64:1 on --tn-bg2 */
114
262
  --tn-alt-bg1: #f0f0f0;
115
263
  --tn-alt-bg2: #e8e8e8;
116
264
  --tn-alt-fg1: #181a26;
@@ -119,6 +267,8 @@
119
267
  --tn-yellow: #DED142;
120
268
  --tn-orange: #E68D37;
121
269
  --tn-red: #CE2929;
270
+ /* --tn-red already clears 4.5:1 against --tn-bg1 here (measured 4.72:1). */
271
+ --tn-error-text: var(--tn-red);
122
272
  --tn-magenta: #C006C7;
123
273
  --tn-violet: #7617D8;
124
274
  --tn-blue: #007db3;
@@ -130,6 +280,13 @@
130
280
  --tn-teal: #008080;
131
281
  --tn-slategray: #708090;
132
282
  --tn-salmon: #fa8072;
283
+ /* Semantic status colours — see the note in :root. Darkened rather than
284
+ lightened, this being a light theme. On --tn-alt-bg1 / --tn-bg1 /
285
+ --tn-bg2. */
286
+ --tn-info: #006997; /* 5.31:1 / 5.41:1 / 6.06:1 */
287
+ --tn-warning: #955313; /* 5.23:1 / 5.32:1 / 5.96:1 */
288
+ --tn-error: #bd2626; /* 5.31:1 / 5.41:1 / 6.05:1 */
289
+ --tn-success: #416f26; /* 5.23:1 / 5.32:1 / 5.95:1 */
133
290
  }
134
291
 
135
292
  /* ================================
@@ -138,16 +295,27 @@
138
295
  .tn-dracula {
139
296
  --tn-primary: #6272a4;
140
297
  --tn-primary-txt: #ffffff;
298
+ /* >=4.5:1 against both --tn-bg1 (5.60:1) and --tn-bg2 (4.61:1); --tn-primary
299
+ itself is only 3.67:1 on --tn-bg1. */
300
+ --tn-primary-text: #8592b8;
141
301
  --tn-topbar: #6272a4;
142
- --tn-topbar-txt: #efefef;
302
+ /* Was #efefef, which read 4.09:1 on this bar (#277). Dracula's own two
303
+ lighter tones do not save it either — --tn-alt-fg1 (#f8f8f2) reads 4.41:1
304
+ here and --tn-alt-fg2 (#fafaf5) 4.49:1 — so nothing already in the palette
305
+ clears 4.5:1 on this bar; white does, by 0.21. */
306
+ --tn-topbar-txt: #ffffff; /* 4.71:1 on --tn-topbar */
307
+ /* Darkened — see the note in :root. */
308
+ --tn-topbar-hover: #515f8c; /* 6.24:1 under --tn-topbar-txt; 1.33:1 against --tn-topbar */
143
309
  --tn-accent: #bd93f9;
310
+ --tn-accent-txt: #282a36; /* 5.90:1 on --tn-accent */
144
311
  --tn-bg1: #181a26;
145
312
  --tn-bg2: #282a36;
146
313
  --tn-bg3: #343746;
147
314
  --tn-fg1: #efefef;
148
315
  --tn-fg2: #cacac5;
149
- --tn-fg3: #9a9a95;
150
- --tn-fg4: #7a7a75;
316
+ /* Non-text, 3:1 on --tn-bg1/--tn-bg2 — see the note in :root (#240). */
317
+ --tn-fg3: #9a9a95; /* 6.12:1 on --tn-bg1, 5.04:1 on --tn-bg2 */
318
+ --tn-fg4: #7a7a75; /* 4.01:1 on --tn-bg1, 3.30:1 on --tn-bg2 */
151
319
  --tn-alt-bg1: #3a3d4a;
152
320
  --tn-alt-bg2: #44475a;
153
321
  --tn-alt-fg1: #f8f8f2;
@@ -156,6 +324,8 @@
156
324
  --tn-yellow: #f1fa8c;
157
325
  --tn-orange: #ffb86c;
158
326
  --tn-red: #ff5555;
327
+ /* --tn-red already clears 4.5:1 against --tn-bg1 here (measured 5.50:1). */
328
+ --tn-error-text: var(--tn-red);
159
329
  --tn-magenta: #ff79c6;
160
330
  --tn-violet: #bd93f9;
161
331
  --tn-blue: #6272a4;
@@ -167,6 +337,12 @@
167
337
  --tn-teal: #008080;
168
338
  --tn-slategray: #708090;
169
339
  --tn-salmon: #fa8072;
340
+ /* Semantic status colours — see the note in :root. On --tn-alt-bg1 /
341
+ --tn-bg1 / --tn-bg2. */
342
+ --tn-info: #9fa9c8; /* 4.62:1 / 7.40:1 / 6.09:1 */
343
+ --tn-warning: var(--tn-orange); /* 6.33:1 / 10.15:1 / 8.36:1 */
344
+ --tn-error: #ff8888; /* 4.69:1 / 7.52:1 / 6.19:1 */
345
+ --tn-success: var(--tn-green); /* 7.86:1 / 12.60:1 / 10.38:1 */
170
346
  }
171
347
 
172
348
  /* ================================
@@ -175,16 +351,39 @@
175
351
  .tn-nord {
176
352
  --tn-primary: #88c0d0;
177
353
  --tn-primary-txt: #333333;
354
+ /* --tn-primary already clears 4.5:1 here, against both --tn-bg1 (6.24:1) and
355
+ --tn-bg2 (5.03:1), so this theme needs no separate shade. Declared anyway
356
+ rather than left to inherit :root's — that value is tuned for :root's
357
+ surfaces, not these. */
358
+ --tn-primary-text: var(--tn-primary);
178
359
  --tn-topbar: #4c566a;
179
- --tn-topbar-txt: #eceff4;
360
+ --tn-topbar-txt: #eceff4; /* 6.40:1 on --tn-topbar */
361
+ /* Darkened — see the note in :root. Not one of Nord's own Polar Night tones,
362
+ unlike the bar above it: the palette spends all three darker ones as
363
+ surfaces, #434c5e on --tn-alt-bg1, #3b4252 on --tn-bg2 and #2e3440 on
364
+ --tn-bg1, so a hover taken from the ramp would render as one of them. */
365
+ --tn-topbar-hover: #3d4555; /* 8.35:1 under --tn-topbar-txt; 1.31:1 against --tn-topbar */
180
366
  --tn-accent: #5e81aC;
367
+ /* Black, where every other theme uses one of its own surfaces. Nord's accent
368
+ sits mid-luminance and the palette runs out at both ends before clearing
369
+ AA on it: the darkest Polar Night, #2e3440, measures 3.10:1, and the
370
+ lightest Snow Storm, #eceff4, 3.50:1. Nothing in the palette works, so the
371
+ text is black — 4.03:1 for pure white is still short. Retuning --tn-accent
372
+ itself would have been the alternative, and it is a border and fill colour
373
+ elsewhere. */
374
+ --tn-accent-txt: #000000; /* 5.21:1 on --tn-accent */
181
375
  --tn-bg1: #2e3440;
182
376
  --tn-bg2: #3b4252;
183
377
  --tn-bg3: #454d5e;
184
378
  --tn-fg1: #eceff4;
185
379
  --tn-fg2: #e5e9f0;
186
- --tn-fg3: #a0a8b4;
187
- --tn-fg4: #7b8394;
380
+ /* Non-text, 3:1 on --tn-bg1/--tn-bg2 — see the note in :root (#240).
381
+ --tn-fg4 was #7b8394 and read 2.64:1 on --tn-bg2 — #3b4252, the lighter of
382
+ the two surfaces and so the harder for a light foreground to clear on a
383
+ dark theme; it steps toward this theme's own --tn-fg2 by the
384
+ smallest amount that gets there with room to spare. */
385
+ --tn-fg3: #a0a8b4; /* 5.21:1 on --tn-bg1, 4.19:1 on --tn-bg2 */
386
+ --tn-fg4: #8b92a2; /* 4.00:1 on --tn-bg1, 3.23:1 on --tn-bg2 */
188
387
  --tn-alt-bg1: #434c5e;
189
388
  --tn-alt-bg2: #4c566a;
190
389
  --tn-alt-fg1: #d8dee9;
@@ -193,6 +392,9 @@
193
392
  --tn-yellow: #ebcb8b;
194
393
  --tn-orange: #d08770;
195
394
  --tn-red: #bf616a;
395
+ /* >=4.5:1 against both --tn-bg1 (5.67:1) and --tn-bg2 (4.57:1); --tn-red itself
396
+ is only 3.05:1 on --tn-bg1. */
397
+ --tn-error-text: #d9a0a6;
196
398
  --tn-magenta: #b48ead;
197
399
  --tn-violet: #775daa;
198
400
  --tn-blue: #5e81aC;
@@ -204,6 +406,13 @@
204
406
  --tn-teal: #008080;
205
407
  --tn-slategray: #708090;
206
408
  --tn-salmon: #fa8072;
409
+ /* Semantic status colours — see the note in :root. This palette's surfaces
410
+ sit close together, so every one of these is a long shift. On
411
+ --tn-alt-bg1 / --tn-bg1 / --tn-bg2. */
412
+ --tn-info: #b4c4d8; /* 4.86:1 / 7.03:1 / 5.67:1 */
413
+ --tn-warning: #e4b9ab; /* 4.86:1 / 7.03:1 / 5.67:1 */
414
+ --tn-error: #e2b8bc; /* 4.85:1 / 7.03:1 / 5.66:1 */
415
+ --tn-success: #b4caa1; /* 4.89:1 / 7.08:1 / 5.70:1 */
207
416
  }
208
417
 
209
418
  /* ================================
@@ -212,15 +421,26 @@
212
421
  .tn-paper {
213
422
  --tn-primary: #0D5687;
214
423
  --tn-primary-txt: #ffffff;
424
+ /* --tn-primary already clears 4.5:1 here, against both --tn-bg1 (6.95:1) and
425
+ --tn-bg2 (7.46:1) — see the note in .tn-nord for why it is still declared. */
426
+ --tn-primary-text: var(--tn-primary);
215
427
  --tn-topbar: #0D5687;
428
+ /* Darkened — see the note in :root. The label here is :root's, which this
429
+ theme's dark bar already suits; the hover is still its own, because the
430
+ shift is off THIS bar. */
431
+ --tn-topbar-hover: #0a446b; /* 7.87:1 under --tn-topbar-txt; 1.31:1 against --tn-topbar */
216
432
  --tn-accent: #f0cb00;
433
+ --tn-accent-txt: #282a36; /* 8.98:1 on --tn-accent */
217
434
  --tn-bg1: #F2F2F2;
218
435
  --tn-bg2: #FAFAFA;
219
436
  --tn-bg3: #e8e8e8;
220
437
  --tn-fg1: #3f3f3f;
221
438
  --tn-fg2: #666666;
222
- --tn-fg3: #888888;
223
- --tn-fg4: #909090;
439
+ /* Non-text, 3:1 on --tn-bg1/--tn-bg2 — see the note in :root (#240). Same
440
+ two values, and the same one-shade step, as .tn-blue: #909090 read 2.85:1
441
+ on --tn-bg1 and #888888 left nothing between it and the minimum. */
442
+ --tn-fg3: #7f7f7f; /* 3.58:1 on --tn-bg1, 3.84:1 on --tn-bg2 */
443
+ --tn-fg4: #868686; /* 3.25:1 on --tn-bg1, 3.49:1 on --tn-bg2 */
224
444
  --tn-alt-bg1: #f0f0f0;
225
445
  --tn-alt-bg2: #e8e8e8;
226
446
  --tn-alt-fg1: #181a26;
@@ -229,6 +449,8 @@
229
449
  --tn-yellow: #f0cb00;
230
450
  --tn-orange: #ee9302;
231
451
  --tn-red: #ff0013;
452
+ /* 4.5:1 against --tn-bg1 (measured 4.67:1); --tn-red itself is only 3.57:1. */
453
+ --tn-error-text: #db0010;
232
454
  --tn-magenta: #d238ff;
233
455
  --tn-violet: #c17ecc;
234
456
  --tn-blue: #0D5687;
@@ -240,34 +462,127 @@
240
462
  --tn-teal: #008080;
241
463
  --tn-slategray: #708090;
242
464
  --tn-salmon: #fa8072;
465
+ /* Semantic status colours — see the note in :root. Darkened rather than
466
+ lightened, this being a light theme. On --tn-alt-bg1 / --tn-bg1 /
467
+ --tn-bg2. */
468
+ --tn-info: var(--tn-blue); /* 6.83:1 / 6.95:1 / 7.46:1 — needs no shade of its own */
469
+ --tn-warning: #8e5801; /* 5.20:1 / 5.29:1 / 5.67:1 */
470
+ --tn-error: #c4000f; /* 5.49:1 / 5.59:1 / 6.00:1 */
471
+ --tn-success: #187232; /* 5.28:1 / 5.37:1 / 5.76:1 */
243
472
  }
244
473
 
245
474
  /* ================================
246
475
  Solarized Dark - Solarized dark color scheme
247
476
  ================================ */
248
477
  .tn-solarized-dark {
249
- --tn-primary: var(--tn-fg1);
478
+ /* The literal #586e75 rather than var(--tn-fg1), which is what this read
479
+ until #265. --tn-primary is a fill colour tuned toward the 3:1 non-text
480
+ minimum and labelled with --tn-primary-txt (white, 5.38:1 on it); --tn-fg1
481
+ is body text on a dark page and has to clear 4.5:1 there. On Solarized's
482
+ backgrounds no single value does both jobs — #586e75 is 2.79:1 as text on
483
+ --tn-bg1, and anything clearing that as text is too light to carry white.
484
+ So the two separate, and pinning the literal keeps every fill, border and
485
+ focus ring rendering the colour it already did. --tn-topbar below is
486
+ pinned to the same literal so that the bar does not move either; its
487
+ label is --tn-topbar-txt, and #277 measured and fixed that pairing. */
488
+ --tn-primary: #586e75;
250
489
  --tn-primary-txt: #ffffff;
490
+ /* >=4.5:1 against both --tn-bg1 (5.34:1) and --tn-bg2 (4.62:1); --tn-primary
491
+ itself is 2.79:1 on --tn-bg1, the worst of the nine, because it is a muted
492
+ Solarized base tone rather than a blue. */
493
+ --tn-primary-text: #879ea5;
251
494
  --tn-btn-default-bg: #0b4f60;
495
+ /* 5.32:1 on --tn-btn-default-bg, up from 3.03:1 before #265 retuned
496
+ --tn-fg2 — this is the only theme declaring both halves of this pair. */
252
497
  --tn-btn-default-txt: var(--tn-fg2);
253
- --tn-topbar: var(--tn-fg1);
254
- --tn-topbar-txt: #cdcdcd;
498
+ --tn-topbar: #586e75;
499
+ /* Was #cdcdcd, which read 3.39:1 on this bar — one of the two pairings #277
500
+ was filed for. Solarized's own base3 (#fdf6e3) would clear at 4.99:1;
501
+ white is taken instead because this bar is --tn-primary's literal and the
502
+ brightest label is what leaves the most room if it ever moves. */
503
+ --tn-topbar-txt: #ffffff; /* 5.38:1 on --tn-topbar */
504
+ /* Darkened — see the note in :root. Off Solarized's ramp for the same reason
505
+ the bar is pinned to a literal above: the tones below base01 are base02
506
+ (#073642) and base03 (#002b36), which this palette spends on --tn-bg2 and
507
+ --tn-bg1, so a hover from the ramp would render as a page surface. */
508
+ --tn-topbar-hover: #495b61; /* 7.11:1 under --tn-topbar-txt; 1.32:1 against --tn-topbar */
255
509
  --tn-accent: var(--tn-cyan);
510
+ --tn-accent-txt: #002b36; /* 4.75:1 on --tn-accent */
256
511
  --tn-bg1: #002b36;
257
512
  --tn-bg2: #073642;
258
513
  --tn-bg3: #0a4050;
259
- --tn-fg1: #586e75;
260
- --tn-fg2: #7f99a2;
261
- --tn-fg3: #4a5e65;
262
- --tn-fg4: #3a4d53;
514
+ /* TEXT, 4.5:1 on --tn-bg1/--tn-bg2, retuned in #265. They were #586e75
515
+ (base01), #7f99a2 and #839496 (base0), and measured 2.79/2.42, 4.99/4.32
516
+ and 4.75/4.11 — the one palette of nine whose text tokens did not clear the
517
+ text minimum on both page surfaces. Solarized's base00-base01 tones are
518
+ designed to sit close to base03, which is what makes them comment colours
519
+ there and unusable as body text here, so the ramp had to move up into the
520
+ base1-base3 range rather than nudge: nothing under roughly base1's
521
+ luminance clears 4.5:1 on --tn-bg2 at all. base3 and base1 are Solarized's
522
+ own tones and --tn-fg2 is a hue-preserving lift of base1 toward base2,
523
+ placed to keep three distinguishable steps instead of three values crowded
524
+ into the 4.50-4.86 band just above the minimum. The order is also righted:
525
+ --tn-fg1 was the LEAST legible of the three, which is how the primary text
526
+ role came to be the failing one. text-fg-contrast.spec.ts holds all nine
527
+ palettes to both the threshold and that order. */
528
+ --tn-fg1: #fdf6e3; /* base3 — 13.92:1 on --tn-bg1, 12.05:1 on --tn-bg2 */
529
+ --tn-fg2: #b8c9c9; /* 8.75:1 on --tn-bg1, 7.57:1 on --tn-bg2 */
530
+ /* Non-text, 3:1 on --tn-bg1/--tn-bg2 — see the note in :root (#240). The
531
+ largest move of the nine, and the only one that changes a theme's look
532
+ rather than nudging it: #4a5e65 read 1.91:1 on --tn-bg2 and #3a4d53
533
+ 1.46:1, the worst pair in the palette, because Solarized's base01/base02
534
+ sit deliberately close to its own background. Two steps that both clear
535
+ 3:1 do not fit between those and this theme's --tn-fg2 as it then was, so
536
+ both move up to where they do. Nothing reads either token, so nothing
537
+ regresses. */
538
+ --tn-fg3: #779099; /* 4.46:1 on --tn-bg1, 3.86:1 on --tn-bg2 */
539
+ --tn-fg4: #6e868e; /* 3.91:1 on --tn-bg1, 3.38:1 on --tn-bg2 */
263
540
  --tn-alt-bg1: #0e4853;
264
541
  --tn-alt-bg2: #155a68;
265
- --tn-alt-fg1: #839496;
266
- --tn-alt-fg2: #282a36;
542
+ /* Was #93a1a1, Solarized's base1 — the dimmest of that palette's own tones
543
+ clearing 4.5:1 on --tn-bg1 (5.61:1) and --tn-bg2 (4.86:1), which #265 picked
544
+ because muted text wants the step below --tn-fg2 rather than a fourth value
545
+ alongside it. That is all the text tokens claim, and it was true. On
546
+ --tn-alt-bg1 base1 read 3.79:1, and that fill is where tn-stepper draws its
547
+ step indicator, as well as the banner and the hovered table row (#283).
548
+
549
+ SOLARIZED HAS NO TONE LEFT TO TAKE, so the muted role leaves the palette
550
+ here — the thing #265's comment on this token was explicitly against.
551
+ base1 is the last of Solarized's base ramp before base2 (#eee8d5) and base3
552
+ (#fdf6e3), and both of those clear 4.5:1 on --tn-alt-bg1 — but this theme
553
+ has already spent them: base2 is --tn-alt-fg2 and base3 is --tn-fg1. So
554
+ staying in the palette means either the value that fails or a muted token
555
+ that is another role's token under a second name, which is the distinctness
556
+ the muted step exists for. This is the
557
+ smallest lift of base1 that clears 4.5:1 on --tn-alt-bg1 by more than a
558
+ rounding, with the hue held at 180 degrees (G and B equal, as base1 has
559
+ them) and the saturation within an 8-bit step of base1's 6.9%. Measured
560
+ unrounded, as the specs compare them: #a3afaf is 4.4956 and FORMATS as
561
+ "4.50", and #a4afaf clears by 0.006, which any later nudge to the fill would
562
+ spend.
563
+
564
+ THE FILL COULD NOT MOVE INSTEAD. base1 clears 4.5:1 only on a surface of
565
+ luminance 0.0373 or below; --tn-bg2 is already at 0.0308 and --tn-bg3 at
566
+ 0.0431. --tn-alt-bg1 would have had to darken past --tn-bg3 into the narrow
567
+ band just above the card surface, inverting the elevation it exists to show
568
+ and taking the banner and the hovered row down with it.
569
+
570
+ Still a step below --tn-fg2 (#b8c9c9), which is what the muted role is for:
571
+ 1.30:1 apart on --tn-alt-bg1, where the other eight palettes ship 1.11:1 to
572
+ 3.01:1. */
573
+ --tn-alt-fg1: #a5b0b0; /* 6.75:1 on --tn-bg1, 5.84:1 on --tn-bg2, 4.56:1 on
574
+ --tn-alt-bg1 (the stepper step indicator) */
575
+ /* Was #282a36 — near-black, copied from a light theme, and painted on this
576
+ theme's own --tn-alt-bg1 (1.40:1) and --tn-alt-bg2 (1.83:1), the only two
577
+ surfaces this token is ever drawn on. Solarized's base2 instead (#238). */
578
+ --tn-alt-fg2: #eee8d5; /* 8.28:1 on --tn-alt-bg1, 6.36:1 on --tn-alt-bg2 */
267
579
  --tn-lines: #586e75;
268
580
  --tn-yellow: #b58900;
269
581
  --tn-orange: #cb4b16;
270
582
  --tn-red: #dc322f;
583
+ /* >=4.5:1 against both --tn-bg1 (5.28:1) and --tn-bg2 (4.57:1); --tn-red itself
584
+ is only 3.25:1 on --tn-bg1. */
585
+ --tn-error-text: #e87876;
271
586
  --tn-magenta: #d33682;
272
587
  --tn-violet: #6c71c4;
273
588
  --tn-blue: #268bd2;
@@ -279,6 +594,12 @@
279
594
  --tn-teal: #008080;
280
595
  --tn-slategray: #708090;
281
596
  --tn-salmon: #fa8072;
597
+ /* Semantic status colours — see the note in :root. On --tn-alt-bg1 /
598
+ --tn-bg1 / --tn-bg2. */
599
+ --tn-info: #73b6e6; /* 4.62:1 / 6.84:1 / 5.92:1 */
600
+ --tn-warning: #f09b78; /* 4.67:1 / 6.91:1 / 5.98:1 */
601
+ --tn-error: #ee9998; /* 4.66:1 / 6.90:1 / 5.97:1 */
602
+ --tn-success: #a2ba00; /* 4.62:1 / 6.83:1 / 5.92:1 */
282
603
  }
283
604
 
284
605
 
@@ -288,16 +609,48 @@
288
609
  .tn-midnight {
289
610
  --tn-primary: var(--tn-blue);
290
611
  --tn-primary-txt: #ffffff;
612
+ /* >=4.5:1 against both --tn-bg1 (6.02:1) and --tn-bg2 (4.61:1); --tn-primary
613
+ itself is only 2.90:1 on --tn-bg1. */
614
+ --tn-primary-text: #50afee;
291
615
  --tn-topbar: var(--tn-blue);
292
- --tn-topbar-txt: var(--tn-fg2);
616
+ /* Was var(--tn-fg2) (#cccccc), which read 3.12:1 on this bar (#277). This
617
+ palette's --tn-fg2 answers to --tn-bg1/--tn-bg2 and cannot also answer to
618
+ a mid-blue bar, which is why the bar's label is its own token. */
619
+ --tn-topbar-txt: #ffffff; /* 5.01:1 on --tn-topbar */
620
+ /* Darkened — see the note in :root. A literal rather than a shift of
621
+ var(--tn-blue): --tn-blue is a fill and border colour in its own right
622
+ here, and this is the bar's hover, not the blue's. */
623
+ --tn-topbar-hover: #0f5f95; /* 6.79:1 under --tn-topbar-txt; 1.36:1 against --tn-topbar */
293
624
  --tn-accent: var(--tn-violet);
625
+ --tn-accent-txt: #212a35; /* 4.92:1 on --tn-accent */
294
626
  --tn-bg1: #212a35;
295
627
  --tn-bg2: #303d48;
296
628
  --tn-bg3: #3d4a55;
297
- --tn-fg1: #aaaaaa;
298
- --tn-fg2: #cccccc;
299
- --tn-fg3: #888888;
300
- --tn-fg4: #666666;
629
+ /* Was #aaaaaa, the calmer half of a deliberate inversion: --tn-fg2 (#cccccc)
630
+ was this palette's emphasis colour and --tn-fg1 the quieter one, the only
631
+ palette of nine ordered that way. Both cleared 4.5:1 on --tn-bg1 and
632
+ --tn-bg2, which is all the text tokens claim — but on the three fills that
633
+ sit above --tn-bg2, #aaaaaa read 3.91:1 on --tn-bg3/--tn-alt-bg1 and
634
+ 3.19:1 on --tn-alt-bg2, the worst of the nine palettes (#282). Those fills
635
+ are the hovered menu item and list item, the active table row, the banner
636
+ and the stepper indicator, so that was primary text on ordinary
637
+ interactive states.
638
+
639
+ THE FILLS COULD NOT MOVE INSTEAD. #aaaaaa clears 4.5:1 only on a surface of
640
+ luminance 0.0504 or below, and --tn-bg2 is already at 0.0443: all three
641
+ fills would have had to land in the band between the card surface and a
642
+ ceiling just above it, 12.9% of the elevation --tn-alt-bg2 has today. The
643
+ fills exist to read as raised, so keeping the inversion meant keeping the
644
+ failure. #cccccc holds its role unchanged. */
645
+ --tn-fg1: #e0e0e0; /* 10.99:1 on --tn-bg1, 8.43:1 on --tn-bg2, 6.89:1 on
646
+ --tn-bg3/--tn-alt-bg1, 5.62:1 on --tn-alt-bg2 */
647
+ --tn-fg2: #cccccc; /* 9.04:1 on --tn-bg1, 6.93:1 on --tn-bg2, 5.66:1 on
648
+ --tn-bg3/--tn-alt-bg1, 4.62:1 on --tn-alt-bg2 */
649
+ /* Non-text, 3:1 on --tn-bg1/--tn-bg2 — see the note in :root (#240).
650
+ #666666 read 1.94:1 on --tn-bg2 and #888888 3.14:1, so --tn-fg4 had to
651
+ move a long way and --tn-fg3 had to move with it to stay a step ahead. */
652
+ --tn-fg3: #969696; /* 4.91:1 on --tn-bg1, 3.76:1 on --tn-bg2 */
653
+ --tn-fg4: #8a8a8a; /* 4.20:1 on --tn-bg1, 3.22:1 on --tn-bg2 */
301
654
  --tn-alt-bg1: #3d4a55;
302
655
  --tn-alt-bg2: #4a5762;
303
656
  --tn-alt-fg1: #c1c1c1;
@@ -306,6 +659,9 @@
306
659
  --tn-yellow: #f0cb00;
307
660
  --tn-orange: #ee9302;
308
661
  --tn-red: #ff0013;
662
+ /* >=4.5:1 against both --tn-bg1 (6.01:1) and --tn-bg2 (4.61:1); --tn-red itself
663
+ is only 3.64:1 on --tn-bg1. */
664
+ --tn-error-text: #ff8089;
309
665
  --tn-magenta: #d238ff;
310
666
  --tn-violet: #c17ecc;
311
667
  --tn-blue: #1274b5;
@@ -317,6 +673,12 @@
317
673
  --tn-teal: #008080;
318
674
  --tn-slategray: #708090;
319
675
  --tn-salmon: #fa8072;
676
+ /* Semantic status colours — see the note in :root. On --tn-alt-bg1 /
677
+ --tn-bg1 / --tn-bg2. */
678
+ --tn-info: #79c1f2; /* 4.64:1 / 7.41:1 / 5.68:1 */
679
+ --tn-warning: #fda61c; /* 4.61:1 / 7.36:1 / 5.64:1 */
680
+ --tn-error: #ff9ea5; /* 4.63:1 / 7.38:1 / 5.66:1 */
681
+ --tn-success: #2ed45f; /* 4.63:1 / 7.39:1 / 5.67:1 */
320
682
  }
321
683
 
322
684
  /* ================================
@@ -324,15 +686,34 @@
324
686
  ================================ */
325
687
  .tn-high-contrast {
326
688
  --tn-primary: var(--tn-blue); /* WCAG fix try another shade of blue #007db3 or black #000000 */
689
+ /* Black. Six of the other eight themes put white on their --tn-primary fill
690
+ and Nord puts #333333 on its pale one; this theme's --tn-blue is light
691
+ enough that white measures only 4.07:1. It was inheriting :root's #ffffff,
692
+ having never declared a --tn-primary-txt of its own, and that is the value
693
+ that failed (#238). */
694
+ --tn-primary-txt: #000000; /* 5.16:1 on --tn-primary */
695
+ /* >=4.5:1 against both --tn-bg1 (4.61:1) and --tn-bg2 (6.27:1); --tn-primary
696
+ itself is only 2.99:1 on --tn-bg1 — which, in the accessibility theme, is
697
+ the one worth noticing. Darkened, this being a light theme. */
698
+ --tn-primary-text: #366584;
327
699
  --tn-topbar: var(--tn-black);
700
+ /* Lightened, and it is the one bar with no other option — see the note in
701
+ :root. #000000 cannot be darkened at all, so the direction the other six
702
+ take is not available here, which is why the fill is a per-palette token
703
+ rather than one derivation. This is the value the old mix already rendered
704
+ (#212121), so the accessibility theme's bar looks exactly as it did. */
705
+ --tn-topbar-hover: #212121; /* 11.93:1 under --tn-topbar-txt; 1.30:1 against --tn-topbar */
328
706
  --tn-accent: var(--tn-magenta);
707
+ --tn-accent-txt: #000000; /* 5.75:1 on --tn-accent */
329
708
  --tn-bg1: #dddddd;
330
709
  --tn-bg2: #ffffff;
331
710
  --tn-bg3: #d0d0d0;
332
711
  --tn-fg1: #222222;
333
712
  --tn-fg2: #333333;
334
- --tn-fg3: #666666;
335
- --tn-fg4: #777777;
713
+ /* Non-text, 3:1 on --tn-bg1/--tn-bg2 — see the note in :root (#240). Both
714
+ already clear it on both surfaces here, so neither moves. */
715
+ --tn-fg3: #666666; /* 4.23:1 on --tn-bg1, 5.74:1 on --tn-bg2 */
716
+ --tn-fg4: #777777; /* 3.30:1 on --tn-bg1, 4.48:1 on --tn-bg2 */
336
717
  --tn-alt-bg1: #f0f0f0;
337
718
  --tn-alt-bg2: #e8e8e8;
338
719
  --tn-alt-fg1: #181a26;
@@ -341,6 +722,8 @@
341
722
  --tn-yellow: #f0cb00;
342
723
  --tn-orange: #ee9302;
343
724
  --tn-red: #ff0013;
725
+ /* 4.5:1 against --tn-bg1 (measured 4.68:1); --tn-red itself is only 2.94:1 — the worst of the nine. */
726
+ --tn-error-text: #c2000e;
344
727
  --tn-magenta: #d238ff;
345
728
  --tn-violet: #9844b1;
346
729
  --tn-blue: #4784ac;
@@ -352,6 +735,14 @@
352
735
  --tn-teal: #008080;
353
736
  --tn-slategray: #708090;
354
737
  --tn-salmon: #fa8072;
738
+ /* Semantic status colours — see the note in :root. Darkened rather than
739
+ lightened, this being a light theme, and further than the other two: the
740
+ accessibility theme is the one where a status colour has to hold up. On
741
+ --tn-alt-bg1 / --tn-bg1 / --tn-bg2. */
742
+ --tn-info: #315c78; /* 6.28:1 / 5.27:1 / 7.16:1 */
743
+ --tn-warning: #7c4d01; /* 6.31:1 / 5.30:1 / 7.19:1 */
744
+ --tn-error: #ad000d; /* 6.62:1 / 5.56:1 / 7.55:1 */
745
+ --tn-success: #296300; /* 6.40:1 / 5.37:1 / 7.29:1 */
355
746
  }
356
747
 
357
748
  /* ================================
@@ -561,6 +952,19 @@ tn-dialog-shell {
561
952
  margin-left: 16px; /* Space between title and button */
562
953
  }
563
954
 
955
+ /*
956
+ What pushes the header chrome to the right edge when there is no title.
957
+ `.tn-dialog__title` has `flex: 1` and used to do that job, but since #219 an
958
+ untitled dialog renders no heading at all — an empty `<h2>` is an
959
+ `empty-heading` violation — and the buttons would otherwise sit hard left.
960
+ Only the FIRST of them takes the auto margin, so the 16px between the
961
+ fullscreen and close buttons is unchanged.
962
+ */
963
+ .tn-dialog__header > .tn-dialog__fullscreen:first-child,
964
+ .tn-dialog__header > .tn-dialog__close:first-child {
965
+ margin-left: auto;
966
+ }
967
+
564
968
  .tn-dialog__close:hover {
565
969
  background: var(--tn-alt-bg1, #f8f9fa);
566
970
  color: var(--tn-fg1, #000000);
@@ -603,14 +1007,21 @@ tn-dialog-shell {
603
1007
  gap: 16px;
604
1008
  }
605
1009
 
606
- /* Hide a content section / actions footer that has nothing to show, so it
607
- doesn't render as an empty bordered, padded bar.
1010
+ /* Hide a header / content section / actions footer that has nothing to show, so
1011
+ it doesn't render as an empty bordered, padded bar.
608
1012
  - `:empty` covers dialogs that project nothing into the slot (the ng-content
609
1013
  comment anchor is ignored by `:empty`, but real text/elements are not).
610
1014
  - The `--hidden` modifier (driven by the [hideContent]/[hideActions] inputs)
611
1015
  covers slots that always project a wrapper element whose contents are
612
1016
  conditional, so the slot is never truly `:empty`.
1017
+ The header joined this list with #219: it used to render its `<h2>`
1018
+ unconditionally and so was never empty, but an untitled dialog now renders no
1019
+ heading — and one opened with [showCloseButton]="false" and no fullscreen
1020
+ button has nothing else in its header either. The `@if` anchors it is left
1021
+ with are comment nodes, which `:empty` ignores, same as the ng-content anchor
1022
+ above.
613
1023
  Declared after the layout rules above so it wins on equal specificity. */
1024
+ .tn-dialog__header:empty,
614
1025
  .tn-dialog__content:empty,
615
1026
  .tn-dialog__content--hidden,
616
1027
  .tn-dialog__actions:empty,