@cahyo-dimas/freeday 2.2.0 → 3.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +251 -0
  2. package/COMPONENTS.md +183 -7
  3. package/README.id.md +1 -1
  4. package/README.md +1 -1
  5. package/USAGE.md +1 -1
  6. package/adapters/blazor/FdyAppShell.razor +1 -1
  7. package/adapters/blazor/FdyAppShell.razor.cs +35 -0
  8. package/adapters/blazor/FdyTable.razor +44 -4
  9. package/adapters/blazor/FdyTable.razor.cs +110 -0
  10. package/adapters/blazor/FdyTableFooter.razor +1 -1
  11. package/adapters/blazor/freeday-blazor.js +8 -0
  12. package/adapters/react/components/FdyAppShell.tsx +52 -11
  13. package/adapters/react/components/FdyTable.tsx +123 -2
  14. package/adapters/react/components/FdyTableFooter.tsx +1 -1
  15. package/adapters/vue/components/FdyAppShell.vue +41 -11
  16. package/adapters/vue/components/FdyTable.vue +125 -4
  17. package/adapters/vue/components/FdyTableFooter.vue +1 -1
  18. package/dist/freeday-app-shell.js +23 -5
  19. package/dist/freeday-busy.js +168 -0
  20. package/dist/freeday-stepper.js +54 -4
  21. package/dist/freeday-table.js +7 -0
  22. package/dist/freeday.bundle.css +652 -43
  23. package/dist/freeday.css +129 -15
  24. package/dist/freeday.js +253 -9
  25. package/dist/freeday.tokens.css +523 -28
  26. package/docs/agent-onboarding.md +4 -0
  27. package/docs/getting-started.md +1 -1
  28. package/package.json +4 -3
  29. package/src/components/app-shell.css +29 -5
  30. package/src/components/appbar.css +2 -2
  31. package/src/components/busy.css +42 -0
  32. package/src/components/card.css +1 -1
  33. package/src/components/composition.css +15 -2
  34. package/src/components/drawer.css +1 -1
  35. package/src/components/filterbar.css +3 -2
  36. package/src/components/input.css +9 -0
  37. package/src/components/menu.css +1 -1
  38. package/src/components/modal.css +1 -1
  39. package/src/components/stepper.css +11 -0
  40. package/src/components/table.css +13 -0
  41. package/tokens/tokens.json +54 -10
@@ -12,6 +12,14 @@
12
12
  --azure-800: #1c398c;
13
13
  --azure-900: #1c336e;
14
14
  --azure-950: #131e42;
15
+ --primary-50: var(--azure-50);
16
+ --primary-100: var(--azure-100);
17
+ --primary-300: var(--azure-300);
18
+ --primary-400: var(--azure-400);
19
+ --primary-500: var(--azure-500);
20
+ --primary-600: var(--azure-600);
21
+ --primary-700: var(--azure-700);
22
+ --primary-800: var(--azure-800);
15
23
  --slate-0: #ffffff;
16
24
  --slate-50: #f7f8fa;
17
25
  --slate-100: #eef0f4;
@@ -41,24 +49,129 @@
41
49
  --red-500: #ef4444;
42
50
  --red-600: #dc2626;
43
51
  --red-700: #b91c1c;
52
+ --amber-50: #fffbeb;
44
53
  --amber-100: #fef3c7;
54
+ --amber-300: #fcd34d;
45
55
  --amber-400: #fbbf24;
46
56
  --amber-500: #f59e0b;
47
57
  --amber-600: #d97706;
48
58
  --amber-700: #b45309;
49
59
  --amber-800: #92400e;
60
+ --green-50: #f0fdf4;
50
61
  --green-100: #dcfce7;
62
+ --green-300: #86efac;
51
63
  --green-400: #4ade80;
52
64
  --green-500: #22c55e;
53
65
  --green-600: #16a34a;
54
66
  --green-700: #15803d;
55
67
  --green-800: #166534;
68
+ --blue-50: #eff6ff;
56
69
  --blue-100: #dbeafe;
57
70
  --blue-300: #93c5fd;
58
71
  --blue-400: #60a5fa;
59
72
  --blue-500: #3b82f6;
60
73
  --blue-600: #2563eb;
61
74
  --blue-700: #1d4ed8;
75
+ --blue-800: #1e40af;
76
+ --emerald-50: #ecfdf5;
77
+ --emerald-100: #d1fae5;
78
+ --emerald-300: #6ee7b7;
79
+ --emerald-400: #34d399;
80
+ --emerald-500: #10b981;
81
+ --emerald-600: #059669;
82
+ --emerald-700: #047857;
83
+ --emerald-800: #065f46;
84
+ --lime-50: #f7fee7;
85
+ --lime-100: #ecfccb;
86
+ --lime-300: #bef264;
87
+ --lime-400: #a3e635;
88
+ --lime-500: #84cc16;
89
+ --lime-600: #65a30d;
90
+ --lime-700: #4d7c0f;
91
+ --lime-800: #3f6212;
92
+ --orange-50: #fff7ed;
93
+ --orange-100: #ffedd5;
94
+ --orange-300: #fdba74;
95
+ --orange-400: #fb923c;
96
+ --orange-500: #f97316;
97
+ --orange-600: #ea580c;
98
+ --orange-700: #c2410c;
99
+ --orange-800: #9a3412;
100
+ --yellow-50: #fefce8;
101
+ --yellow-100: #fef9c3;
102
+ --yellow-300: #fde047;
103
+ --yellow-400: #facc15;
104
+ --yellow-500: #eab308;
105
+ --yellow-600: #ca8a04;
106
+ --yellow-700: #a16207;
107
+ --yellow-800: #854d0e;
108
+ --yellow-900: #713f12;
109
+ --teal-50: #f0fdfa;
110
+ --teal-100: #ccfbf1;
111
+ --teal-300: #5eead4;
112
+ --teal-400: #2dd4bf;
113
+ --teal-500: #14b8a6;
114
+ --teal-600: #0d9488;
115
+ --teal-700: #0f766e;
116
+ --teal-800: #115e59;
117
+ --cyan-50: #ecfeff;
118
+ --cyan-100: #cffafe;
119
+ --cyan-300: #67e8f9;
120
+ --cyan-400: #22d3ee;
121
+ --cyan-500: #06b6d4;
122
+ --cyan-600: #0891b2;
123
+ --cyan-700: #0e7490;
124
+ --cyan-800: #155e75;
125
+ --indigo-50: #eef2ff;
126
+ --indigo-100: #e0e7ff;
127
+ --indigo-200: #c7d2fe;
128
+ --indigo-300: #a5b4fc;
129
+ --indigo-400: #818cf8;
130
+ --indigo-500: #6366f1;
131
+ --indigo-600: #4f46e5;
132
+ --indigo-700: #4338ca;
133
+ --indigo-800: #3730a3;
134
+ --violet-50: #f5f3ff;
135
+ --violet-100: #ede9fe;
136
+ --violet-200: #ddd6fe;
137
+ --violet-300: #c4b5fd;
138
+ --violet-400: #a78bfa;
139
+ --violet-500: #8b5cf6;
140
+ --violet-600: #7c3aed;
141
+ --violet-700: #6d28d9;
142
+ --violet-800: #5b21b6;
143
+ --purple-50: #faf5ff;
144
+ --purple-100: #f3e8ff;
145
+ --purple-300: #d8b4fe;
146
+ --purple-400: #c084fc;
147
+ --purple-500: #a855f7;
148
+ --purple-600: #9333ea;
149
+ --purple-700: #7e22ce;
150
+ --purple-800: #6b21a8;
151
+ --fuchsia-50: #fdf4ff;
152
+ --fuchsia-100: #fae8ff;
153
+ --fuchsia-300: #f0abfc;
154
+ --fuchsia-400: #e879f9;
155
+ --fuchsia-500: #d946ef;
156
+ --fuchsia-600: #c026d3;
157
+ --fuchsia-700: #a21caf;
158
+ --fuchsia-800: #86198f;
159
+ --pink-50: #fdf2f8;
160
+ --pink-100: #fce7f3;
161
+ --pink-300: #f9a8d4;
162
+ --pink-400: #f472b6;
163
+ --pink-500: #ec4899;
164
+ --pink-600: #db2777;
165
+ --pink-700: #be185d;
166
+ --pink-800: #9d174d;
167
+ --rose-50: #fff1f2;
168
+ --rose-100: #ffe4e6;
169
+ --rose-300: #fda4af;
170
+ --rose-400: #fb7185;
171
+ --rose-500: #f43f5e;
172
+ --rose-600: #e11d48;
173
+ --rose-700: #be123c;
174
+ --rose-800: #9f1239;
62
175
  --font-display: 'Sora', ui-sans-serif, system-ui, sans-serif;
63
176
  --font-body: 'IBM Plex Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
64
177
  --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
@@ -118,12 +231,12 @@
118
231
  --control-switch-w: 2.4rem;
119
232
  --control-switch-h: 1.35rem;
120
233
  --bw: 1px;
121
- --color-primary: var(--azure-600);
122
- --color-primary-hover: var(--azure-700);
123
- --color-primary-active: var(--azure-800);
234
+ --color-primary: var(--primary-600);
235
+ --color-primary-hover: var(--primary-700);
236
+ --color-primary-active: var(--primary-800);
124
237
  --color-on-primary: var(--slate-0);
125
- --color-primary-soft: var(--azure-50);
126
- --color-primary-border: var(--azure-100);
238
+ --color-primary-soft: var(--primary-50);
239
+ --color-primary-border: var(--primary-100);
127
240
  --color-accent: var(--sky-600);
128
241
  --color-accent-hover: var(--sky-700);
129
242
  --color-on-accent: var(--slate-0);
@@ -132,6 +245,8 @@
132
245
  --color-surface: var(--slate-0);
133
246
  --color-surface-2: var(--slate-50);
134
247
  --color-surface-3: var(--slate-100);
248
+ --color-surface-raised: var(--color-surface);
249
+ --color-scrim: rgba(10,12,25,.5);
135
250
  --color-text: var(--slate-900);
136
251
  --color-text-muted: var(--slate-600);
137
252
  --color-text-subtle: var(--slate-500);
@@ -152,7 +267,7 @@
152
267
  --color-danger-soft: var(--red-100);
153
268
  --color-info: var(--blue-600);
154
269
  --color-info-soft: var(--blue-100);
155
- --color-primary-strong: var(--azure-700);
270
+ --color-primary-strong: var(--primary-700);
156
271
  --color-success-strong: var(--green-800);
157
272
  --color-warning-strong: var(--amber-800);
158
273
  --color-danger-strong: var(--red-700);
@@ -175,7 +290,9 @@
175
290
  --tone-6: var(--chart-6);
176
291
  --tone-7: var(--chart-7);
177
292
  --tone-8: var(--chart-8);
178
- --focus-ring: var(--azure-600);
293
+ --surface-filter: none;
294
+ --surface-inset: inset 0 0 0 0 transparent;
295
+ --focus-ring: var(--primary-600);
179
296
  --focus-ring-width: 2px;
180
297
  }
181
298
  /* The SYSTEM default stays root-scoped: it is a statement about the document, and dropping :root
@@ -189,12 +306,12 @@
189
306
  --shadow-4: 0 28px 56px -12px rgba(0,0,0,.72),0 2px 8px rgba(0,0,0,.5);
190
307
  --shadow-lift: 0 1px 2px rgba(0,0,0,.5),0 18px 38px -14px rgba(0,0,0,.72);
191
308
  --shadow-lift-hover: 0 3px 6px rgba(0,0,0,.55),0 32px 56px -16px rgba(0,0,0,.82);
192
- --color-primary: var(--azure-500);
193
- --color-primary-hover: var(--azure-400);
194
- --color-primary-active: var(--azure-300);
309
+ --color-primary: var(--primary-500);
310
+ --color-primary-hover: var(--primary-400);
311
+ --color-primary-active: var(--primary-300);
195
312
  --color-on-primary: var(--slate-0);
196
- --color-primary-soft: rgba(32,80,216,.20);
197
- --color-primary-border: rgba(92,139,246,.28);
313
+ --color-primary-soft: color-mix(in srgb, var(--primary-600) 20%, transparent);
314
+ --color-primary-border: color-mix(in srgb, var(--primary-400) 28%, transparent);
198
315
  --color-accent: var(--sky-400);
199
316
  --color-accent-hover: var(--sky-300);
200
317
  --color-on-accent: #06233f;
@@ -203,6 +320,7 @@
203
320
  --color-surface: var(--slate-950);
204
321
  --color-surface-2: var(--slate-900);
205
322
  --color-surface-3: var(--slate-800);
323
+ --color-surface-raised: var(--color-surface);
206
324
  --color-text: var(--slate-100);
207
325
  --color-text-muted: var(--slate-400);
208
326
  --color-text-subtle: var(--slate-500);
@@ -221,7 +339,7 @@
221
339
  --color-danger-soft: rgba(248,113,113,.18);
222
340
  --color-info: var(--blue-400);
223
341
  --color-info-soft: rgba(96,165,250,.18);
224
- --color-primary-strong: var(--azure-300);
342
+ --color-primary-strong: var(--primary-300);
225
343
  --color-success-strong: var(--green-400);
226
344
  --color-warning-strong: var(--amber-400);
227
345
  --color-danger-strong: var(--red-300);
@@ -236,7 +354,7 @@
236
354
  --chart-8: #e66767;
237
355
  --chart-grid: var(--slate-800);
238
356
  --chart-tick: var(--slate-600);
239
- --focus-ring: var(--azure-400);
357
+ --focus-ring: var(--primary-400);
240
358
  }
241
359
  }
242
360
  /* The two EXPLICIT opt-ins are deliberately NOT scoped to :root, for the same reason as density
@@ -252,12 +370,12 @@
252
370
  --shadow-4: 0 28px 56px -12px rgba(0,0,0,.72),0 2px 8px rgba(0,0,0,.5);
253
371
  --shadow-lift: 0 1px 2px rgba(0,0,0,.5),0 18px 38px -14px rgba(0,0,0,.72);
254
372
  --shadow-lift-hover: 0 3px 6px rgba(0,0,0,.55),0 32px 56px -16px rgba(0,0,0,.82);
255
- --color-primary: var(--azure-500);
256
- --color-primary-hover: var(--azure-400);
257
- --color-primary-active: var(--azure-300);
373
+ --color-primary: var(--primary-500);
374
+ --color-primary-hover: var(--primary-400);
375
+ --color-primary-active: var(--primary-300);
258
376
  --color-on-primary: var(--slate-0);
259
- --color-primary-soft: rgba(32,80,216,.20);
260
- --color-primary-border: rgba(92,139,246,.28);
377
+ --color-primary-soft: color-mix(in srgb, var(--primary-600) 20%, transparent);
378
+ --color-primary-border: color-mix(in srgb, var(--primary-400) 28%, transparent);
261
379
  --color-accent: var(--sky-400);
262
380
  --color-accent-hover: var(--sky-300);
263
381
  --color-on-accent: #06233f;
@@ -266,6 +384,7 @@
266
384
  --color-surface: var(--slate-950);
267
385
  --color-surface-2: var(--slate-900);
268
386
  --color-surface-3: var(--slate-800);
387
+ --color-surface-raised: var(--color-surface);
269
388
  --color-text: var(--slate-100);
270
389
  --color-text-muted: var(--slate-400);
271
390
  --color-text-subtle: var(--slate-500);
@@ -284,7 +403,7 @@
284
403
  --color-danger-soft: rgba(248,113,113,.18);
285
404
  --color-info: var(--blue-400);
286
405
  --color-info-soft: rgba(96,165,250,.18);
287
- --color-primary-strong: var(--azure-300);
406
+ --color-primary-strong: var(--primary-300);
288
407
  --color-success-strong: var(--green-400);
289
408
  --color-warning-strong: var(--amber-400);
290
409
  --color-danger-strong: var(--red-300);
@@ -299,7 +418,7 @@
299
418
  --chart-8: #e66767;
300
419
  --chart-grid: var(--slate-800);
301
420
  --chart-tick: var(--slate-600);
302
- --focus-ring: var(--azure-400);
421
+ --focus-ring: var(--primary-400);
303
422
  }
304
423
  [data-theme="light"] {
305
424
  --shadow-1: 0 1px 2px rgba(16,14,30,.06);
@@ -308,12 +427,12 @@
308
427
  --shadow-4: 0 24px 48px -12px rgba(16,14,30,.26),0 2px 8px rgba(16,14,30,.10);
309
428
  --shadow-lift: 0 1px 2px rgba(16,14,30,.05),0 16px 34px -14px rgba(16,14,30,.24);
310
429
  --shadow-lift-hover: 0 2px 4px rgba(16,14,30,.06),0 30px 50px -16px rgba(16,14,30,.33);
311
- --color-primary: var(--azure-600);
312
- --color-primary-hover: var(--azure-700);
313
- --color-primary-active: var(--azure-800);
430
+ --color-primary: var(--primary-600);
431
+ --color-primary-hover: var(--primary-700);
432
+ --color-primary-active: var(--primary-800);
314
433
  --color-on-primary: var(--slate-0);
315
- --color-primary-soft: var(--azure-50);
316
- --color-primary-border: var(--azure-100);
434
+ --color-primary-soft: var(--primary-50);
435
+ --color-primary-border: var(--primary-100);
317
436
  --color-accent: var(--sky-600);
318
437
  --color-accent-hover: var(--sky-700);
319
438
  --color-on-accent: var(--slate-0);
@@ -322,6 +441,7 @@
322
441
  --color-surface: var(--slate-0);
323
442
  --color-surface-2: var(--slate-50);
324
443
  --color-surface-3: var(--slate-100);
444
+ --color-surface-raised: var(--color-surface);
325
445
  --color-text: var(--slate-900);
326
446
  --color-text-muted: var(--slate-600);
327
447
  --color-text-subtle: var(--slate-500);
@@ -340,7 +460,7 @@
340
460
  --color-danger-soft: var(--red-100);
341
461
  --color-info: var(--blue-600);
342
462
  --color-info-soft: var(--blue-100);
343
- --color-primary-strong: var(--azure-700);
463
+ --color-primary-strong: var(--primary-700);
344
464
  --color-success-strong: var(--green-800);
345
465
  --color-warning-strong: var(--amber-800);
346
466
  --color-danger-strong: var(--red-700);
@@ -355,7 +475,7 @@
355
475
  --chart-8: #e34948;
356
476
  --chart-grid: var(--slate-200);
357
477
  --chart-tick: var(--slate-400);
358
- --focus-ring: var(--azure-600);
478
+ --focus-ring: var(--primary-600);
359
479
  }
360
480
  /* Density is deliberately NOT scoped to :root. These are custom properties, so they inherit,
361
481
  * putting the attribute on any ancestor (a route wrapper, one section) densifies just that
@@ -380,6 +500,381 @@
380
500
  --space-6: 1.5rem;
381
501
  --control-h: 2.5rem;
382
502
  }
503
+ [data-style="glass"] {
504
+ --color-surface-raised: rgba(255,255,255,.82);
505
+ --surface-filter: blur(14px) saturate(150%);
506
+ --surface-inset: inset 0 1px 0 rgba(255,255,255,.55);
507
+ }
508
+ [data-style="soft"] {
509
+ --color-surface-raised: var(--color-surface);
510
+ --surface-filter: none;
511
+ --surface-inset: inset 0 0 0 0 transparent;
512
+ }
513
+ @media (prefers-color-scheme: dark) {
514
+ :root:not([data-theme="light"])[data-style="glass"] {
515
+ --color-surface-raised: rgba(23,27,38,.90);
516
+ --surface-inset: inset 0 1px 0 rgba(255,255,255,.09);
517
+ }
518
+ }
519
+ [data-theme="dark"][data-style="glass"] {
520
+ --color-surface-raised: rgba(23,27,38,.90);
521
+ --surface-inset: inset 0 1px 0 rgba(255,255,255,.09);
522
+ }
523
+ [data-theme="light"][data-style="glass"] {
524
+ --color-surface-raised: rgba(255,255,255,.82);
525
+ --surface-inset: inset 0 1px 0 rgba(255,255,255,.55);
526
+ }
527
+ /* Primary palette axis. 18 options, see $primaries in tokens.json. */
528
+ [data-primary="azure"] {
529
+ --primary-50: var(--azure-50);
530
+ --primary-100: var(--azure-100);
531
+ --primary-300: var(--azure-300);
532
+ --primary-400: var(--azure-400);
533
+ --primary-500: var(--azure-500);
534
+ --primary-600: var(--azure-600);
535
+ --primary-700: var(--azure-700);
536
+ --primary-800: var(--azure-800);
537
+ --color-on-primary: var(--slate-0);
538
+ }
539
+ [data-primary="sky"] {
540
+ --primary-50: var(--sky-50);
541
+ --primary-100: var(--sky-100);
542
+ --primary-300: var(--sky-300);
543
+ --primary-400: var(--sky-400);
544
+ --primary-500: var(--sky-500);
545
+ --primary-600: var(--sky-600);
546
+ --primary-700: var(--sky-700);
547
+ --primary-800: var(--sky-800);
548
+ --color-on-primary: var(--slate-0);
549
+ }
550
+ @media (prefers-color-scheme: dark) {
551
+ :root:not([data-theme="light"])[data-primary="sky"] {
552
+ --color-on-primary: var(--slate-950);
553
+ }
554
+ }
555
+ [data-theme="dark"][data-primary="sky"] {
556
+ --color-on-primary: var(--slate-950);
557
+ }
558
+ [data-theme="light"][data-primary="sky"] {
559
+ --color-on-primary: var(--slate-0);
560
+ }
561
+ [data-primary="blue"] {
562
+ --primary-50: var(--blue-50);
563
+ --primary-100: var(--blue-100);
564
+ --primary-300: var(--blue-300);
565
+ --primary-400: var(--blue-400);
566
+ --primary-500: var(--blue-500);
567
+ --primary-600: var(--blue-600);
568
+ --primary-700: var(--blue-700);
569
+ --primary-800: var(--blue-800);
570
+ --color-on-primary: var(--slate-0);
571
+ }
572
+ @media (prefers-color-scheme: dark) {
573
+ :root:not([data-theme="light"])[data-primary="blue"] {
574
+ --color-on-primary: var(--slate-950);
575
+ }
576
+ }
577
+ [data-theme="dark"][data-primary="blue"] {
578
+ --color-on-primary: var(--slate-950);
579
+ }
580
+ [data-theme="light"][data-primary="blue"] {
581
+ --color-on-primary: var(--slate-0);
582
+ }
583
+ [data-primary="indigo"] {
584
+ --primary-50: var(--indigo-50);
585
+ --primary-100: var(--indigo-100);
586
+ --primary-300: var(--indigo-300);
587
+ --primary-400: var(--indigo-400);
588
+ --primary-500: var(--indigo-500);
589
+ --primary-600: var(--indigo-600);
590
+ --primary-700: var(--indigo-700);
591
+ --primary-800: var(--indigo-800);
592
+ --color-on-primary: var(--slate-0);
593
+ }
594
+ @media (prefers-color-scheme: dark) {
595
+ :root:not([data-theme="light"])[data-primary="indigo"] {
596
+ --primary-300: var(--indigo-200);
597
+ --primary-400: var(--indigo-300);
598
+ --primary-500: var(--indigo-400);
599
+ --color-on-primary: var(--slate-950);
600
+ }
601
+ }
602
+ [data-theme="dark"][data-primary="indigo"] {
603
+ --primary-300: var(--indigo-200);
604
+ --primary-400: var(--indigo-300);
605
+ --primary-500: var(--indigo-400);
606
+ --color-on-primary: var(--slate-950);
607
+ }
608
+ [data-theme="light"][data-primary="indigo"] {
609
+ --primary-300: var(--indigo-300);
610
+ --primary-400: var(--indigo-400);
611
+ --primary-500: var(--indigo-500);
612
+ --color-on-primary: var(--slate-0);
613
+ }
614
+ [data-primary="violet"] {
615
+ --primary-50: var(--violet-50);
616
+ --primary-100: var(--violet-100);
617
+ --primary-300: var(--violet-300);
618
+ --primary-400: var(--violet-400);
619
+ --primary-500: var(--violet-500);
620
+ --primary-600: var(--violet-600);
621
+ --primary-700: var(--violet-700);
622
+ --primary-800: var(--violet-800);
623
+ --color-on-primary: var(--slate-0);
624
+ }
625
+ @media (prefers-color-scheme: dark) {
626
+ :root:not([data-theme="light"])[data-primary="violet"] {
627
+ --primary-300: var(--violet-200);
628
+ --primary-400: var(--violet-300);
629
+ --primary-500: var(--violet-400);
630
+ --color-on-primary: var(--slate-950);
631
+ }
632
+ }
633
+ [data-theme="dark"][data-primary="violet"] {
634
+ --primary-300: var(--violet-200);
635
+ --primary-400: var(--violet-300);
636
+ --primary-500: var(--violet-400);
637
+ --color-on-primary: var(--slate-950);
638
+ }
639
+ [data-theme="light"][data-primary="violet"] {
640
+ --primary-300: var(--violet-300);
641
+ --primary-400: var(--violet-400);
642
+ --primary-500: var(--violet-500);
643
+ --color-on-primary: var(--slate-0);
644
+ }
645
+ [data-primary="purple"] {
646
+ --primary-50: var(--purple-50);
647
+ --primary-100: var(--purple-100);
648
+ --primary-300: var(--purple-300);
649
+ --primary-400: var(--purple-400);
650
+ --primary-500: var(--purple-500);
651
+ --primary-600: var(--purple-600);
652
+ --primary-700: var(--purple-700);
653
+ --primary-800: var(--purple-800);
654
+ --color-on-primary: var(--slate-0);
655
+ }
656
+ @media (prefers-color-scheme: dark) {
657
+ :root:not([data-theme="light"])[data-primary="purple"] {
658
+ --color-on-primary: var(--slate-950);
659
+ }
660
+ }
661
+ [data-theme="dark"][data-primary="purple"] {
662
+ --color-on-primary: var(--slate-950);
663
+ }
664
+ [data-theme="light"][data-primary="purple"] {
665
+ --color-on-primary: var(--slate-0);
666
+ }
667
+ [data-primary="fuchsia"] {
668
+ --primary-50: var(--fuchsia-50);
669
+ --primary-100: var(--fuchsia-100);
670
+ --primary-300: var(--fuchsia-300);
671
+ --primary-400: var(--fuchsia-400);
672
+ --primary-500: var(--fuchsia-500);
673
+ --primary-600: var(--fuchsia-600);
674
+ --primary-700: var(--fuchsia-700);
675
+ --primary-800: var(--fuchsia-800);
676
+ --color-on-primary: var(--slate-0);
677
+ }
678
+ @media (prefers-color-scheme: dark) {
679
+ :root:not([data-theme="light"])[data-primary="fuchsia"] {
680
+ --color-on-primary: var(--slate-950);
681
+ }
682
+ }
683
+ [data-theme="dark"][data-primary="fuchsia"] {
684
+ --color-on-primary: var(--slate-950);
685
+ }
686
+ [data-theme="light"][data-primary="fuchsia"] {
687
+ --color-on-primary: var(--slate-0);
688
+ }
689
+ [data-primary="pink"] {
690
+ --primary-50: var(--pink-50);
691
+ --primary-100: var(--pink-100);
692
+ --primary-300: var(--pink-300);
693
+ --primary-400: var(--pink-400);
694
+ --primary-500: var(--pink-500);
695
+ --primary-600: var(--pink-600);
696
+ --primary-700: var(--pink-700);
697
+ --primary-800: var(--pink-800);
698
+ --color-on-primary: var(--slate-0);
699
+ }
700
+ @media (prefers-color-scheme: dark) {
701
+ :root:not([data-theme="light"])[data-primary="pink"] {
702
+ --color-on-primary: var(--slate-950);
703
+ }
704
+ }
705
+ [data-theme="dark"][data-primary="pink"] {
706
+ --color-on-primary: var(--slate-950);
707
+ }
708
+ [data-theme="light"][data-primary="pink"] {
709
+ --color-on-primary: var(--slate-0);
710
+ }
711
+ [data-primary="rose"] {
712
+ --primary-50: var(--rose-50);
713
+ --primary-100: var(--rose-100);
714
+ --primary-300: var(--rose-300);
715
+ --primary-400: var(--rose-400);
716
+ --primary-500: var(--rose-500);
717
+ --primary-600: var(--rose-600);
718
+ --primary-700: var(--rose-700);
719
+ --primary-800: var(--rose-800);
720
+ --color-on-primary: var(--slate-0);
721
+ }
722
+ @media (prefers-color-scheme: dark) {
723
+ :root:not([data-theme="light"])[data-primary="rose"] {
724
+ --color-on-primary: var(--slate-950);
725
+ }
726
+ }
727
+ [data-theme="dark"][data-primary="rose"] {
728
+ --color-on-primary: var(--slate-950);
729
+ }
730
+ [data-theme="light"][data-primary="rose"] {
731
+ --color-on-primary: var(--slate-0);
732
+ }
733
+ [data-primary="orange"] {
734
+ --primary-50: var(--orange-50);
735
+ --primary-100: var(--orange-100);
736
+ --primary-300: var(--orange-300);
737
+ --primary-400: var(--orange-400);
738
+ --primary-500: var(--orange-500);
739
+ --primary-600: var(--orange-600);
740
+ --primary-700: var(--orange-700);
741
+ --primary-800: var(--orange-800);
742
+ --color-on-primary: var(--slate-950);
743
+ }
744
+ [data-primary="amber"] {
745
+ --primary-50: var(--amber-50);
746
+ --primary-100: var(--amber-100);
747
+ --primary-300: var(--amber-300);
748
+ --primary-400: var(--amber-400);
749
+ --primary-500: var(--amber-500);
750
+ --primary-600: var(--amber-600);
751
+ --primary-700: var(--amber-700);
752
+ --primary-800: var(--amber-800);
753
+ --color-on-primary: var(--slate-950);
754
+ }
755
+ [data-primary="yellow"] {
756
+ --primary-50: var(--yellow-50);
757
+ --primary-100: var(--yellow-100);
758
+ --primary-300: var(--yellow-300);
759
+ --primary-400: var(--yellow-400);
760
+ --primary-500: var(--yellow-500);
761
+ --primary-600: var(--yellow-700);
762
+ --primary-700: var(--yellow-800);
763
+ --primary-800: var(--yellow-900);
764
+ --color-on-primary: var(--slate-0);
765
+ }
766
+ @media (prefers-color-scheme: dark) {
767
+ :root:not([data-theme="light"])[data-primary="yellow"] {
768
+ --color-on-primary: var(--slate-950);
769
+ }
770
+ }
771
+ [data-theme="dark"][data-primary="yellow"] {
772
+ --color-on-primary: var(--slate-950);
773
+ }
774
+ [data-theme="light"][data-primary="yellow"] {
775
+ --color-on-primary: var(--slate-0);
776
+ }
777
+ [data-primary="lime"] {
778
+ --primary-50: var(--lime-50);
779
+ --primary-100: var(--lime-100);
780
+ --primary-300: var(--lime-300);
781
+ --primary-400: var(--lime-400);
782
+ --primary-500: var(--lime-500);
783
+ --primary-600: var(--lime-600);
784
+ --primary-700: var(--lime-700);
785
+ --primary-800: var(--lime-800);
786
+ --color-on-primary: var(--slate-950);
787
+ }
788
+ [data-primary="green"] {
789
+ --primary-50: var(--green-50);
790
+ --primary-100: var(--green-100);
791
+ --primary-300: var(--green-300);
792
+ --primary-400: var(--green-400);
793
+ --primary-500: var(--green-500);
794
+ --primary-600: var(--green-600);
795
+ --primary-700: var(--green-700);
796
+ --primary-800: var(--green-800);
797
+ --color-on-primary: var(--slate-950);
798
+ }
799
+ [data-primary="emerald"] {
800
+ --primary-50: var(--emerald-50);
801
+ --primary-100: var(--emerald-100);
802
+ --primary-300: var(--emerald-300);
803
+ --primary-400: var(--emerald-400);
804
+ --primary-500: var(--emerald-500);
805
+ --primary-600: var(--emerald-600);
806
+ --primary-700: var(--emerald-700);
807
+ --primary-800: var(--emerald-800);
808
+ --color-on-primary: var(--slate-950);
809
+ }
810
+ [data-primary="teal"] {
811
+ --primary-50: var(--teal-50);
812
+ --primary-100: var(--teal-100);
813
+ --primary-300: var(--teal-300);
814
+ --primary-400: var(--teal-400);
815
+ --primary-500: var(--teal-500);
816
+ --primary-600: var(--teal-600);
817
+ --primary-700: var(--teal-700);
818
+ --primary-800: var(--teal-800);
819
+ --color-on-primary: var(--slate-950);
820
+ }
821
+ [data-primary="cyan"] {
822
+ --primary-50: var(--cyan-50);
823
+ --primary-100: var(--cyan-100);
824
+ --primary-300: var(--cyan-300);
825
+ --primary-400: var(--cyan-400);
826
+ --primary-500: var(--cyan-500);
827
+ --primary-600: var(--cyan-600);
828
+ --primary-700: var(--cyan-700);
829
+ --primary-800: var(--cyan-800);
830
+ --color-on-primary: var(--slate-950);
831
+ }
832
+ [data-primary="noir"] {
833
+ --primary-50: var(--slate-100);
834
+ --primary-100: var(--slate-200);
835
+ --primary-300: var(--slate-500);
836
+ --primary-400: var(--slate-700);
837
+ --primary-500: var(--slate-800);
838
+ --primary-600: var(--slate-900);
839
+ --primary-700: var(--slate-800);
840
+ --primary-800: var(--slate-950);
841
+ --color-on-primary: var(--slate-0);
842
+ }
843
+ @media (prefers-color-scheme: dark) {
844
+ :root:not([data-theme="light"])[data-primary="noir"] {
845
+ --primary-50: var(--slate-800);
846
+ --primary-100: var(--slate-700);
847
+ --primary-300: var(--slate-300);
848
+ --primary-400: var(--slate-200);
849
+ --primary-500: var(--slate-100);
850
+ --primary-600: var(--slate-50);
851
+ --primary-700: var(--slate-200);
852
+ --primary-800: var(--slate-300);
853
+ --color-on-primary: var(--slate-950);
854
+ }
855
+ }
856
+ [data-theme="dark"][data-primary="noir"] {
857
+ --primary-50: var(--slate-800);
858
+ --primary-100: var(--slate-700);
859
+ --primary-300: var(--slate-300);
860
+ --primary-400: var(--slate-200);
861
+ --primary-500: var(--slate-100);
862
+ --primary-600: var(--slate-50);
863
+ --primary-700: var(--slate-200);
864
+ --primary-800: var(--slate-300);
865
+ --color-on-primary: var(--slate-950);
866
+ }
867
+ [data-theme="light"][data-primary="noir"] {
868
+ --primary-50: var(--slate-100);
869
+ --primary-100: var(--slate-200);
870
+ --primary-300: var(--slate-500);
871
+ --primary-400: var(--slate-700);
872
+ --primary-500: var(--slate-800);
873
+ --primary-600: var(--slate-900);
874
+ --primary-700: var(--slate-800);
875
+ --primary-800: var(--slate-950);
876
+ --color-on-primary: var(--slate-0);
877
+ }
383
878
 
384
879
  /* Freeday components. GENERATED by tokens/build.mjs. Do not edit by hand. */
385
880
  /* Freeday base layer: reset ringan + default via token */
@@ -468,7 +963,7 @@ a { color: var(--color-primary); }
468
963
  * > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
469
964
  * topbar a tall containing block so it can travel, a bare grid cell can't. */
470
965
  .fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
471
- .fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:15.5rem;box-sizing:border-box;background:var(--color-surface);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
966
+ .fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:15.5rem;box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
472
967
  .fdy-app__content{flex:1;min-width:0;display:flex;flex-direction:column;}
473
968
  /* Brand is a fixed header, same height as the topbar so they align, and only
474
969
  * the nav below it scrolls. Bare text (no children) renders as one bold
@@ -541,7 +1036,23 @@ a { color: var(--color-primary); }
541
1036
  /* Desktop collapse: animate width -> 0 (content clips via overflow:hidden; the content area
542
1037
  * flexes in to fill). Not display:none, so the collapse/expand slides instead of snapping. */
543
1038
  @media (min-width:721px){.fdy-app--nav-collapsed .fdy-app__sidebar{width:0;border-right-width:0;}}
544
- .fdy-app__topbar{position:sticky;top:0;z-index:20;min-height:var(--space-16);display:flex;align-items:center;gap:var(--space-3);padding:0 var(--space-8);background:var(--color-surface);border-bottom:var(--bw) solid var(--color-border);}
1039
+ /* Desktop OVERLAY: the nav floats over the content instead of displacing it, the arrangement a
1040
+ * "menu mode" preference offers on a wide screen. The declarations below are the mobile off-canvas
1041
+ * ones repeated, and that repetition is not an oversight: the two conditions are
1042
+ * `(max-width:720px)` and `(min-width:721px) and .fdy-app--nav-overlay`, which no single selector
1043
+ * can express, and the mobile path has to keep working with no JS and no class at all. What the
1044
+ * modifier changes is the LAYOUT; every behaviour (backdrop, Escape, close-on-follow, focus return,
1045
+ * inert, the Tab trap) is the same code path the drawer already used. */
1046
+ @media (min-width:721px){
1047
+ .fdy-app--nav-overlay .fdy-app__sidebar{position:fixed;top:0;left:0;bottom:0;height:100dvh;width:16rem;z-index:60;transform:translateX(-100%);box-shadow:var(--shadow-4);transition:transform var(--dur-base) var(--ease-standard);}
1048
+ .fdy-app--nav-overlay.fdy-app--nav-open .fdy-app__sidebar{transform:translateX(0);}
1049
+ .fdy-app--nav-overlay .fdy-app__backdrop{display:block;position:fixed;inset:0;z-index:55;background:var(--color-scrim);opacity:0;visibility:hidden;pointer-events:none;transition:opacity var(--dur-base) var(--ease-standard),visibility var(--dur-base) var(--ease-standard);}
1050
+ .fdy-app--nav-overlay.fdy-app--nav-open .fdy-app__backdrop{opacity:1;visibility:visible;pointer-events:auto;}
1051
+ /* In overlay mode the sidebar is out of flow, so the collapse-to-zero-width rule above must not
1052
+ also apply — it would fight the transform for the same panel. */
1053
+ .fdy-app--nav-overlay.fdy-app--nav-collapsed .fdy-app__sidebar{width:16rem;border-right-width:var(--bw);}
1054
+ }
1055
+ .fdy-app__topbar{position:sticky;top:0;z-index:20;min-height:var(--space-16);display:flex;align-items:center;gap:var(--space-3);padding:0 var(--space-8);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-bottom:var(--bw) solid var(--color-border);}
545
1056
  .fdy-app__title{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);color:var(--color-text);margin:0 auto 0 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
546
1057
  .fdy-app__main{flex:1;padding:var(--space-8);}
547
1058
  .fdy-skip{position:absolute;left:var(--space-2);top:var(--space-2);background:var(--color-primary);color:var(--color-on-primary);padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);z-index:100;transform:translateY(-250%);transition:transform var(--dur-fast) var(--ease-standard);}
@@ -550,13 +1061,21 @@ a { color: var(--color-primary); }
550
1061
  * drop sticky so nothing escapes the box; sidebar stretches to fill it. */
551
1062
  .fdy-app--static .fdy-app__sidebar{position:static;height:auto;align-self:stretch;}
552
1063
  .fdy-app--static .fdy-app__topbar{position:static;}
553
- /* Mobile: the sidebar becomes an off-canvas drawer opened by the topbar toggle. */
1064
+ /* `pointer-events` on the backdrop is load-bearing, not a belt-and-braces extra. `visibility` is a
1065
+ * TRANSITIONED property here, so a backdrop on its way out stays `visible` for the whole duration
1066
+ * and keeps swallowing clicks aimed at the topbar underneath it — and switching INTO overlay mode is
1067
+ * worse, because the element goes display:none -> display:block with its visibility animating from
1068
+ * the initial `visible`, so a mode switch alone hands the page ~200ms of dead clicks. Measured: with
1069
+ * only `visibility:hidden`, elementFromPoint over the nav toggle returned the backdrop.
1070
+ * Mobile: the sidebar becomes an off-canvas drawer opened by the topbar toggle. */
554
1071
  .fdy-app__backdrop{display:none;}
555
1072
  @media (max-width:720px){
556
1073
  .fdy-app__sidebar{position:fixed;top:0;left:0;bottom:0;height:100dvh;width:16rem;z-index:60;transform:translateX(-100%);transition:transform var(--dur-base) var(--ease-standard);box-shadow:var(--shadow-4);}
557
1074
  .fdy-app--nav-open .fdy-app__sidebar{transform:translateX(0);}
558
- .fdy-app__backdrop{display:block;position:fixed;inset:0;z-index:55;background:rgba(8,10,20,.45);opacity:0;visibility:hidden;transition:opacity var(--dur-base) var(--ease-standard),visibility var(--dur-base) var(--ease-standard);}
559
- .fdy-app--nav-open .fdy-app__backdrop{opacity:1;visibility:visible;}
1075
+ /* --color-scrim, not a literal: this was the last raw colour left in a component stylesheet, and
1076
+ it also means the drawer behind the nav dims exactly like every other overlay in the kit. */
1077
+ .fdy-app__backdrop{display:block;position:fixed;inset:0;z-index:55;background:var(--color-scrim);opacity:0;visibility:hidden;pointer-events:none;transition:opacity var(--dur-base) var(--ease-standard),visibility var(--dur-base) var(--ease-standard);}
1078
+ .fdy-app--nav-open .fdy-app__backdrop{opacity:1;visibility:visible;pointer-events:auto;}
560
1079
  .fdy-app__main{padding:var(--space-5);}
561
1080
  .fdy-app__topbar{padding:0 var(--space-4);}
562
1081
  }
@@ -565,9 +1084,9 @@ a { color: var(--color-primary); }
565
1084
  /* Freeday. App bar (standalone top bar: brand · actions · spacer).
566
1085
  * Distinct from .fdy-app__topbar (which is bound to the shell grid). Use anywhere;
567
1086
  * sticky / elevated / dense / coloured via modifiers. Pure CSS. */
568
- .fdy-appbar{display:flex;align-items:center;gap:var(--space-3);height:var(--space-16);padding:0 var(--space-4);background:var(--color-surface);color:var(--color-text);border-bottom:var(--bw) solid var(--color-border);}
1087
+ .fdy-appbar{display:flex;align-items:center;gap:var(--space-3);height:var(--space-16);padding:0 var(--space-4);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);border-bottom:var(--bw) solid var(--color-border);}
569
1088
  .fdy-appbar--sticky{position:sticky;top:0;z-index:90;}
570
- .fdy-appbar--elevated{border-bottom:0;box-shadow:var(--shadow-2);}
1089
+ .fdy-appbar--elevated{border-bottom:0;box-shadow:var(--shadow-2),var(--surface-inset);}
571
1090
  .fdy-appbar--dense{height:var(--space-12);}
572
1091
  .fdy-appbar--primary{background:var(--color-primary);color:var(--color-on-primary);border-bottom-color:transparent;}
573
1092
  .fdy-appbar__brand{display:inline-flex;align-items:center;gap:var(--space-2);font-family:var(--font-display);font-weight:var(--weight-bold);font-size:var(--text-lg);color:inherit;white-space:nowrap;}
@@ -700,6 +1219,49 @@ a { color: var(--color-primary); }
700
1219
  @media (min-width:960px){.fdy-hide-above-md{display:none !important;}}
701
1220
  @media (min-width:1280px){.fdy-hide-above-lg{display:none !important;}}
702
1221
 
1222
+ /* Freeday. Busy overlay */
1223
+ /* The blocking "the app is working" state, for an operation the reader must not interrupt or
1224
+ * double-submit. There is deliberately NO markup contract to hand-write: it is created by
1225
+ * `Freeday.busy()` and removed by `Freeday.idle()`, because two of these on screen at once — with
1226
+ * two different captions, from two components that each thought they owned it — is the exact bug
1227
+ * this component exists to prevent. One owner, enforced by there being only one way in.
1228
+ *
1229
+ * It is not a dialog. Nothing here asks a question and nothing can be dismissed; interaction is
1230
+ * removed with `inert` on everything else, not with a focus trap. */
1231
+
1232
+ /* `display` is gated on `.is-open` rather than on `:popover-open`, and that is load-bearing. The UA
1233
+ * sheet hides a popover with `[popover]:not(:popover-open){display:none}`, but AUTHOR rules beat UA
1234
+ * rules whatever the specificity, so a bare `.fdy-busy{display:flex}` would paint the overlay
1235
+ * permanently the moment the element existed. Gating on a class instead also means the overlay
1236
+ * still works where the Popover API does not: it falls back to a plain fixed layer at this
1237
+ * z-index, which cannot clear an open <dialog> but does everything else. */
1238
+ .fdy-busy{display:none;position:fixed;inset:0;width:100%;height:100%;max-width:none;max-height:none;margin:0;padding:0;border:0;overflow:hidden;background:var(--color-scrim);z-index:190;}
1239
+ .fdy-busy.is-open{display:flex;align-items:center;justify-content:center;}
1240
+ /* The popover's own backdrop would double the dim; this element paints its own. */
1241
+ .fdy-busy::backdrop{background:transparent;}
1242
+
1243
+ /* The caption sits on a solid surface, not on the scrim. A scrim is semi-transparent over unknown
1244
+ * content, so text on it has no computable contrast ratio and no gate could ever cover it. On
1245
+ * --color-surface it is the same pair the contrast suite already checks.
1246
+ *
1247
+ * THIS IS ALSO WHY THIS PANEL DOES NOT JOIN THE GLASS AXIS, and the omission is a decision rather
1248
+ * than an oversight. Every other raised panel reads --color-surface-raised and frosts under
1249
+ * [data-style="glass"]; this one stays opaque. The glass gate asserts its ink against the panel
1250
+ * composited over the PAGE GROUND (--color-surface-2), which is what sits behind a card or a modal.
1251
+ * Behind this one is the scrim, over arbitrary content — the very case that has no computable
1252
+ * ratio. A frosted busy panel would be the one surface in the kit whose caption nothing could
1253
+ * verify, while it is up precisely because the app is doing something the reader must not
1254
+ * interrupt. Left opaque on purpose; do not "fix" it for consistency. */
1255
+ .fdy-busy__panel{display:flex;flex-direction:column;align-items:center;gap:var(--space-4);padding:var(--space-6) var(--space-8);border-radius:var(--radius-lg);background:var(--color-surface);box-shadow:var(--shadow-lift);max-width:min(20rem,calc(100vw - var(--space-8)));}
1256
+ .fdy-busy__mark{display:flex;align-items:center;justify-content:center;min-height:2.5rem;}
1257
+ .fdy-busy__caption{margin:0;font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text);text-align:center;}
1258
+
1259
+ /* Nothing travels under reduced motion. The spinner keeps turning (slowed by spinner.css): a
1260
+ * loading indicator that stops moving reads as "finished", which is the opposite of the message. */
1261
+ @media (prefers-reduced-motion:reduce){
1262
+ .fdy-busy{transition:none;}
1263
+ }
1264
+
703
1265
  /* Freeday. Button */
704
1266
  .fdy-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
705
1267
  height:var(--control-h);padding:0 var(--space-4);
@@ -803,7 +1365,7 @@ a { color: var(--color-primary); }
803
1365
  /* Freeday. Card */
804
1366
  /* position:relative, containing block for out-of-flow card content (see base.css); it also gives
805
1367
  consumers the anchor a corner ribbon/badge needs. */
806
- .fdy-card{position:relative;background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-lift);transition:box-shadow var(--dur-base) var(--ease-standard),transform var(--dur-base) var(--ease-standard);}
1368
+ .fdy-card{position:relative;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-lift),var(--surface-inset);transition:box-shadow var(--dur-base) var(--ease-standard),transform var(--dur-base) var(--ease-standard);}
807
1369
  .fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
808
1370
  .fdy-card--interactive{cursor:pointer;}
809
1371
  .fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
@@ -1117,9 +1679,22 @@ a { color: var(--color-primary); }
1117
1679
  grid track, `container-type:inline-size` on a standalone .fdy-stat would stop it sizing to its
1118
1680
  own content. The unconditional rule above stays as the fallback wherever @container is missing. */
1119
1681
  @supports (container-type:inline-size){
1120
- .fdy-stats>.fdy-stat{container-type:inline-size;}
1121
- .fdy-stats>.fdy-stat .fdy-stat__value{font-size:clamp(var(--text-xl),11cqw,var(--text-3xl));}
1682
+ .fdy-stats:not(.fdy-stats--inline)>.fdy-stat{container-type:inline-size;}
1683
+ .fdy-stats:not(.fdy-stats--inline)>.fdy-stat .fdy-stat__value{font-size:clamp(var(--text-xl),11cqw,var(--text-3xl));}
1122
1684
  }
1685
+ /* The price of that container, and the reason --inline opts out of it (#050). `container-type`
1686
+ applies inline-size CONTAINMENT: the tile's min-content and max-content contributions are both
1687
+ ZERO. So the 11rem floor above is not a taste choice, it is the only thing giving these tracks a
1688
+ size, and re-tracking the grid on content (`auto`, `max-content`, `minmax(0,…)`) resolves every
1689
+ track to 0. `justify-items` is the sharper edge: anything but the default `stretch` sizes the
1690
+ tile to that zero contribution even when the track is correctly wide, and three labels paint on
1691
+ top of each other. Measured from IDU_EMATE_ACCT_WEB's page header, where a hugging strip came out
1692
+ 40px wide, all of it gap.
1693
+ --inline is the shape that header wanted: a strip that hugs its numbers and sits beside a page
1694
+ title, instead of a full-width band whose 11rem floor collapses to one column at 390px. It cannot
1695
+ have both, so it drops the container and keeps the flat --text-3xl the base rule already sets;
1696
+ a track sized to its own content is never the narrow track the clamp exists for. */
1697
+ .fdy-stats--inline{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:minmax(0,max-content);}
1123
1698
  .fdy-stats--boxed{background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);gap:0;}
1124
1699
  .fdy-stats--boxed .fdy-stat{padding:var(--space-5);border-right:var(--bw) solid var(--color-border-muted);}
1125
1700
  .fdy-stats--boxed .fdy-stat:last-child{border-right:0;}
@@ -1248,7 +1823,7 @@ a { color: var(--color-primary); }
1248
1823
  /* display is gated behind [open] so the native <dialog> stays hidden until opened.
1249
1824
  * An unconditional display here would beat the UA `dialog:not([open]){display:none}`
1250
1825
  * rule (author origin wins over user-agent) and leave the drawer visible on load. */
1251
- .fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;max-height:none;width:min(20rem,82vw);flex-direction:column;border:0;background:var(--color-surface);color:var(--color-text);box-shadow:var(--shadow-4);overflow:hidden;transform:translateX(-100%);transition:transform var(--dur-slow) var(--ease-standard),overlay var(--dur-slow) allow-discrete,display var(--dur-slow) allow-discrete;}
1826
+ .fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;max-height:none;width:min(20rem,82vw);flex-direction:column;border:0;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-4),var(--surface-inset);overflow:hidden;transform:translateX(-100%);transition:transform var(--dur-slow) var(--ease-standard),overlay var(--dur-slow) allow-discrete,display var(--dur-slow) allow-discrete;}
1252
1827
  .fdy-drawer[open]{display:flex;transform:none;}
1253
1828
  .fdy-drawer--right{inset:0 0 0 auto;transform:translateX(100%);}
1254
1829
  .fdy-drawer::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition:opacity var(--dur-slow) var(--ease-standard),overlay var(--dur-slow) allow-discrete,display var(--dur-slow) allow-discrete;}
@@ -1325,8 +1900,9 @@ a { color: var(--color-primary); }
1325
1900
  /* Fields are direct children. Drop the standalone field's 22rem cap; default to the
1326
1901
  * medium column width with no unbounded growth (that's what caused the raggedness). */
1327
1902
  .fdy-filterbar>.fdy-field{max-width:none;flex:0 0 auto;width:12rem;}
1328
- /* Controls fill their field's column (drop the standalone input-group/combo caps). */
1329
- .fdy-filterbar>.fdy-field .fdy-input-group,.fdy-filterbar>.fdy-field .fdy-combo{max-width:none;width:100%;}
1903
+ /* Controls fill their field's column: released in input.css for every field, not just this one.
1904
+ The rule used to live here, naming the input group and the combo only, which is why an
1905
+ .fdy-autocomplete or an .fdy-cascade in a --w-2xl field still stopped 48px short of it (#051). */
1330
1906
  /* Column-width rhythm, pick one per field so bars across the app line up. */
1331
1907
  .fdy-filterbar>.fdy-field--w-sm{width:9rem;}
1332
1908
  .fdy-filterbar>.fdy-field--w-lg{width:15rem;}
@@ -1403,6 +1979,15 @@ a { color: var(--color-primary); }
1403
1979
 
1404
1980
  /* Freeday. Field & Input */
1405
1981
  .fdy-field{display:flex;flex-direction:column;gap:var(--space-2);max-width:22rem;}
1982
+ /* Inside a labelled field, a control fills the field (#051). The 22rem cap those controls carry is
1983
+ for one standing on its own, with no field to take a width from; #017 already released the date
1984
+ and time pickers here for exactly this reason, and its comment claims a picker then fills its
1985
+ field "like every other control". That was not true of these four: a `--full` row in a
1986
+ .fdy-form-grid held a 352px combo under a 600px label, and a 26rem toolbar search field hid 64px
1987
+ of dead space INSIDE itself, so an author who wrote `gap:var(--space-3)` measured 76px on screen.
1988
+ The field's own max-width above is what governs, and it still caps a field nobody has widened, so
1989
+ this changes nothing wherever the cap was already doing its job. */
1990
+ .fdy-field>.fdy-input-group,.fdy-field>.fdy-combo,.fdy-field>.fdy-autocomplete,.fdy-field>.fdy-cascade{max-width:none;}
1406
1991
  /* A grouped field is legitimately a <fieldset> (it needs a <legend>), e.g. a radio/checkbox group.
1407
1992
  Neutralise the UA groove border / padding / min-inline-size so it lays out identically to the
1408
1993
  <div> form (min-inline-size:min-content would otherwise block the filterbar column widths). */
@@ -1491,7 +2076,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1491
2076
 
1492
2077
  /* Freeday. Menu (action popup) + Split button. Enhanced by freeday-menu.js. */
1493
2078
  .fdy-menu-wrap{position:relative;display:inline-flex;}
1494
- .fdy-menu{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;min-width:11rem;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
2079
+ .fdy-menu{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;min-width:11rem;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3),var(--surface-inset);}
1495
2080
  .fdy-menu[hidden]{display:none;}
1496
2081
  .fdy-menu--end{left:auto;right:0;}
1497
2082
  .fdy-menu__item{display:flex;align-items:center;gap:var(--space-2);width:100%;padding:var(--space-2) var(--space-3);border:0;background:transparent;border-radius:var(--radius-sm);font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);text-align:left;cursor:pointer;white-space:nowrap;}
@@ -1522,7 +2107,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1522
2107
  .fdy-btn-split__toggle svg{display:block;width:1rem;height:1rem;}
1523
2108
 
1524
2109
  /* Freeday. Modal (native <dialog>) */
1525
- .fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - var(--space-16));padding:0;border:var(--bw) solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-surface);color:var(--color-text);box-shadow:var(--shadow-lift-hover);overflow:hidden;flex-direction:column;opacity:0;transform:translateY(8px) scale(.98);transition:opacity var(--dur-base) var(--ease-spring),transform var(--dur-base) var(--ease-spring),overlay var(--dur-base) allow-discrete,display var(--dur-base) allow-discrete;}
2110
+ .fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - var(--space-16));padding:0;border:var(--bw) solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-lift-hover),var(--surface-inset);overflow:hidden;flex-direction:column;opacity:0;transform:translateY(8px) scale(.98);transition:opacity var(--dur-base) var(--ease-spring),transform var(--dur-base) var(--ease-spring),overlay var(--dur-base) allow-discrete,display var(--dur-base) allow-discrete;}
1526
2111
  .fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
1527
2112
  .fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
1528
2113
  .fdy-modal--lg{width:min(48rem,calc(100vw - var(--space-8)));}
@@ -1672,7 +2257,18 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1672
2257
  .fdy-step.is-active .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary-strong);}
1673
2258
  .fdy-step.is-complete .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary);color:var(--color-on-primary);}
1674
2259
  .fdy-step.is-error .fdy-step__marker{border-color:var(--color-danger);background:var(--color-danger-btn);color:var(--color-on-danger);}
2260
+ /* The error badge. Colour alone cannot carry "this step has a problem" — the marker already turns
2261
+ red and that is exactly the signal a colour-blind reader loses — so the badge holds a COUNT and
2262
+ the step names it in text. Positioned against `.fdy-step` rather than `__btn`, because the
2263
+ indicator-only variant has no `__btn` and the badge has to land in the same place in both.
2264
+ It is a SIBLING of the marker, never a child: the enhancer rewrites `__marker.innerHTML` on
2265
+ every render, so a badge inside it would be destroyed the first time the reader moved a step. */
2266
+ .fdy-step__badge{position:absolute;top:0;left:50%;z-index:2;transform:translate(.45rem,-.3rem);min-width:1.05rem;height:1.05rem;padding:0 .25rem;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--radius-full);background:var(--color-danger-btn);color:var(--color-on-danger);font-size:var(--text-xs);font-weight:var(--weight-semibold);font-variant-numeric:tabular-nums;line-height:1;}
1675
2267
  .fdy-step__label{font-size:var(--text-sm);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;}
2268
+ .fdy-step.is-error .fdy-step__label{color:var(--color-danger-strong);}
2269
+ /* While a guard is deciding, the whole control is spoken for: the nav buttons are disabled by the
2270
+ enhancer, and this dims the header so a second click has nothing to aim at. */
2271
+ .fdy-stepper[aria-busy="true"]{opacity:.72;}
1676
2272
  .fdy-step.is-active .fdy-step__label{color:var(--color-text);font-weight:var(--weight-semibold);}
1677
2273
  .fdy-step__btn:focus-visible{outline:none;}
1678
2274
  .fdy-step__btn:focus-visible .fdy-step__marker{box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 30%,transparent);}
@@ -1691,6 +2287,13 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1691
2287
  .fdy-table td{padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);color:var(--color-text);}
1692
2288
  .fdy-table tbody tr:last-child td{border-bottom:0;}
1693
2289
  .fdy-table tbody tr{transition:background-color var(--dur-fast) var(--ease-standard);}
2290
+ /* Zebra rows. Sits BEFORE :hover and [aria-selected] on purpose: all three land on the same
2291
+ specificity (0,2,2), so source order is what decides, and a stripe that won would make hover
2292
+ invisible on every second row. The tint is HALF a surface step rather than --color-surface-2
2293
+ itself, for the same reason: hover paints the full step, so the two must not be the same colour.
2294
+ Exposed as a Tier-3 token through the var() FALLBACK, never as a declaration — a custom property
2295
+ set here would outrank a host's own :root override. */
2296
+ .fdy-table--striped tbody tr:nth-child(even){background:var(--fdy-table-stripe,color-mix(in srgb,var(--color-surface-2) 55%,var(--color-surface)));}
1694
2297
  .fdy-table tbody tr:hover{background:var(--color-surface-2);}
1695
2298
  /* Monospace data, ids, codes, IPs, timestamps. Alignment-neutral so it works in any cell
1696
2299
  or inline (`<span class="fdy-mono">`), not just numeric columns. */
@@ -1755,6 +2358,12 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1755
2358
  a hovered row should band unbroken across the freeze line, says so itself. */
1756
2359
  .fdy-table--sticky tbody .fdy-table__freeze{z-index:1;}
1757
2360
  .fdy-table--sticky tbody td.fdy-table__freeze{background:var(--color-surface);}
2361
+ /* A frozen cell has to paint an OPAQUE background of its own (it scrolls over its neighbours), so
2362
+ on a striped table it would cut a plain column straight through the zebra. Repaint it with the
2363
+ stripe — and deliberately WITHOUT `.fdy-table--sticky` in the selector, which would push this to
2364
+ (0,4,3) and beat the hover rule below. At (0,3,3) it ties, and the later rule wins, so a hovered
2365
+ row still bands unbroken across the freeze line. */
2366
+ .fdy-table--striped tbody tr:nth-child(even) td.fdy-table__freeze{background:var(--fdy-table-stripe,color-mix(in srgb,var(--color-surface-2) 55%,var(--color-surface)));}
1758
2367
  .fdy-table--sticky tbody tr:hover td.fdy-table__freeze{background:var(--color-surface-2);}
1759
2368
  /* A frozen header cell is stuck on both axes wherever the head is frozen too, so
1760
2369
  it has to outrank both. */