@cahyo-dimas/freeday 3.5.0 → 3.7.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 (59) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/COMPONENTS.md +251 -8
  3. package/README.id.md +19 -6
  4. package/README.md +17 -5
  5. package/USAGE.md +42 -0
  6. package/adapters/blazor/FdyDateRange.razor.cs +10 -0
  7. package/adapters/blazor/FdyTable.razor +6 -2
  8. package/adapters/blazor/FdyTable.razor.cs +21 -0
  9. package/adapters/blazor/TableTypes.cs +4 -0
  10. package/adapters/react/components/FdyDateRange.tsx +8 -2
  11. package/adapters/react/components/FdyTable.tsx +24 -3
  12. package/adapters/react/index.d.ts +1 -1
  13. package/adapters/vue/components/FdyDateRange.vue +11 -5
  14. package/adapters/vue/components/FdyTable.vue +18 -2
  15. package/dist/freeday-confirm.js +101 -0
  16. package/dist/freeday-copy.js +58 -0
  17. package/dist/freeday-datepicker.js +17 -2
  18. package/dist/freeday-form.js +30 -1
  19. package/dist/freeday-search.js +77 -0
  20. package/dist/freeday.bundle.css +544 -122
  21. package/dist/freeday.css +241 -118
  22. package/dist/freeday.d.ts +17 -2
  23. package/dist/freeday.js +286 -3
  24. package/dist/freeday.tokens.css +303 -4
  25. package/docs/agent-onboarding.md +8 -0
  26. package/docs/getting-started.md +1 -1
  27. package/package.json +2 -2
  28. package/src/base.css +1 -1
  29. package/src/components/accordion.css +1 -1
  30. package/src/components/alert.css +5 -5
  31. package/src/components/app-shell.css +2 -2
  32. package/src/components/appbar.css +3 -3
  33. package/src/components/avatar.css +17 -9
  34. package/src/components/badge.css +18 -14
  35. package/src/components/breadcrumb.css +1 -1
  36. package/src/components/button.css +21 -13
  37. package/src/components/card.css +7 -0
  38. package/src/components/carousel.css +3 -3
  39. package/src/components/cascade.css +4 -4
  40. package/src/components/chip.css +33 -15
  41. package/src/components/combo.css +5 -5
  42. package/src/components/composition.css +36 -0
  43. package/src/components/datepicker.css +8 -8
  44. package/src/components/description-list.css +11 -1
  45. package/src/components/file-upload.css +9 -4
  46. package/src/components/input-group.css +2 -2
  47. package/src/components/input.css +2 -2
  48. package/src/components/menu.css +3 -3
  49. package/src/components/pagination.css +1 -1
  50. package/src/components/selection.css +8 -2
  51. package/src/components/skeleton.css +1 -1
  52. package/src/components/states.css +7 -0
  53. package/src/components/stepper.css +1 -1
  54. package/src/components/table.css +25 -11
  55. package/src/components/tabs.css +1 -1
  56. package/src/components/timepicker.css +3 -3
  57. package/src/components/toast.css +1 -1
  58. package/src/components/tree.css +1 -1
  59. package/tokens/tokens.json +57 -4
package/dist/freeday.css CHANGED
@@ -12,7 +12,7 @@ body {
12
12
  -webkit-font-smoothing: antialiased;
13
13
  }
14
14
  h1, h2, h3, h4 { font-family: var(--font-display); font-weight: var(--weight-bold); letter-spacing: var(--tracking-tighter); line-height: var(--leading-snug); margin: 0; }
15
- a { color: var(--color-primary); }
15
+ a { color: var(--color-primary-strong); }
16
16
  :focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-width); border-radius: var(--radius-xs); }
17
17
  :where(button, input, select, textarea) { font: inherit; }
18
18
  /* Hidden from sight, kept for assistive tech. `clip` hides PAINTING, not LAYOUT: the box still
@@ -52,7 +52,7 @@ a { color: var(--color-primary); }
52
52
  .fdy-accordion__item > summary::after{content:"";margin-left:auto;margin-top:-.15rem;flex:none;width:.5rem;height:.5rem;border-right:2px solid var(--color-text-subtle);border-bottom:2px solid var(--color-text-subtle);transform:rotate(45deg);transition:transform var(--dur-fast) var(--ease-standard);}
53
53
  .fdy-accordion__item[open] > summary::after{transform:rotate(225deg);margin-top:.1rem;}
54
54
  .fdy-accordion__item > summary:hover{background:var(--color-surface-2);}
55
- .fdy-accordion__item > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
55
+ .fdy-accordion__item > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px rgba(var(--color-primary-rgb),.45);}
56
56
  .fdy-accordion__panel{padding:0 var(--space-4) var(--space-4);color:var(--color-text-muted);font-size:var(--text-sm);line-height:var(--leading-normal);}
57
57
  /* Content reveal on open (fade + rise). Native <details> can't animate its own height cross-
58
58
  * browser without JS, so we animate the revealed panel instead, cheap, safe, reduced-motion gated. */
@@ -67,14 +67,14 @@ a { color: var(--color-primary); }
67
67
  .fdy-alert__text{color:var(--color-text);line-height:var(--leading-normal);}
68
68
  .fdy-alert__close{flex:none;align-self:flex-start;border:0;background:transparent;color:var(--color-text-muted);cursor:pointer;font-size:var(--text-lg);line-height:1;padding:0 var(--space-1);border-radius:var(--radius-sm);}
69
69
  .fdy-alert__close:hover{color:var(--color-text);}
70
- .fdy-alert__close:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
71
- .fdy-alert--info{background:var(--color-info-soft);border-color:color-mix(in srgb,var(--color-info) 26%,transparent);}
70
+ .fdy-alert__close:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
71
+ .fdy-alert--info{background:var(--color-info-soft);border-color:rgba(var(--color-info-rgb),.26);}
72
72
  .fdy-alert--info .fdy-alert__title,.fdy-alert--info .fdy-alert__icon{color:var(--color-info-strong);}
73
- .fdy-alert--success{background:var(--color-success-soft);border-color:color-mix(in srgb,var(--color-success) 26%,transparent);}
73
+ .fdy-alert--success{background:var(--color-success-soft);border-color:rgba(var(--color-success-rgb),.26);}
74
74
  .fdy-alert--success .fdy-alert__title,.fdy-alert--success .fdy-alert__icon{color:var(--color-success-strong);}
75
- .fdy-alert--warning{background:var(--color-warning-soft);border-color:color-mix(in srgb,var(--color-warning) 26%,transparent);}
75
+ .fdy-alert--warning{background:var(--color-warning-soft);border-color:rgba(var(--color-warning-rgb),.26);}
76
76
  .fdy-alert--warning .fdy-alert__title,.fdy-alert--warning .fdy-alert__icon{color:var(--color-warning-strong);}
77
- .fdy-alert--danger{background:var(--color-danger-soft);border-color:color-mix(in srgb,var(--color-danger) 26%,transparent);}
77
+ .fdy-alert--danger{background:var(--color-danger-soft);border-color:rgba(var(--color-danger-rgb),.26);}
78
78
  .fdy-alert--danger .fdy-alert__title,.fdy-alert--danger .fdy-alert__icon{color:var(--color-danger-strong);}
79
79
 
80
80
  /* Freeday. App Shell.
@@ -123,7 +123,7 @@ a { color: var(--color-primary); }
123
123
  .fdy-nav__item:hover{background:var(--color-surface-2);color:var(--color-text);}
124
124
  /* Active indicator stays in the primary ramp so it never clashes with a re-themed accent. */
125
125
  .fdy-nav__item[aria-current="page"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
126
- .fdy-nav__item:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
126
+ .fdy-nav__item:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
127
127
  /* Sidebar-menu extras: per-node icon (leading), count badge (trailing), and a
128
128
  * text label that truncates. Any of these are optional, a bare text item works. */
129
129
  .fdy-nav__icon{flex:none;display:inline-flex;width:1.15rem;height:1.15rem;}
@@ -141,7 +141,7 @@ a { color: var(--color-primary); }
141
141
  .fdy-nav__grouplabel{list-style:none;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);padding:var(--space-3) var(--space-3) var(--space-1);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:var(--tracking-wide);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
142
142
  .fdy-nav__grouplabel::-webkit-details-marker{display:none;}
143
143
  .fdy-nav__grouplabel:hover{color:var(--color-text-muted);}
144
- .fdy-nav__grouplabel:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
144
+ .fdy-nav__grouplabel:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
145
145
  .fdy-nav__grouplabel::after{content:"";flex:none;width:.45rem;height:.45rem;margin-right:var(--space-1);border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform var(--dur-fast) var(--ease-standard);opacity:.7;}
146
146
  .fdy-nav__group[open] > .fdy-nav__grouplabel::after{transform:rotate(45deg);}
147
147
  /* --flat: calmer, non-collapsible grouped nav, hide the caret, drop the
@@ -242,13 +242,13 @@ a { color: var(--color-primary); }
242
242
  is the same component whether the nav is a sidebar column or a top row. */
243
243
  /* Controls on a coloured app bar go on-colour (icons stay legible, hover is a light wash) */
244
244
  .fdy-appbar--primary .fdy-btn--ghost{background:transparent;border-color:transparent;color:var(--color-on-primary);box-shadow:none;}
245
- .fdy-appbar--primary .fdy-btn--ghost:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);transform:none;}
245
+ .fdy-appbar--primary .fdy-btn--ghost:hover{background:rgba(var(--color-on-primary-rgb),.16);color:var(--color-on-primary);transform:none;}
246
246
  /* Nav links on a coloured bar: full on-colour ink in every state (a dimmed variant would trade away
247
247
  the contrast the bar's own token pair guarantees). The current page is carried by the background
248
248
  wash plus the semibold weight the base rule already applies, and by `aria-current` itself. */
249
249
  .fdy-appbar--primary .fdy-nav__item{color:var(--color-on-primary);}
250
- .fdy-appbar--primary .fdy-nav__item:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);}
251
- .fdy-appbar--primary .fdy-nav__item[aria-current="page"]{background:color-mix(in srgb,var(--color-on-primary) 24%,transparent);color:var(--color-on-primary);}
250
+ .fdy-appbar--primary .fdy-nav__item:hover{background:rgba(var(--color-on-primary-rgb),.16);color:var(--color-on-primary);}
251
+ .fdy-appbar--primary .fdy-nav__item[aria-current="page"]{background:rgba(var(--color-on-primary-rgb),.24);color:var(--color-on-primary);}
252
252
 
253
253
  /* Freeday. Autocomplete (text input + filtered suggestion listbox, APG editable combobox).
254
254
  * Enhanced by freeday-autocomplete.js. */
@@ -273,6 +273,8 @@ a { color: var(--color-primary); }
273
273
  .fdy-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--text-xs);}
274
274
  .fdy-avatar--sm{width:2rem;height:2rem;font-size:var(--text-xs);}
275
275
  .fdy-avatar--lg{width:3.5rem;height:3.5rem;font-size:var(--text-lg);}
276
+ /* Square: a mark that is not a person (a company logo, a card's icon chip, #070). */
277
+ .fdy-avatar--square{border-radius:var(--radius-md);}
276
278
  .fdy-avatar-group{display:inline-flex;}
277
279
  .fdy-avatar-group .fdy-avatar{border:2px solid var(--color-surface);margin-left:calc(var(--space-2)*-1);}
278
280
  .fdy-avatar-group .fdy-avatar:first-child{margin-left:0;}
@@ -280,22 +282,28 @@ a { color: var(--color-primary); }
280
282
  * text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
281
283
  * test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
282
284
  * hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
283
- .fdy-avatar--tone-1,.fdy-avatar--tone-2,.fdy-avatar--tone-3,.fdy-avatar--tone-4,.fdy-avatar--tone-5,.fdy-avatar--tone-6,.fdy-avatar--tone-7,.fdy-avatar--tone-8{background:color-mix(in srgb,var(--_fdy-avatar-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-avatar-tone) 50%,var(--color-text));}
284
- .fdy-avatar--tone-1{--_fdy-avatar-tone:var(--tone-1);}
285
- .fdy-avatar--tone-2{--_fdy-avatar-tone:var(--tone-2);}
286
- .fdy-avatar--tone-3{--_fdy-avatar-tone:var(--tone-3);}
287
- .fdy-avatar--tone-4{--_fdy-avatar-tone:var(--tone-4);}
288
- .fdy-avatar--tone-5{--_fdy-avatar-tone:var(--tone-5);}
289
- .fdy-avatar--tone-6{--_fdy-avatar-tone:var(--tone-6);}
290
- .fdy-avatar--tone-7{--_fdy-avatar-tone:var(--tone-7);}
291
- .fdy-avatar--tone-8{--_fdy-avatar-tone:var(--tone-8);}
285
+ /* One flat token per half, computed in advance by tokens/build.mjs: --tone-N-bg is 18% of the
286
+ * tone over --color-surface, --tone-N-ink is 50% of it toward --color-text, both per theme.
287
+ * They USED to be mixed here, with the hue injected per element through a private custom
288
+ * property — which put var() inside color-mix(), the one shape that cannot be rescued: valid at
289
+ * parse time, invalid at computed-value time, and there `background` and `color` drop to their
290
+ * INITIAL values, not to any fallback. @supports answered it for exactly as long as somebody
291
+ * remembered to write the counterpart; a precomputed token removes the question. */
292
+ .fdy-avatar--tone-1{background:var(--tone-1-bg);color:var(--tone-1-ink);}
293
+ .fdy-avatar--tone-2{background:var(--tone-2-bg);color:var(--tone-2-ink);}
294
+ .fdy-avatar--tone-3{background:var(--tone-3-bg);color:var(--tone-3-ink);}
295
+ .fdy-avatar--tone-4{background:var(--tone-4-bg);color:var(--tone-4-ink);}
296
+ .fdy-avatar--tone-5{background:var(--tone-5-bg);color:var(--tone-5-ink);}
297
+ .fdy-avatar--tone-6{background:var(--tone-6-bg);color:var(--tone-6-ink);}
298
+ .fdy-avatar--tone-7{background:var(--tone-7-bg);color:var(--tone-7-ink);}
299
+ .fdy-avatar--tone-8{background:var(--tone-8-bg);color:var(--tone-8-ink);}
292
300
 
293
301
  /* Freeday. Badge */
294
- .fdy-badge{display:inline-flex;align-items:center;gap:var(--space-1);font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);line-height:1;white-space:nowrap;padding:.35em .7em;border-radius:var(--radius-full);color:var(--color-primary-strong);background:var(--color-primary-soft);border:var(--bw) solid color-mix(in srgb,var(--color-primary) 22%,transparent);box-shadow:0 1px 1.5px rgba(16,14,30,.05);}
295
- .fdy-badge--success{color:var(--color-success-strong);background:var(--color-success-soft);border-color:color-mix(in srgb,var(--color-success) 26%,transparent);}
296
- .fdy-badge--warning{color:var(--color-warning-strong);background:var(--color-warning-soft);border-color:color-mix(in srgb,var(--color-warning) 26%,transparent);}
297
- .fdy-badge--danger{color:var(--color-danger-strong);background:var(--color-danger-soft);border-color:color-mix(in srgb,var(--color-danger) 26%,transparent);}
298
- .fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:color-mix(in srgb,var(--color-info) 26%,transparent);}
302
+ .fdy-badge{display:inline-flex;align-items:center;gap:var(--space-1);font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);line-height:1;white-space:nowrap;padding:.35em .7em;border-radius:var(--radius-full);color:var(--color-primary-strong);background:var(--color-primary-soft);border:var(--bw) solid rgba(var(--color-primary-rgb),.22);box-shadow:0 1px 1.5px rgba(16,14,30,.05);}
303
+ .fdy-badge--success{color:var(--color-success-strong);background:var(--color-success-soft);border-color:rgba(var(--color-success-rgb),.26);}
304
+ .fdy-badge--warning{color:var(--color-warning-strong);background:var(--color-warning-soft);border-color:rgba(var(--color-warning-rgb),.26);}
305
+ .fdy-badge--danger{color:var(--color-danger-strong);background:var(--color-danger-soft);border-color:rgba(var(--color-danger-rgb),.26);}
306
+ .fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:rgba(var(--color-info-rgb),.26);}
299
307
  .fdy-badge--outline{background:transparent;border-color:var(--color-border-strong);color:var(--color-text-muted);box-shadow:none;}
300
308
 
301
309
  /* Categorical tone badges, a status vocabulary larger than the semantic palette, from the
@@ -307,15 +315,19 @@ a { color: var(--color-primary); }
307
315
  * cannot. A back-office document list carrying Draft, Submitted, Approved, Completed, Settled,
308
316
  * Closed, Transferred, InDeclaration, Open and Rejected in ONE column collapsed to three colours,
309
317
  * which is the report this shipped for. */
310
- .fdy-badge--tone-1,.fdy-badge--tone-2,.fdy-badge--tone-3,.fdy-badge--tone-4,.fdy-badge--tone-5,.fdy-badge--tone-6,.fdy-badge--tone-7,.fdy-badge--tone-8{background:color-mix(in srgb,var(--_fdy-badge-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-badge-tone) 50%,var(--color-text));border-color:color-mix(in srgb,var(--_fdy-badge-tone) 26%,transparent);}
311
- .fdy-badge--tone-1{--_fdy-badge-tone:var(--tone-1);}
312
- .fdy-badge--tone-2{--_fdy-badge-tone:var(--tone-2);}
313
- .fdy-badge--tone-3{--_fdy-badge-tone:var(--tone-3);}
314
- .fdy-badge--tone-4{--_fdy-badge-tone:var(--tone-4);}
315
- .fdy-badge--tone-5{--_fdy-badge-tone:var(--tone-5);}
316
- .fdy-badge--tone-6{--_fdy-badge-tone:var(--tone-6);}
317
- .fdy-badge--tone-7{--_fdy-badge-tone:var(--tone-7);}
318
- .fdy-badge--tone-8{--_fdy-badge-tone:var(--tone-8);}
318
+ /* Precomputed tone tokens, the same --tone-N-bg / --tone-N-ink the avatar and the chip read, so
319
+ * one contrast assertion still covers all three. --tone-N-border-soft is the badge's own edge:
320
+ * 26% of the tone over transparent, which is a translucent colour and therefore stays rgba()
321
+ * rather than flattening to a hex. See avatar.css for why none of this is mixed in the
322
+ * component any more, and tokens/build.mjs for where the mixing moved to. */
323
+ .fdy-badge--tone-1{background:var(--tone-1-bg);color:var(--tone-1-ink);border-color:var(--tone-1-border-soft);}
324
+ .fdy-badge--tone-2{background:var(--tone-2-bg);color:var(--tone-2-ink);border-color:var(--tone-2-border-soft);}
325
+ .fdy-badge--tone-3{background:var(--tone-3-bg);color:var(--tone-3-ink);border-color:var(--tone-3-border-soft);}
326
+ .fdy-badge--tone-4{background:var(--tone-4-bg);color:var(--tone-4-ink);border-color:var(--tone-4-border-soft);}
327
+ .fdy-badge--tone-5{background:var(--tone-5-bg);color:var(--tone-5-ink);border-color:var(--tone-5-border-soft);}
328
+ .fdy-badge--tone-6{background:var(--tone-6-bg);color:var(--tone-6-ink);border-color:var(--tone-6-border-soft);}
329
+ .fdy-badge--tone-7{background:var(--tone-7-bg);color:var(--tone-7-ink);border-color:var(--tone-7-border-soft);}
330
+ .fdy-badge--tone-8{background:var(--tone-8-bg);color:var(--tone-8-ink);border-color:var(--tone-8-border-soft);}
319
331
 
320
332
  /* Overlay badge, anchors a small count/dot/icon badge to a corner of any element
321
333
  * (icon, button, text). Wrap the content in .fdy-badge-wrap; add .fdy-badge-ov as a
@@ -406,7 +418,7 @@ a { color: var(--color-primary); }
406
418
  .fdy-breadcrumb__item{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--color-text-muted);}
407
419
  .fdy-breadcrumb__link{color:var(--color-text-muted);text-decoration:none;border-radius:var(--radius-sm);}
408
420
  .fdy-breadcrumb__link:hover{color:var(--color-primary-strong);text-decoration:underline;}
409
- .fdy-breadcrumb__link:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
421
+ .fdy-breadcrumb__link:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
410
422
  .fdy-breadcrumb__item[aria-current="page"]{color:var(--color-text);font-weight:var(--weight-medium);}
411
423
  .fdy-breadcrumb__sep{color:var(--color-text-subtle);user-select:none;}
412
424
 
@@ -476,16 +488,24 @@ a { color: var(--color-primary); }
476
488
  }
477
489
 
478
490
  /* Freeday. Button */
491
+ /* The fill is a FLAT colour with a black veil laid over it, not a gradient between two shades.
492
+ * It used to be `linear-gradient(primary, color-mix(in srgb, #000 10%, primary))`, and that is the
493
+ * same colour at every stop — veiling a solid base with black 0 -> .1 darkens it by exactly 10% —
494
+ * but the mix form carries var(), so on Safari <= 16.1 the whole declaration failed at
495
+ * computed-value time and the button lost its fill rather than falling back. Measured identical to
496
+ * the mix it replaces (within the 1/255 the gradient's own 8-bit rounding costs); see
497
+ * test/color-mix.test.mjs for the rule and tokens/build.mjs for why none of these may use
498
+ * color-mix(). Pressed states veil from the TOP, which is the same trick read upside down. */
479
499
  .fdy-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
480
500
  height:var(--control-h);padding:0 var(--space-4);
481
501
  font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-semibold);
482
502
  line-height:1;white-space:nowrap;cursor:pointer;text-decoration:none;color:var(--color-on-primary);
483
- background:linear-gradient(180deg,var(--color-primary),color-mix(in srgb,#000 10%,var(--color-primary)));
503
+ background:var(--color-primary) linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.1));
484
504
  border:var(--bw) solid transparent;border-radius:var(--radius-md);
485
- box-shadow:0 2px 9px -1px color-mix(in srgb,var(--color-primary) 50%,transparent),inset 0 1px 0 rgba(255,255,255,.26);
505
+ box-shadow:0 2px 9px -1px rgba(var(--color-primary-rgb),.5),inset 0 1px 0 rgba(255,255,255,.26);
486
506
  transition:background var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard),transform var(--dur-fast) var(--ease-standard);}
487
- .fdy-btn:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px color-mix(in srgb,var(--color-primary) 60%,transparent),inset 0 1px 0 rgba(255,255,255,.26);}
488
- .fdy-btn:active{transform:translateY(1px);box-shadow:0 1px 4px -1px color-mix(in srgb,var(--color-primary) 46%,transparent);}
507
+ .fdy-btn:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px rgba(var(--color-primary-rgb),.6),inset 0 1px 0 rgba(255,255,255,.26);}
508
+ .fdy-btn:active{transform:translateY(1px);box-shadow:0 1px 4px -1px rgba(var(--color-primary-rgb),.46);}
489
509
  .fdy-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none;}
490
510
  /* Pressed / toggle. `aria-pressed` is already the right attribute for a toggle button, so this adds
491
511
  no class and no new markup contract, and it is what makes .fdy-btn-group a complete segmented
@@ -493,8 +513,8 @@ a { color: var(--color-primary); }
493
513
  defines its OWN pressed look below; a single shared rule cannot work, because a gradient is a
494
514
  background-IMAGE and would blank the ghost variant's background-color. Solid: invert the gradient
495
515
  and sink the shadow inward, so a held button reads as depressed, not merely primary-coloured. */
496
- .fdy-btn[aria-pressed="true"]{background:linear-gradient(180deg,color-mix(in srgb,#000 12%,var(--color-primary)),var(--color-primary));box-shadow:inset 0 2px 6px color-mix(in srgb,#000 22%,transparent);}
497
- .fdy-btn[aria-pressed="true"]:hover{transform:none;box-shadow:inset 0 2px 6px color-mix(in srgb,#000 22%,transparent);}
516
+ .fdy-btn[aria-pressed="true"]{background:var(--color-primary) linear-gradient(180deg,rgba(0,0,0,.12),rgba(0,0,0,0));box-shadow:inset 0 2px 6px rgba(0,0,0,.22);}
517
+ .fdy-btn[aria-pressed="true"]:hover{transform:none;box-shadow:inset 0 2px 6px rgba(0,0,0,.22);}
498
518
 
499
519
  .fdy-btn--ghost{background:var(--color-surface);color:var(--color-primary-strong);border-color:var(--color-border-strong);box-shadow:var(--shadow-1);}
500
520
  .fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
@@ -506,15 +526,15 @@ a { color: var(--color-primary); }
506
526
  screen. `.fdy-menu__item--danger` has always modelled the quiet form (red text, not a red block);
507
527
  the pairing rules below bring it to the button. They are more specific than this one and come
508
528
  after it, so this rule needs no :not() guard of its own, one mechanism, not two. */
509
- .fdy-btn--danger{background:linear-gradient(180deg,var(--color-danger-btn),color-mix(in srgb,#000 12%,var(--color-danger-btn)));color:var(--color-on-danger);box-shadow:0 2px 9px -1px color-mix(in srgb,var(--color-danger-btn) 46%,transparent),inset 0 1px 0 rgba(255,255,255,.22);}
510
- .fdy-btn--danger:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px color-mix(in srgb,var(--color-danger-btn) 54%,transparent),inset 0 1px 0 rgba(255,255,255,.22);}
529
+ .fdy-btn--danger{background:var(--color-danger-btn) linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.12));color:var(--color-on-danger);box-shadow:0 2px 9px -1px rgba(var(--color-danger-btn-rgb),.46),inset 0 1px 0 rgba(255,255,255,.22);}
530
+ .fdy-btn--danger:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px rgba(var(--color-danger-btn-rgb),.54),inset 0 1px 0 rgba(255,255,255,.22);}
511
531
  .fdy-btn--danger:active{transform:translateY(1px);}
512
532
  /* Quiet destructive: the variant keeps its own ground and only the ink turns. */
513
- .fdy-btn--ghost.fdy-btn--danger{background:transparent;color:var(--color-danger-strong);border-color:color-mix(in srgb,var(--color-danger) 40%,transparent);box-shadow:none;}
533
+ .fdy-btn--ghost.fdy-btn--danger{background:transparent;color:var(--color-danger-strong);border-color:rgba(var(--color-danger-rgb),.4);box-shadow:none;}
514
534
  .fdy-btn--ghost.fdy-btn--danger:hover{background:var(--color-danger-soft);border-color:var(--color-danger);color:var(--color-danger-strong);}
515
535
  .fdy-btn--text.fdy-btn--danger{background:none;color:var(--color-danger-strong);border-color:transparent;box-shadow:none;}
516
536
  .fdy-btn--text.fdy-btn--danger:hover{background:var(--color-danger-soft);color:var(--color-danger-strong);}
517
- .fdy-btn--danger[aria-pressed="true"]{background:linear-gradient(180deg,color-mix(in srgb,#000 14%,var(--color-danger-btn)),var(--color-danger-btn));box-shadow:inset 0 2px 6px color-mix(in srgb,#000 24%,transparent);}
537
+ .fdy-btn--danger[aria-pressed="true"]{background:var(--color-danger-btn) linear-gradient(180deg,rgba(0,0,0,.14),rgba(0,0,0,0));box-shadow:inset 0 2px 6px rgba(0,0,0,.24);}
518
538
 
519
539
  .fdy-btn--sm{height:calc(var(--control-h) - var(--space-2));padding:0 var(--space-3);font-size:var(--text-xs);border-radius:var(--radius-sm);}
520
540
  .fdy-btn--lg{height:calc(var(--control-h) + var(--space-2));padding:0 var(--space-6);font-size:var(--text-base);border-radius:var(--radius-lg);}
@@ -563,7 +583,7 @@ a { color: var(--color-primary); }
563
583
  .fdy-btn-group .fdy-btn:last-child{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md);}
564
584
 
565
585
  /* FAB, circular elevated action button (icon), optional extended pill with label */
566
- .fdy-fab{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);width:3.5rem;height:3.5rem;padding:0;border:var(--bw) solid transparent;border-radius:var(--radius-full);cursor:pointer;color:var(--color-on-primary);background:linear-gradient(180deg,var(--color-primary),color-mix(in srgb,#000 10%,var(--color-primary)));box-shadow:var(--shadow-3);transition:transform var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
586
+ .fdy-fab{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);width:3.5rem;height:3.5rem;padding:0;border:var(--bw) solid transparent;border-radius:var(--radius-full);cursor:pointer;color:var(--color-on-primary);background:var(--color-primary) linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.1));box-shadow:var(--shadow-3);transition:transform var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
567
587
  .fdy-fab:hover{transform:translateY(-1px);box-shadow:var(--shadow-4);}
568
588
  .fdy-fab:active{transform:translateY(1px);}
569
589
  .fdy-fab:focus-visible{border-radius:var(--radius-full);}
@@ -572,8 +592,8 @@ a { color: var(--color-primary); }
572
592
  .fdy-fab--sm{width:2.5rem;height:2.5rem;}
573
593
  .fdy-fab--sm svg{width:1.25rem;height:1.25rem;}
574
594
  .fdy-fab--extended{width:auto;padding:0 var(--space-5);font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-semibold);}
575
- .fdy-fab--danger{background:linear-gradient(180deg,var(--color-danger-btn),color-mix(in srgb,#000 12%,var(--color-danger-btn)));color:var(--color-on-danger);}
576
- .fdy-fab--accent{background:linear-gradient(180deg,var(--color-accent),color-mix(in srgb,#000 10%,var(--color-accent)));color:var(--color-on-accent);}
595
+ .fdy-fab--danger{background:var(--color-danger-btn) linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.12));color:var(--color-on-danger);}
596
+ .fdy-fab--accent{background:var(--color-accent) linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.1));color:var(--color-on-accent);}
577
597
 
578
598
  /* Freeday. Card */
579
599
  /* position:relative, containing block for out-of-flow card content (see base.css); it also gives
@@ -609,6 +629,13 @@ a { color: var(--color-primary); }
609
629
  absolutely positioned dismiss button back into the flow. Do not merge these two rules. */
610
630
  :where(:is(.fdy-card,.fdy-list__row):has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch)){position:relative;}
611
631
  .fdy-card__body{padding:var(--space-5);}
632
+ /* Header band (#070): an identity mark, the title, and the card's way out pinned right. Beside the
633
+ title the action sits in one place on every card; under the content it moved each time the
634
+ content grew. Outside __body's padding, so its rule spans the card edge to edge. The mark is an
635
+ .fdy-avatar (--sm --square --tone-N), so its tints are the ones the contrast gate already covers. */
636
+ .fdy-card__head{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
637
+ .fdy-card__head .fdy-card__title{flex:1 1 auto;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
638
+ .fdy-card__action{flex:none;display:inline-flex;align-items:center;gap:var(--space-2);}
612
639
  .fdy-card__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0 0 var(--space-1);}
613
640
  .fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
614
641
  .fdy-card__footer{padding:var(--space-3) var(--space-5);border-top:var(--bw) solid var(--color-border-muted);background:linear-gradient(180deg,var(--color-surface),var(--color-surface-2));display:flex;gap:var(--space-2);justify-content:flex-end;}
@@ -621,9 +648,9 @@ a { color: var(--color-primary); }
621
648
  .fdy-carousel__viewport{position:relative;display:flex;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;border-radius:var(--radius-lg);scrollbar-width:none;-ms-overflow-style:none;}
622
649
  .fdy-carousel__viewport::-webkit-scrollbar{display:none;}
623
650
  .fdy-carousel__slide{flex:0 0 100%;min-width:0;scroll-snap-align:center;}
624
- .fdy-carousel__arrow{position:absolute;top:50%;transform:translateY(-50%);z-index:2;display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border:0;border-radius:var(--radius-full);background:color-mix(in srgb,var(--color-surface) 86%,transparent);color:var(--color-text);box-shadow:var(--shadow-2);cursor:pointer;transition:background var(--dur-fast) var(--ease-standard);}
651
+ .fdy-carousel__arrow{position:absolute;top:50%;transform:translateY(-50%);z-index:2;display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border:0;border-radius:var(--radius-full);background:rgba(var(--color-surface-rgb),.86);color:var(--color-text);box-shadow:var(--shadow-2);cursor:pointer;transition:background var(--dur-fast) var(--ease-standard);}
625
652
  .fdy-carousel__arrow:hover{background:var(--color-surface);}
626
- .fdy-carousel__arrow:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 40%,transparent);}
653
+ .fdy-carousel__arrow:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.4);}
627
654
  .fdy-carousel__arrow:disabled{opacity:.4;cursor:not-allowed;}
628
655
  .fdy-carousel__arrow--prev{left:var(--space-3);}
629
656
  .fdy-carousel__arrow--next{right:var(--space-3);}
@@ -631,7 +658,7 @@ a { color: var(--color-primary); }
631
658
  .fdy-carousel__dots{display:flex;justify-content:center;gap:var(--space-2);margin-top:var(--space-3);}
632
659
  .fdy-carousel__dot{width:.5rem;height:.5rem;padding:0;border:0;border-radius:var(--radius-full);background:var(--color-border-strong);cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),width var(--dur-fast) var(--ease-standard);}
633
660
  .fdy-carousel__dot[aria-current="true"]{background:var(--color-primary);width:1.25rem;}
634
- .fdy-carousel__dot:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 40%,transparent);}
661
+ .fdy-carousel__dot:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.4);}
635
662
  @media (prefers-reduced-motion:reduce){.fdy-carousel__viewport{scroll-behavior:auto;}}
636
663
 
637
664
  /* Freeday. Cascade select (hierarchical drill-down picker).
@@ -643,14 +670,14 @@ a { color: var(--color-primary); }
643
670
  .fdy-cascade{position:relative;display:inline-block;width:100%;max-width:22rem;}
644
671
  .fdy-cascade__trigger{display:inline-flex;align-items:center;justify-content:space-between;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
645
672
  .fdy-cascade__trigger:hover{border-color:var(--color-text-muted);}
646
- .fdy-cascade__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
647
- .fdy-cascade__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
673
+ .fdy-cascade__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
674
+ .fdy-cascade__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
648
675
  .fdy-cascade__trigger:disabled,.fdy-cascade__trigger[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
649
676
  .fdy-cascade__trigger[aria-readonly="true"]{background:var(--color-surface-2);border-color:var(--color-border);cursor:default;}
650
677
  .fdy-cascade__trigger[aria-readonly="true"]:hover{border-color:var(--color-border);}
651
678
  .fdy-cascade__trigger[aria-readonly="true"]:focus-visible{border-color:var(--color-border-strong);box-shadow:none;}
652
679
  .fdy-cascade--error .fdy-cascade__trigger,.fdy-cascade__trigger[aria-invalid="true"]{border-color:var(--color-danger);}
653
- .fdy-cascade--error .fdy-cascade__trigger:focus-visible,.fdy-cascade--error .fdy-cascade__trigger.is-open,.fdy-cascade__trigger[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-danger) 26%,transparent);}
680
+ .fdy-cascade--error .fdy-cascade__trigger:focus-visible,.fdy-cascade--error .fdy-cascade__trigger.is-open,.fdy-cascade__trigger[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb),.26);}
654
681
  .fdy-cascade__value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
655
682
  .fdy-cascade__value--placeholder{color:var(--color-text-subtle);}
656
683
  .fdy-cascade__trigger::after{content:"";flex:none;width:.5rem;height:.5rem;margin-top:-.15rem;border-right:2px solid var(--color-text-subtle);border-bottom:2px solid var(--color-text-subtle);transform:rotate(45deg);transition:transform var(--dur-fast) var(--ease-standard);}
@@ -662,7 +689,7 @@ a { color: var(--color-primary); }
662
689
  .fdy-cascade__head{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-1) var(--space-1) var(--space-2);margin-bottom:var(--space-1);border-bottom:var(--bw) solid var(--color-border);}
663
690
  .fdy-cascade__back{display:inline-flex;align-items:center;justify-content:center;flex:none;width:1.5rem;height:1.5rem;padding:0;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-muted);cursor:pointer;}
664
691
  .fdy-cascade__back:hover{background:var(--color-surface-3);color:var(--color-text);}
665
- .fdy-cascade__back:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
692
+ .fdy-cascade__back:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
666
693
  .fdy-cascade__back[hidden]{display:none;}
667
694
  .fdy-cascade__back svg{display:block;width:1rem;height:1rem;}
668
695
  .fdy-cascade__crumb{min-width:0;flex:1;font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
@@ -825,30 +852,48 @@ a { color: var(--color-primary); }
825
852
 
826
853
  /* Freeday. Chip (tag / removable filter) */
827
854
  .fdy-chip{display:inline-flex;align-items:center;gap:var(--space-2);height:1.75rem;padding:0 var(--space-3);border-radius:var(--radius-full);font-size:var(--text-xs);font-weight:var(--weight-medium);color:var(--color-text);background:var(--color-surface-3);border:var(--bw) solid var(--color-border);}
828
- .fdy-chip--primary{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:color-mix(in srgb,var(--color-primary) 22%,transparent);}
855
+ .fdy-chip--primary{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:rgba(var(--color-primary-rgb),.22);}
829
856
  /* Categorical tone chips, non-semantic category tags / legend swatches, from the general
830
857
  * --tone-1..8 palette. Same validated AA formula as .fdy-avatar--tone-* (text >=4.5:1 both themes,
831
858
  * gated by test/contrast.test.mjs). */
832
- .fdy-chip--tone-1,.fdy-chip--tone-2,.fdy-chip--tone-3,.fdy-chip--tone-4,.fdy-chip--tone-5,.fdy-chip--tone-6,.fdy-chip--tone-7,.fdy-chip--tone-8{background:color-mix(in srgb,var(--_fdy-chip-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-chip-tone) 50%,var(--color-text));border-color:color-mix(in srgb,var(--_fdy-chip-tone) 30%,var(--color-border));}
833
- .fdy-chip--tone-1{--_fdy-chip-tone:var(--tone-1);}
834
- .fdy-chip--tone-2{--_fdy-chip-tone:var(--tone-2);}
835
- .fdy-chip--tone-3{--_fdy-chip-tone:var(--tone-3);}
836
- .fdy-chip--tone-4{--_fdy-chip-tone:var(--tone-4);}
837
- .fdy-chip--tone-5{--_fdy-chip-tone:var(--tone-5);}
838
- .fdy-chip--tone-6{--_fdy-chip-tone:var(--tone-6);}
839
- .fdy-chip--tone-7{--_fdy-chip-tone:var(--tone-7);}
840
- .fdy-chip--tone-8{--_fdy-chip-tone:var(--tone-8);}
841
- .fdy-chip__remove{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-right:calc(var(--space-1)*-1);border:0;padding:0;border-radius:var(--radius-full);background:transparent;color:currentColor;opacity:.6;cursor:pointer;font-size:var(--text-sm);line-height:1;}
842
- .fdy-chip__remove:hover{opacity:1;background:color-mix(in srgb,currentColor 16%,transparent);}
843
- .fdy-chip__remove:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
859
+ /* Precomputed tone tokens, the same --tone-N-bg / --tone-N-ink the avatar and the badge read.
860
+ * --tone-N-border is the chip's edge: 30% of the tone over --color-border, opaque, which is a
861
+ * different edge from the badge's translucent one and so a different token. See avatar.css for
862
+ * why the mix left the component, and tokens/build.mjs for where it went. */
863
+ .fdy-chip--tone-1{background:var(--tone-1-bg);color:var(--tone-1-ink);border-color:var(--tone-1-border);}
864
+ .fdy-chip--tone-2{background:var(--tone-2-bg);color:var(--tone-2-ink);border-color:var(--tone-2-border);}
865
+ .fdy-chip--tone-3{background:var(--tone-3-bg);color:var(--tone-3-ink);border-color:var(--tone-3-border);}
866
+ .fdy-chip--tone-4{background:var(--tone-4-bg);color:var(--tone-4-ink);border-color:var(--tone-4-border);}
867
+ .fdy-chip--tone-5{background:var(--tone-5-bg);color:var(--tone-5-ink);border-color:var(--tone-5-border);}
868
+ .fdy-chip--tone-6{background:var(--tone-6-bg);color:var(--tone-6-ink);border-color:var(--tone-6-border);}
869
+ .fdy-chip--tone-7{background:var(--tone-7-bg);color:var(--tone-7-ink);border-color:var(--tone-7-border);}
870
+ .fdy-chip--tone-8{background:var(--tone-8-bg);color:var(--tone-8-ink);border-color:var(--tone-8-border);}
871
+ .fdy-chip__remove{position:relative;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-right:calc(var(--space-1)*-1);border:0;padding:0;border-radius:var(--radius-full);background:transparent;color:currentColor;opacity:.6;cursor:pointer;font-size:var(--text-sm);line-height:1;}
872
+ /* The hover wash is 16% of the chip's OWN ink, and the chip's ink is whatever the variant set:
873
+ * --color-text, --color-primary-strong, --tone-N-ink. So no token can stand in for it, which is why
874
+ * this was the last color-mix() in the kit — and the only one whose two-line fallback actually
875
+ * worked, the mix carrying no var() and therefore being rejected at parse time.
876
+ *
877
+ * It is a VEIL now, the same trick the button gradients use (button.css): currentColor at .16 over
878
+ * the chip, which is algebraically the colour the mix produced — `F·.16 + (1-.16)·ground` either
879
+ * way, including at the glyph's antialiased edges, where the veil's colour equals the glyph's and so
880
+ * composites back to itself. No @supports, no fallback line, and nothing newer than Safari 4.
881
+ *
882
+ * Absolutely positioned, so it paints above the × as well as the background; `border-radius:inherit`
883
+ * keeps it inside the pill. No transition: the mix had none, and a veil that fades is a different
884
+ * component. */
885
+ .fdy-chip__remove::before{content:"";position:absolute;inset:0;border-radius:inherit;background:currentColor;opacity:0;}
886
+ .fdy-chip__remove:hover{opacity:1;}
887
+ .fdy-chip__remove:hover::before{opacity:.16;}
888
+ .fdy-chip__remove:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
844
889
  /* Group wrapper for a row of chips (choice / filter). */
845
890
  .fdy-chips{display:flex;flex-wrap:wrap;gap:var(--space-2);}
846
891
  /* Interactive chips: choice (toggle) + filter (toggle with a check when on).
847
892
  * Rendered as <button aria-pressed> and enhanced by freeday-chip.js. */
848
893
  .fdy-chip--choice,.fdy-chip--filter{cursor:pointer;font-family:inherit;background:var(--color-surface);border-color:var(--color-border);transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
849
894
  .fdy-chip--choice:hover,.fdy-chip--filter:hover{background:var(--color-surface-3);border-color:var(--color-text-subtle);}
850
- .fdy-chip--choice:focus-visible,.fdy-chip--filter:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
851
- .fdy-chip--choice[aria-pressed="true"],.fdy-chip--filter[aria-pressed="true"]{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:color-mix(in srgb,var(--color-primary) 40%,transparent);}
895
+ .fdy-chip--choice:focus-visible,.fdy-chip--filter:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
896
+ .fdy-chip--choice[aria-pressed="true"],.fdy-chip--filter[aria-pressed="true"]{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:rgba(var(--color-primary-rgb),.4);}
852
897
  .fdy-chip--choice:disabled,.fdy-chip--filter:disabled,.fdy-chip--choice[aria-disabled="true"],.fdy-chip--filter[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
853
898
  .fdy-chip__check{display:none;flex:none;width:.85rem;height:.85rem;margin-left:calc(var(--space-1)*-1);}
854
899
  .fdy-chip__check svg{display:block;width:100%;height:100%;}
@@ -858,8 +903,8 @@ a { color: var(--color-primary); }
858
903
  .fdy-combo{position:relative;display:block;max-width:22rem;}
859
904
  .fdy-combo__button{display:inline-flex;align-items:center;justify-content:space-between;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
860
905
  .fdy-combo__button:hover{border-color:var(--color-text-muted);}
861
- .fdy-combo__button:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
862
- .fdy-combo__button.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
906
+ .fdy-combo__button:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
907
+ .fdy-combo__button.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
863
908
  .fdy-combo__button:disabled,.fdy-combo__button[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
864
909
  /* Read-only: stays focusable & shows its value at full contrast but can't open, muted surface,
865
910
  softened border, default cursor, no focus-ring escalation (matches input[readonly], #39). */
@@ -867,7 +912,7 @@ a { color: var(--color-primary); }
867
912
  .fdy-combo__button[aria-readonly="true"]:hover{border-color:var(--color-border);}
868
913
  .fdy-combo__button[aria-readonly="true"]:focus-visible{border-color:var(--color-border-strong);box-shadow:none;}
869
914
  .fdy-combo--error .fdy-combo__button,.fdy-combo__button[aria-invalid="true"]{border-color:var(--color-danger);}
870
- .fdy-combo--error .fdy-combo__button:focus-visible,.fdy-combo--error .fdy-combo__button.is-open,.fdy-combo__button[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-danger) 26%,transparent);}
915
+ .fdy-combo--error .fdy-combo__button:focus-visible,.fdy-combo--error .fdy-combo__button.is-open,.fdy-combo__button[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb),.26);}
871
916
  .fdy-combo__value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
872
917
  .fdy-combo__value--placeholder{color:var(--color-text-subtle);}
873
918
  .fdy-combo__button::after{content:"";flex:none;width:.5rem;height:.5rem;margin-top:-.15rem;border-right:2px solid var(--color-text-subtle);border-bottom:2px solid var(--color-text-subtle);transform:rotate(45deg);transition:transform var(--dur-fast) var(--ease-standard);}
@@ -882,14 +927,14 @@ a { color: var(--color-primary); }
882
927
  .fdy-combo__option{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;white-space:nowrap;}
883
928
  .fdy-combo__option.is-highlighted{background:var(--color-surface-3);}
884
929
  .fdy-combo__option[aria-selected="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
885
- .fdy-combo__option.is-highlighted[aria-selected="true"]{box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 55%,transparent);}
930
+ .fdy-combo__option.is-highlighted[aria-selected="true"]{box-shadow:inset 0 0 0 2px rgba(var(--color-primary-rgb),.55);}
886
931
  /* The tick is CSS, not content (#004 §2). Inside role="option" it joined the option's ACCESSIBLE
887
932
  NAME, so the selected option announced "✓August" while every other announced "August": the state
888
933
  was read twice, once as aria-selected, once as decoration, and a test asking for an option by
889
934
  its own label stopped matching the one that was selected. The `/ ""` is alt text: the glyph is
890
935
  painted, the accessibility tree gets nothing. Same reasoning as .fdy-label--required (1.29.0).
891
936
  The empty box still reserves its width, so labels stay aligned whether selected or not. */
892
- .fdy-combo__check{display:inline-flex;justify-content:center;width:var(--space-4);flex:none;font-weight:var(--weight-bold);color:var(--color-primary);}
937
+ .fdy-combo__check{display:inline-flex;justify-content:center;width:var(--space-4);flex:none;font-weight:var(--weight-bold);color:var(--color-primary-strong);}
893
938
  .fdy-combo__option[aria-selected="true"] .fdy-combo__check::after{content:"✓" / "";}
894
939
  @media (prefers-reduced-motion:no-preference){.fdy-combo__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
895
940
  @keyframes fdy-combo-in{from{opacity:0;transform:translateY(-4px);}to{opacity:1;transform:none;}}
@@ -991,6 +1036,42 @@ a { color: var(--color-primary); }
991
1036
  .fdy-text-success{color:var(--color-success-strong);}
992
1037
  .fdy-text-warning{color:var(--color-warning-strong);}
993
1038
  .fdy-text-danger{color:var(--color-danger-strong);}
1039
+ /* Document columns (#065): a form or record page with an optional identity rail on the left
1040
+ * (__lead) and a summary rail on the right (__aside). The widths follow which rails are PRESENT,
1041
+ * so the layout is decided once here and not again by every screen:
1042
+ * __main alone one column
1043
+ * __main + __aside 8 / 4 (from md)
1044
+ * __lead + __main 3 / 9 (from md)
1045
+ * all three 3 / 6 / 3 (from lg; between md and lg both rails share the left)
1046
+ * Stacked, the summary rail comes FIRST: last, it sits under every section, and its button with it.
1047
+ * From md each rail is sticky and scrolls on its own, so the total and the status stay answerable
1048
+ * while the main column scrolls with the page, and a rail taller than the screen never hangs its
1049
+ * end off it. Screen only: a printed page is not held to a window. */
1050
+ .fdy-page__columns{--_fdy-rail-top:var(--fdy-page-sticky-top,var(--space-4));display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-6);align-items:start;}
1051
+ /* Under the shell the sticky topbar is in the way; --fdy-page-sticky-top overrides either default. */
1052
+ .fdy-app .fdy-page__columns{--_fdy-rail-top:var(--fdy-page-sticky-top,calc(var(--space-16) + var(--space-4)));}
1053
+ .fdy-page__columns > *{min-width:0;}
1054
+ @media (max-width:959.98px){.fdy-page__aside{order:-1;}}
1055
+ @media (min-width:960px){
1056
+ .fdy-page__columns:has(> .fdy-page__aside){grid-template-columns:minmax(0,2fr) minmax(0,1fr);}
1057
+ .fdy-page__columns:has(> .fdy-page__lead){grid-template-columns:minmax(0,1fr) minmax(0,3fr);}
1058
+ /* Both rails, two columns: they stack on the left while __main spans both rows on the right. */
1059
+ .fdy-page__columns:has(> .fdy-page__lead):has(> .fdy-page__aside) > .fdy-page__main{grid-row:span 2;}
1060
+ }
1061
+ @media (min-width:1280px){
1062
+ .fdy-page__columns:has(> .fdy-page__lead):has(> .fdy-page__aside){grid-template-columns:minmax(0,1fr) minmax(0,2fr) minmax(0,1fr);}
1063
+ .fdy-page__columns:has(> .fdy-page__lead):has(> .fdy-page__aside) > .fdy-page__main{grid-row:auto;}
1064
+ }
1065
+ /* The padding, taken back by the margin, is room for a focus ring at a card's edge: a scrolling box
1066
+ clips whatever draws outside it. */
1067
+ @media screen and (min-width:960px){
1068
+ .fdy-page__lead,.fdy-page__aside{position:sticky;top:var(--_fdy-rail-top);max-height:calc(100dvh - var(--_fdy-rail-top) - var(--space-4));overflow-y:auto;overscroll-behavior:contain;padding:var(--space-1);margin:calc(var(--space-1) * -1);}
1069
+ }
1070
+ /* Two sticky rails in ONE column would slide over each other: between md and lg, with both rails
1071
+ present, they scroll with the page instead. */
1072
+ @media screen and (min-width:960px) and (max-width:1279.98px){
1073
+ .fdy-page__columns:has(> .fdy-page__lead):has(> .fdy-page__aside) > :is(.fdy-page__lead,.fdy-page__aside){position:static;max-height:none;overflow:visible;}
1074
+ }
994
1075
 
995
1076
  /* Freeday. Date picker (input-styled trigger + calendar popover).
996
1077
  * Built by freeday-datepicker.js from an empty [data-fdy-datepicker] wrapper. */
@@ -1003,10 +1084,10 @@ a { color: var(--color-primary); }
1003
1084
  .fdy-field>.fdy-datepicker,.fdy-field>.fdy-timepicker,.fdy-field>.fdy-daterange{max-width:none;}
1004
1085
  .fdy-datepicker__trigger{display:inline-flex;align-items:center;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
1005
1086
  .fdy-datepicker__trigger:hover{border-color:var(--color-text-muted);}
1006
- .fdy-datepicker__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1007
- .fdy-datepicker__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1087
+ .fdy-datepicker__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1088
+ .fdy-datepicker__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1008
1089
  .fdy-datepicker--error .fdy-datepicker__trigger,.fdy-datepicker__trigger[aria-invalid="true"]{border-color:var(--color-danger);}
1009
- .fdy-datepicker--error .fdy-datepicker__trigger:focus-visible,.fdy-datepicker--error .fdy-datepicker__trigger.is-open,.fdy-datepicker__trigger[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-danger) 26%,transparent);}
1090
+ .fdy-datepicker--error .fdy-datepicker__trigger:focus-visible,.fdy-datepicker--error .fdy-datepicker__trigger.is-open,.fdy-datepicker__trigger[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb),.26);}
1010
1091
  .fdy-datepicker__trigger:disabled,.fdy-datepicker__trigger[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
1011
1092
  .fdy-datepicker__trigger[aria-readonly="true"]{background:var(--color-surface-2);border-color:var(--color-border);cursor:default;}
1012
1093
  .fdy-datepicker__trigger[aria-readonly="true"]:hover{border-color:var(--color-border);}
@@ -1021,7 +1102,7 @@ a { color: var(--color-primary); }
1021
1102
  .fdy-datepicker__icon.is-hidden{visibility:hidden;}
1022
1103
  .fdy-datepicker__clear{position:absolute;top:50%;right:var(--space-3);transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:1.15rem;height:1.15rem;padding:0;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-subtle);cursor:pointer;transition:color var(--dur-fast) var(--ease-standard),background var(--dur-fast) var(--ease-standard);}
1023
1104
  .fdy-datepicker__clear:hover{color:var(--color-text);background:var(--color-surface-2);}
1024
- .fdy-datepicker__clear:focus-visible{outline:none;color:var(--color-text);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1105
+ .fdy-datepicker__clear:focus-visible{outline:none;color:var(--color-text);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1025
1106
  .fdy-datepicker__clear svg{display:block;width:.9rem;height:.9rem;}
1026
1107
 
1027
1108
  .fdy-datepicker__panel{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;padding:var(--space-3);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);}
@@ -1033,16 +1114,16 @@ a { color: var(--color-primary); }
1033
1114
  reset and the same hover/focus treatment as the arrows beside it. */
1034
1115
  .fdy-cal__title{flex:1;appearance:none;border:0;background:transparent;margin:0;padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);text-align:center;font-family:inherit;font-weight:var(--weight-semibold);font-size:var(--text-sm);color:var(--color-text);text-transform:capitalize;cursor:pointer;}
1035
1116
  .fdy-cal__title:hover{background:var(--color-surface-2);}
1036
- .fdy-cal__title:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1117
+ .fdy-cal__title:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1037
1118
  .fdy-cal__month,.fdy-cal__year{display:inline-flex;align-items:center;justify-content:center;height:2.25rem;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text);font-family:inherit;font-size:var(--text-sm);text-transform:capitalize;cursor:pointer;}
1038
1119
  .fdy-cal__month:hover,.fdy-cal__year:hover{background:var(--color-surface-2);}
1039
- .fdy-cal__month:focus-visible,.fdy-cal__year:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1120
+ .fdy-cal__month:focus-visible,.fdy-cal__year:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1040
1121
  .fdy-cal__month:disabled,.fdy-cal__year:disabled{opacity:.4;cursor:not-allowed;background:transparent;}
1041
1122
  .fdy-cal__month.is-today,.fdy-cal__year.is-today{font-weight:var(--weight-bold);box-shadow:inset 0 0 0 1.5px var(--color-border-strong);}
1042
1123
  .fdy-cal__month.is-selected,.fdy-cal__year.is-selected{background:var(--color-primary);color:var(--color-on-primary);font-weight:var(--weight-semibold);}
1043
1124
  .fdy-cal__nav{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-muted);font-size:var(--text-lg);line-height:1;cursor:pointer;}
1044
1125
  .fdy-cal__nav:hover{background:var(--color-surface-2);color:var(--color-text);}
1045
- .fdy-cal__nav:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1126
+ .fdy-cal__nav:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1046
1127
  .fdy-cal__grid{display:grid;grid-template-columns:repeat(7,2rem);gap:2px;}
1047
1128
  /* Month grid. 3×4 over the same track width as the 7-column day grid, so the panel does not resize
1048
1129
  when you drill and the pointer does not have to travel.
@@ -1054,7 +1135,7 @@ a { color: var(--color-primary); }
1054
1135
  .fdy-cal__dow{display:flex;align-items:center;justify-content:center;height:1.75rem;font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--color-text-muted);text-transform:capitalize;}
1055
1136
  .fdy-cal__day{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text);font-size:var(--text-sm);font-variant-numeric:tabular-nums;cursor:pointer;}
1056
1137
  .fdy-cal__day:hover{background:var(--color-surface-2);}
1057
- .fdy-cal__day:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1138
+ .fdy-cal__day:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1058
1139
  .fdy-cal__day.is-outside{color:var(--color-text-subtle);opacity:.55;}
1059
1140
  .fdy-cal__day.is-today{font-weight:var(--weight-bold);box-shadow:inset 0 0 0 1.5px var(--color-border-strong);}
1060
1141
  .fdy-cal__day.in-range{background:var(--color-primary-soft);color:var(--color-primary-strong);border-radius:0;}
@@ -1086,9 +1167,19 @@ a { color: var(--color-primary); }
1086
1167
  /* Freeday. Description list (key-value detail view) */
1087
1168
  .fdy-dl{display:grid;grid-template-columns:auto 1fr;gap:var(--space-2) var(--space-5);margin:0;}
1088
1169
  .fdy-dl dt{color:var(--color-text-muted);font-size:var(--text-sm);}
1089
- .fdy-dl dd{margin:0;color:var(--color-text);font-size:var(--text-sm);font-weight:var(--weight-medium);}
1170
+ /* overflow-wrap: an email, an order code or a 13-digit phone number is one unbreakable token, and
1171
+ without it the value prints over whatever sits beside it (#066). */
1172
+ .fdy-dl dd{margin:0;min-width:0;color:var(--color-text);font-size:var(--text-sm);font-weight:var(--weight-medium);overflow-wrap:anywhere;}
1090
1173
  .fdy-dl--rows{grid-template-columns:1fr;gap:var(--space-1);}
1091
1174
  .fdy-dl--rows dt{margin-top:var(--space-2);}
1175
+ /* Facts grid (#066): each pair a <div> (allowed inside <dl>), caption above value, as many columns
1176
+ as the list's OWN width holds, never more than three. Sized by the element rather than the
1177
+ window, so a grid in a narrow side rail stays one column with no modifier: a window breakpoint
1178
+ gave a rail two columns of about 110px. min(100%, …) keeps a single column from overflowing a
1179
+ rail narrower than the column floor. */
1180
+ .fdy-dl--grid{grid-template-columns:repeat(auto-fill,minmax(min(100%,max(var(--fdy-dl-col-min,14rem),calc((100% - 2 * var(--space-6)) / 3))),1fr));gap:var(--space-5) var(--space-6);}
1181
+ .fdy-dl--grid > div{min-width:0;}
1182
+ .fdy-dl--grid dd{margin-top:var(--space-1);}
1092
1183
 
1093
1184
  /* Freeday. Divider */
1094
1185
  .fdy-divider{border:0;border-top:var(--bw) solid var(--color-border);margin:var(--space-4) 0;}
@@ -1125,11 +1216,11 @@ a { color: var(--color-primary); }
1125
1216
  * Enhanced by freeday-upload.js; states: idle · drag-over · uploading · done · error. */
1126
1217
  .fdy-dropzone{display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:var(--space-6);text-align:center;border:2px dashed var(--color-control-border);border-radius:var(--radius-lg);background:var(--color-surface-2);color:var(--color-text-muted);cursor:pointer;transition:border-color var(--dur-fast) var(--ease-standard),background var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
1127
1218
  .fdy-dropzone:hover{border-color:var(--color-text-muted);}
1128
- .fdy-dropzone:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1219
+ .fdy-dropzone:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1129
1220
  .fdy-dropzone.is-dragover{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary-strong);}
1130
1221
  .fdy-dropzone[aria-disabled="true"],.fdy-dropzone.is-disabled{opacity:.55;cursor:not-allowed;pointer-events:none;}
1131
1222
  .fdy-dropzone__icon{color:var(--color-text-subtle);}
1132
- .fdy-dropzone.is-dragover .fdy-dropzone__icon{color:var(--color-primary);}
1223
+ .fdy-dropzone.is-dragover .fdy-dropzone__icon{color:var(--color-primary-strong);}
1133
1224
  .fdy-dropzone__icon svg{display:block;width:2rem;height:2rem;}
1134
1225
  .fdy-dropzone__title{font-size:inherit;font-weight:var(--weight-medium);color:var(--color-text);margin:0;}
1135
1226
  .fdy-dropzone__hint{font-size:var(--text-xs);}
@@ -1156,9 +1247,14 @@ a { color: var(--color-primary); }
1156
1247
  .fdy-file__progress{margin-top:var(--space-2);}
1157
1248
  .fdy-file__remove{flex:none;display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-muted);font-size:var(--text-lg);line-height:1;cursor:pointer;}
1158
1249
  .fdy-file__remove:hover{background:var(--color-surface-2);color:var(--color-text);}
1159
- .fdy-file__remove:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1250
+ .fdy-file__remove:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1160
1251
  .fdy-file--success .fdy-file__icon{color:var(--color-success);}
1161
- .fdy-file--error{border-color:color-mix(in srgb,var(--color-danger) 40%,var(--color-border));}
1252
+ /* 40% of --color-danger over --color-border, as a token rather than as a mix written here: it blends
1253
+ * two ROLES, so no channel triple can state it, and mixing it in the component carried var() into
1254
+ * color-mix() — the shape that goes invalid at computed-value time and drops border-color to its
1255
+ * initial currentColor, which is the black box an iPhone 7 drew around a failed upload. The blend is
1256
+ * now computed per theme by tokens/build.mjs. */
1257
+ .fdy-file--error{border-color:var(--color-danger-border);}
1162
1258
  .fdy-file--error .fdy-file__icon{color:var(--color-danger);}
1163
1259
  .fdy-file--error .fdy-file__sub{color:var(--color-danger-strong);}
1164
1260
 
@@ -1230,7 +1326,7 @@ a { color: var(--color-primary); }
1230
1326
  /* Freeday. Input group (prefix/suffix addons, e.g. Rp / % / icon) */
1231
1327
  .fdy-input-group{display:flex;align-items:stretch;height:var(--control-h);max-width:22rem;background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);overflow:hidden;transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
1232
1328
  .fdy-input-group:hover{border-color:var(--color-text-muted);}
1233
- .fdy-input-group:focus-within{border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1329
+ .fdy-input-group:focus-within{border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1234
1330
  /* The group owns the height; the input fills whatever the border leaves. It used to subtract 3px
1235
1331
  for two 1.5px borders, which is arithmetic the engine is free to round: at dpr 1 each border
1236
1332
  resolves to 1px, so the group rendered 1px short of every control beside it (#018). Stretching
@@ -1259,7 +1355,7 @@ a { color: var(--color-primary); }
1259
1355
  .fdy-input-group__btn:disabled:hover{background:var(--color-surface-2);color:var(--color-text-muted);}
1260
1356
  /* Error state must win over the neutralizing rules above, promote to the wrapper. */
1261
1357
  .fdy-input-group:has(.fdy-input--error),.fdy-input-group:has(.fdy-combo--error),.fdy-input-group:has([aria-invalid="true"]){border-color:var(--color-danger);}
1262
- .fdy-input-group:has(.fdy-input--error):focus-within,.fdy-input-group:has(.fdy-combo--error):focus-within,.fdy-input-group:has([aria-invalid="true"]):focus-within{border-color:var(--color-danger);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-danger) 26%,transparent);}
1358
+ .fdy-input-group:has(.fdy-input--error):focus-within,.fdy-input-group:has(.fdy-combo--error):focus-within,.fdy-input-group:has([aria-invalid="true"]):focus-within{border-color:var(--color-danger);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-danger-rgb),.26);}
1263
1359
 
1264
1360
  /* Freeday. Field & Input */
1265
1361
  .fdy-field{display:flex;flex-direction:column;gap:var(--space-2);max-width:22rem;}
@@ -1295,7 +1391,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1295
1391
  .fdy-input::placeholder,.fdy-textarea::placeholder{color:var(--color-text-subtle);}
1296
1392
  .fdy-textarea{height:auto;min-height:5.5rem;padding:var(--space-3);line-height:var(--leading-normal);resize:vertical;}
1297
1393
  .fdy-input:hover,.fdy-textarea:hover{border-color:var(--color-text-muted);}
1298
- .fdy-input:focus,.fdy-textarea:focus{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1394
+ .fdy-input:focus,.fdy-textarea:focus{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1299
1395
  .fdy-input:disabled,.fdy-textarea:disabled,.fdy-input[aria-disabled="true"],.fdy-textarea[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
1300
1396
  /* Read-only (#39): the value is real and may be read/copied, only not edited. Full contrast (unlike
1301
1397
  :disabled which dims to placeholder-grey), muted surface + softened border so it still reads as
@@ -1312,7 +1408,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1312
1408
  .fdy-input[type="number"]{appearance:textfield;-webkit-appearance:textfield;}
1313
1409
  .fdy-input[type="number"]::-webkit-outer-spin-button,.fdy-input[type="number"]::-webkit-inner-spin-button{appearance:none;-webkit-appearance:none;margin:0;}
1314
1410
  .fdy-input--error,.fdy-input[aria-invalid="true"],.fdy-textarea[aria-invalid="true"]{border-color:var(--color-danger);}
1315
- .fdy-input--error:focus,.fdy-input[aria-invalid="true"]:focus,.fdy-textarea[aria-invalid="true"]:focus{border-color:var(--color-danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-danger) 26%,transparent);}
1411
+ .fdy-input--error:focus,.fdy-input[aria-invalid="true"]:focus,.fdy-textarea[aria-invalid="true"]:focus{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb),.26);}
1316
1412
  /* Required marker. The control already carries `required`, so the asterisk is decoration: the
1317
1413
  `/ ""` is CSS alt text, the glyph is painted but exposes NOTHING to the accessibility tree, so a
1318
1414
  screen reader does not read "star" after every label. That is stricter than a <span> an app has to
@@ -1378,7 +1474,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1378
1474
  /* Focus is not hover. The enhancer moves real DOM focus (freeday-menu.js `.focus()`), so this is
1379
1475
  the only thing a keyboard user has to go on, with the hover fill alone, arrowing through the
1380
1476
  menu is invisible. Same ring as .fdy-nav__item, which got this right. Reported twice. */
1381
- .fdy-menu__item:focus-visible{outline:none;background:var(--color-surface-3);box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
1477
+ .fdy-menu__item:focus-visible{outline:none;background:var(--color-surface-3);box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
1382
1478
  .fdy-menu__item:disabled{opacity:.5;cursor:not-allowed;background:transparent;}
1383
1479
  .fdy-menu__item--danger{color:var(--color-danger-strong);}
1384
1480
  .fdy-menu__item svg{display:block;width:1rem;height:1rem;flex:none;}
@@ -1395,8 +1491,8 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1395
1491
  .fdy-btn-split__toggle{padding:0 var(--space-2);margin-left:calc(var(--bw) * -1);border-top-left-radius:0;border-bottom-left-radius:0;}
1396
1492
  /* Hairline divider between the halves (outranks the segment rule above). */
1397
1493
  .fdy-btn-split > .fdy-btn-split__toggle{box-shadow:inset 0 1px 0 rgba(255,255,255,.26),inset 1px 0 0 rgba(0,0,0,.16);}
1398
- .fdy-btn-split:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px color-mix(in srgb,var(--color-primary) 55%,transparent);}
1399
- .fdy-btn-split:active{transform:translateY(1px);box-shadow:0 1px 4px -1px color-mix(in srgb,var(--color-primary) 46%,transparent);}
1494
+ .fdy-btn-split:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px rgba(var(--color-primary-rgb),.55);}
1495
+ .fdy-btn-split:active{transform:translateY(1px);box-shadow:0 1px 4px -1px rgba(var(--color-primary-rgb),.46);}
1400
1496
  .fdy-btn-split__toggle:hover,.fdy-btn-split__toggle:focus-visible{z-index:1;}
1401
1497
  .fdy-btn-split__toggle svg{display:block;width:1rem;height:1rem;}
1402
1498
 
@@ -1427,7 +1523,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1427
1523
  .fdy-pagination__list{display:flex;align-items:center;gap:var(--space-1);list-style:none;margin:0;padding:0;}
1428
1524
  .fdy-pagination__link{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;padding:0 var(--space-2);border-radius:var(--radius-md);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text);background:transparent;border:var(--bw) solid transparent;text-decoration:none;cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
1429
1525
  .fdy-pagination__link:hover{background:var(--color-surface-2);border-color:var(--color-border);}
1430
- .fdy-pagination__link:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1526
+ .fdy-pagination__link:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1431
1527
  .fdy-pagination__link[aria-current="page"]{background:var(--color-primary);color:var(--color-on-primary);}
1432
1528
  .fdy-pagination__link[aria-disabled="true"],.fdy-pagination__link:disabled{opacity:.5;cursor:not-allowed;pointer-events:none;}
1433
1529
  .fdy-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;color:var(--color-text-subtle);}
@@ -1473,13 +1569,19 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1473
1569
  .fdy-radio input::before{content:"";width:.55rem;height:.55rem;border-radius:50%;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);}
1474
1570
  .fdy-check input:checked,.fdy-radio input:checked{background:var(--color-primary);border-color:var(--color-primary);}
1475
1571
  .fdy-check input:checked::before,.fdy-radio input:checked::before{transform:scale(1);}
1476
- .fdy-check input:focus-visible,.fdy-radio input:focus-visible,.fdy-switch input:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1572
+ .fdy-check input:focus-visible,.fdy-radio input:focus-visible,.fdy-switch input:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1477
1573
  .fdy-check input:disabled,.fdy-radio input:disabled,.fdy-switch input:disabled{opacity:.5;cursor:not-allowed;}
1478
1574
  .fdy-check:has(input:disabled),.fdy-radio:has(input:disabled),.fdy-switch:has(input:disabled){cursor:not-allowed;}
1479
1575
  .fdy-switch input{appearance:none;-webkit-appearance:none;flex:none;position:relative;width:var(--control-switch-w);height:var(--control-switch-h);margin:0;border-radius:var(--radius-full);background:var(--color-control-border);cursor:pointer;transition:background var(--dur-base) var(--ease-standard);}
1480
1576
  .fdy-switch input::before{content:"";position:absolute;top:2px;left:2px;width:calc(var(--control-switch-h) - 4px);height:calc(var(--control-switch-h) - 4px);border-radius:50%;background:var(--color-switch-thumb);box-shadow:var(--shadow-1);transition:transform var(--dur-base) var(--ease-spring);}
1481
1577
  .fdy-switch input:checked{background:var(--color-primary);}
1482
1578
  .fdy-switch input:checked::before{transform:translateX(calc(var(--control-switch-w) - var(--control-switch-h)));}
1579
+ /* State hint (#068): one line saying what the CURRENT state means, never "on means X, off means Y",
1580
+ which leaves the reader to work out which half applies. The control's aria-describedby points at
1581
+ the .fdy-help WRAPPING both lines: a hidden child of a referenced node is skipped, so a screen
1582
+ reader hears only the line on screen. Point it at the two children and both are read. */
1583
+ .fdy-field:has(:is(.fdy-switch,.fdy-check) input:checked) [data-fdy-when="off"],
1584
+ .fdy-field:not(:has(:is(.fdy-switch,.fdy-check) input:checked)) [data-fdy-when="on"]{display:none;}
1483
1585
 
1484
1586
  /* Standalone checkbox (e.g. table row/select-all), supports :indeterminate */
1485
1587
  .fdy-checkbox{appearance:none;-webkit-appearance:none;flex:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);border-radius:var(--radius-sm);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
@@ -1487,11 +1589,11 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1487
1589
  .fdy-checkbox:checked,.fdy-checkbox:indeterminate{background:var(--color-primary);border-color:var(--color-primary);}
1488
1590
  .fdy-checkbox:checked::before{transform:scale(1);}
1489
1591
  .fdy-checkbox:indeterminate::before{width:.6rem;height:2px;transform:scale(1);clip-path:none;box-shadow:none;background:var(--color-on-primary);}
1490
- .fdy-checkbox:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1592
+ .fdy-checkbox:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1491
1593
  .fdy-checkbox:disabled{opacity:.5;cursor:not-allowed;}
1492
1594
 
1493
1595
  /* Freeday. Skeleton */
1494
- .fdy-skeleton{display:block;border-radius:var(--radius-sm);background:var(--color-surface-3);background-image:linear-gradient(90deg,transparent,color-mix(in srgb,var(--color-surface) 60%,transparent),transparent);background-size:200% 100%;background-repeat:no-repeat;animation:fdy-skeleton 1.4s ease-in-out infinite;}
1596
+ .fdy-skeleton{display:block;border-radius:var(--radius-sm);background:var(--color-surface-3);background-image:linear-gradient(90deg,transparent,rgba(var(--color-surface-rgb),.6),transparent);background-size:200% 100%;background-repeat:no-repeat;animation:fdy-skeleton 1.4s ease-in-out infinite;}
1495
1597
  .fdy-skeleton--text{height:.8rem;margin:.3rem 0;}
1496
1598
  .fdy-skeleton--title{height:1.2rem;width:40%;}
1497
1599
  .fdy-skeleton--circle{border-radius:var(--radius-full);}
@@ -1531,6 +1633,13 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1531
1633
  .fdy-state__text{font-size:var(--text-sm);color:var(--color-text-muted);max-width:24rem;line-height:var(--leading-normal);}
1532
1634
  .fdy-state__actions{margin-top:var(--space-2);display:flex;gap:var(--space-2);flex-wrap:wrap;justify-content:center;}
1533
1635
  .fdy-state--danger .fdy-state__icon{color:var(--color-danger-strong);}
1636
+ /* Compact (#069): the same state inside a small card, one line of icon and text instead of a
1637
+ page-sized block. Danger matters most here: a widget that renders zero for a failed request
1638
+ reads as "nothing to do", the opposite of what happened. */
1639
+ .fdy-state--compact{flex-direction:row;align-items:flex-start;text-align:left;gap:var(--space-2);padding:var(--space-2) 0;}
1640
+ .fdy-state--compact .fdy-state__icon{width:1rem;height:1rem;flex:none;}
1641
+ .fdy-state--compact .fdy-state__text{font-size:var(--text-xs);max-width:none;}
1642
+ .fdy-state--compact .fdy-state__actions{margin:0 0 0 auto;}
1534
1643
 
1535
1644
  /* Freeday. Stepper / Wizard (progress header + optional panels).
1536
1645
  * Enhanced by freeday-stepper.js for prev/next navigation. */
@@ -1565,7 +1674,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1565
1674
  .fdy-stepper[aria-busy="true"]{opacity:.72;}
1566
1675
  .fdy-step.is-active .fdy-step__label{color:var(--color-text);font-weight:var(--weight-semibold);}
1567
1676
  .fdy-step__btn:focus-visible{outline:none;}
1568
- .fdy-step__btn:focus-visible .fdy-step__marker{box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 30%,transparent);}
1677
+ .fdy-step__btn:focus-visible .fdy-step__marker{box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.3);}
1569
1678
 
1570
1679
  .fdy-step-panels{margin-top:var(--space-5);}
1571
1680
  .fdy-step-nav{display:flex;justify-content:space-between;gap:var(--space-2);margin-top:var(--space-5);}
@@ -1586,9 +1695,23 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1586
1695
  invisible on every second row. The tint is HALF a surface step rather than --color-surface-2
1587
1696
  itself, for the same reason: hover paints the full step, so the two must not be the same colour.
1588
1697
  Exposed as a Tier-3 token through the var() FALLBACK, never as a declaration — a custom property
1589
- set here would outrank a host's own :root override. */
1590
- .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)));}
1591
- .fdy-table tbody tr:hover{background:var(--color-surface-2);}
1698
+ set here would outrank a host's own :root override.
1699
+ The tint arrives as --color-surface-stripe, already flattened, rather than as a color-mix() of
1700
+ two surfaces. A custom property cannot carry color-mix(): the `background` that finally reads it
1701
+ contains var(), so on Safari <= 16.1 it failed at computed-value time and the stripe vanished,
1702
+ and a preceding declaration is never consulted in that path. The token is also OPAQUE on
1703
+ purpose, which the frozen column below depends on. */
1704
+ /* The resting colour is also published as --_fdy-row-rest, so a row that must not answer the
1705
+ pointer can hover back to exactly what it was (see --hover-actionable). */
1706
+ .fdy-table--striped tbody tr:nth-child(even){--_fdy-row-rest:var(--fdy-table-stripe,var(--color-surface-stripe));background:var(--_fdy-row-rest);}
1707
+ .fdy-table tbody tr:hover{background:var(--_fdy-row-hover,var(--color-surface-2));}
1708
+ /* Opt-in (#064): only a row that DOES something answers the pointer. A tint on a row with no click
1709
+ handler promises a target that is not there: people click the row, nothing happens, and the
1710
+ button they had to aim at was elsewhere in it. Striping, not hover, is what keeps the eye on a
1711
+ line. The inert row's hover resolves to its own resting colour, so a stripe survives it; a frozen
1712
+ cell falls back to its opaque surface, because it scrolls over its neighbours. */
1713
+ .fdy-table--hover-actionable tbody tr:not(.fdy-table__row--activatable){--_fdy-row-hover:var(--_fdy-row-rest,transparent);}
1714
+ .fdy-table--hover-actionable tbody tr:not(.fdy-table__row--activatable) td.fdy-table__freeze{--_fdy-row-hover:var(--_fdy-row-rest,var(--color-surface));}
1592
1715
  /* Monospace data, ids, codes, IPs, timestamps. Alignment-neutral so it works in any cell
1593
1716
  or inline (`<span class="fdy-mono">`), not just numeric columns. */
1594
1717
  .fdy-mono{font-family:var(--font-mono);font-variant-numeric:tabular-nums;}
@@ -1597,20 +1720,20 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1597
1720
  .fdy-table__num{text-align:right;font-variant-numeric:tabular-nums;font-family:var(--font-mono);}
1598
1721
  /* Loading / empty state cell (FdyTable spans it across all columns). */
1599
1722
  .fdy-table__state{text-align:center;color:var(--color-text-muted);padding:var(--space-6) var(--space-4);}
1600
- /* Activatable row (FdyTable rowActivatable): pointer + keyboard focus ring. The hover tint already
1601
- comes from the base `.fdy-table tbody tr:hover` rule, so this only adds the affordance + focus. */
1723
+ /* Activatable row (FdyTable rowActivatable): pointer + keyboard focus ring. The hover tint comes from
1724
+ the base `.fdy-table tbody tr:hover` rule, which --hover-actionable keeps for these rows only. */
1602
1725
  .fdy-table__row--activatable{cursor:pointer;}
1603
1726
  .fdy-table__row--activatable:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px;}
1604
1727
  /* Expandable detail row (FdyTable row-detail slot): a muted panel under its row, no hover tint. */
1605
1728
  .fdy-table__detailrow>td,.fdy-table__detailrow:hover>td{background:var(--color-surface-2);}
1606
1729
  .fdy-table th[aria-sort]{cursor:pointer;}
1607
- .fdy-table th[aria-sort="ascending"]::after{content:" ↑";color:var(--color-primary);}
1608
- .fdy-table th[aria-sort="descending"]::after{content:" ↓";color:var(--color-primary);}
1730
+ .fdy-table th[aria-sort="ascending"]::after{content:" ↑";color:var(--color-primary-strong);}
1731
+ .fdy-table th[aria-sort="descending"]::after{content:" ↓";color:var(--color-primary-strong);}
1609
1732
 
1610
1733
  /* Sortable header button (accessible click target inside th) */
1611
1734
  .fdy-table__sortbtn{appearance:none;border:0;background:transparent;font:inherit;color:inherit;text-transform:inherit;letter-spacing:inherit;font-weight:inherit;cursor:pointer;padding:0;}
1612
1735
  .fdy-table__sortbtn:hover{color:var(--color-text);}
1613
- .fdy-table__sortbtn:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1736
+ .fdy-table__sortbtn:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1614
1737
 
1615
1738
  /* Selection column + selected row */
1616
1739
  .fdy-table__selcol{width:1px;white-space:nowrap;}
@@ -1657,8 +1780,8 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1657
1780
  stripe — and deliberately WITHOUT `.fdy-table--sticky` in the selector, which would push this to
1658
1781
  (0,4,3) and beat the hover rule below. At (0,3,3) it ties, and the later rule wins, so a hovered
1659
1782
  row still bands unbroken across the freeze line. */
1660
- .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)));}
1661
- .fdy-table--sticky tbody tr:hover td.fdy-table__freeze{background:var(--color-surface-2);}
1783
+ .fdy-table--striped tbody tr:nth-child(even) td.fdy-table__freeze{background:var(--fdy-table-stripe,var(--color-surface-stripe));}
1784
+ .fdy-table--sticky tbody tr:hover td.fdy-table__freeze{background:var(--_fdy-row-hover,var(--color-surface-2));}
1662
1785
  /* A frozen header cell is stuck on both axes wherever the head is frozen too, so
1663
1786
  it has to outrank both. */
1664
1787
  .fdy-table--sticky thead .fdy-table__freeze{z-index:3;}
@@ -1694,7 +1817,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1694
1817
  .fdy-table__filterwrap{display:inline-flex;align-items:center;vertical-align:middle;}
1695
1818
  .fdy-table__filterbtn{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;margin-left:var(--space-1);padding:0;vertical-align:middle;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-subtle);cursor:pointer;}
1696
1819
  .fdy-table__filterbtn:hover{background:var(--color-surface-3);color:var(--color-text);}
1697
- .fdy-table__filterbtn:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1820
+ .fdy-table__filterbtn:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1698
1821
  .fdy-table__filterbtn.is-active{color:var(--color-primary-strong);}
1699
1822
  .fdy-table__filterbtn svg{width:.85rem;height:.85rem;}
1700
1823
 
@@ -1750,7 +1873,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1750
1873
  a panel per tab) for in-page tabs; use these classes on plain links for routed ones, and
1751
1874
  `.fdy-nav--horizontal` for an application's PRIMARY navigation. */
1752
1875
  .fdy-tabs__tab[aria-selected="true"],.fdy-tabs__tab[aria-current="page"]{color:var(--color-primary-strong);border-bottom-color:var(--color-primary);}
1753
- .fdy-tabs__tab:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1876
+ .fdy-tabs__tab:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1754
1877
  .fdy-tabs__panel{padding:var(--space-5) 0;}
1755
1878
  .fdy-tabs__panel[hidden]{display:none;}
1756
1879
  /* Reveal the newly-shown panel on tab switch (fade + rise), reduced-motion gated. */
@@ -1775,10 +1898,10 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1775
1898
  .fdy-timepicker{position:relative;display:inline-block;width:100%;max-width:11rem;}
1776
1899
  .fdy-timepicker__trigger{display:inline-flex;align-items:center;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
1777
1900
  .fdy-timepicker__trigger:hover{border-color:var(--color-text-muted);}
1778
- .fdy-timepicker__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1779
- .fdy-timepicker__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1901
+ .fdy-timepicker__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1902
+ .fdy-timepicker__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1780
1903
  .fdy-timepicker--error .fdy-timepicker__trigger,.fdy-timepicker__trigger[aria-invalid="true"]{border-color:var(--color-danger);}
1781
- .fdy-timepicker--error .fdy-timepicker__trigger:focus-visible,.fdy-timepicker--error .fdy-timepicker__trigger.is-open,.fdy-timepicker__trigger[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-danger) 26%,transparent);}
1904
+ .fdy-timepicker--error .fdy-timepicker__trigger:focus-visible,.fdy-timepicker--error .fdy-timepicker__trigger.is-open,.fdy-timepicker__trigger[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb),.26);}
1782
1905
  .fdy-timepicker__trigger:disabled,.fdy-timepicker__trigger[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
1783
1906
  .fdy-timepicker__value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-variant-numeric:tabular-nums;}
1784
1907
  .fdy-timepicker__value--placeholder{color:var(--color-text-subtle);}
@@ -1818,7 +1941,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1818
1941
  .fdy-toast__text{opacity:.88;line-height:var(--leading-normal);}
1819
1942
  .fdy-toast__close{flex:none;border:0;background:transparent;color:var(--color-inverse-text);opacity:.7;cursor:pointer;font-size:var(--text-lg);line-height:1;padding:0 var(--space-1);border-radius:var(--radius-sm);}
1820
1943
  .fdy-toast__close:hover{opacity:1;}
1821
- .fdy-toast__close:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-inverse-text) 40%,transparent);}
1944
+ .fdy-toast__close:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 3px rgba(var(--color-inverse-text-rgb),.4);}
1822
1945
  .fdy-toast.is-leaving{opacity:0;transform:translateX(16px);transition:opacity var(--dur-base) var(--ease-standard),transform var(--dur-base) var(--ease-standard);}
1823
1946
  @media (prefers-reduced-motion:no-preference){.fdy-toast{animation:fdy-toast-in var(--dur-base) var(--ease-spring);}}
1824
1947
  @keyframes fdy-toast-in{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:none;}}
@@ -1838,7 +1961,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1838
1961
  .fdy-tree__branch > summary{list-style:none;cursor:pointer;}
1839
1962
  .fdy-tree__branch > summary::-webkit-details-marker{display:none;}
1840
1963
  .fdy-tree__branch > summary:hover,.fdy-tree__leaf:hover{background:var(--color-surface-2);}
1841
- .fdy-tree__branch > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
1964
+ .fdy-tree__branch > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px rgba(var(--color-primary-rgb),.45);}
1842
1965
  .fdy-tree__chevron{flex:none;width:.9rem;height:.9rem;color:var(--color-text-subtle);transition:transform var(--dur-fast) var(--ease-standard);}
1843
1966
  .fdy-tree__branch[open] > summary .fdy-tree__chevron{transform:rotate(90deg);}
1844
1967
  .fdy-tree__icon{flex:none;width:1rem;height:1rem;color:var(--color-text-muted);}