@cahyo-dimas/freeday 3.6.0 → 3.8.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 (52) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/COMPONENTS.md +119 -6
  3. package/README.id.md +19 -6
  4. package/README.md +17 -5
  5. package/USAGE.md +42 -0
  6. package/adapters/blazor/FdyDatepicker.razor +3 -1
  7. package/adapters/blazor/FdyDatepicker.razor.cs +22 -1
  8. package/adapters/blazor/FdyTableFooter.razor +6 -6
  9. package/adapters/blazor/FdyTableFooter.razor.cs +36 -0
  10. package/adapters/react/components/FdyDatepicker.tsx +122 -42
  11. package/adapters/react/components/FdyTableFooter.tsx +37 -6
  12. package/adapters/react/index.d.ts +1 -1
  13. package/adapters/vue/components/FdyDatepicker.vue +119 -34
  14. package/adapters/vue/components/FdyTableFooter.vue +39 -6
  15. package/dist/freeday-datepicker.js +68 -14
  16. package/dist/freeday-menu.js +15 -1
  17. package/dist/freeday-popover.js +33 -11
  18. package/dist/freeday-tabs.js +21 -2
  19. package/dist/freeday.bundle.css +493 -117
  20. package/dist/freeday.css +190 -113
  21. package/dist/freeday.js +137 -28
  22. package/dist/freeday.tokens.css +303 -4
  23. package/docs/getting-started.md +1 -1
  24. package/package.json +2 -2
  25. package/src/base.css +1 -1
  26. package/src/components/accordion.css +1 -1
  27. package/src/components/alert.css +5 -5
  28. package/src/components/app-shell.css +2 -2
  29. package/src/components/appbar.css +3 -3
  30. package/src/components/avatar.css +15 -9
  31. package/src/components/badge.css +18 -14
  32. package/src/components/breadcrumb.css +1 -1
  33. package/src/components/button.css +21 -13
  34. package/src/components/carousel.css +3 -3
  35. package/src/components/cascade.css +4 -4
  36. package/src/components/chip.css +33 -15
  37. package/src/components/combo.css +5 -5
  38. package/src/components/datepicker.css +8 -8
  39. package/src/components/file-upload.css +9 -4
  40. package/src/components/input-group.css +2 -2
  41. package/src/components/input.css +2 -2
  42. package/src/components/menu.css +3 -3
  43. package/src/components/pagination.css +1 -1
  44. package/src/components/selection.css +2 -2
  45. package/src/components/skeleton.css +1 -1
  46. package/src/components/stepper.css +1 -1
  47. package/src/components/table.css +12 -7
  48. package/src/components/tabs.css +32 -1
  49. package/src/components/timepicker.css +3 -3
  50. package/src/components/toast.css +1 -1
  51. package/src/components/tree.css +1 -1
  52. package/tokens/tokens.json +57 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cahyo-dimas/freeday",
3
- "version": "3.6.0",
3
+ "version": "3.8.0",
4
4
  "description": "Freeday: token-driven, framework-agnostic UI KIT (design source-of-truth).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -96,7 +96,7 @@
96
96
  "scripts": {
97
97
  "build": "node tokens/build.mjs",
98
98
  "test": "node --test",
99
- "test:browser": "node --test --test-concurrency=3 browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/field-width.mjs browser/cfl-multi.mjs browser/cfl-dialog-only.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs browser/bottom-nav.mjs browser/picker-states.mjs browser/tabs-strip.mjs browser/cfl-narrow.mjs browser/form-reveal.mjs browser/confirm-search-copy.mjs browser/daterange-settled.mjs browser/display-parts.mjs browser/table-empty.mjs browser/page-columns.mjs browser/table-hover.mjs browser/harness-contract.mjs",
99
+ "test:browser": "node --test --test-concurrency=3 browser/color-mix.mjs browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/field-width.mjs browser/cfl-multi.mjs browser/cfl-dialog-only.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs browser/bottom-nav.mjs browser/picker-states.mjs browser/tabs-strip.mjs browser/tabs-vertical.mjs browser/menu-align.mjs browser/cfl-narrow.mjs browser/form-reveal.mjs browser/confirm-search-copy.mjs browser/daterange-settled.mjs browser/display-parts.mjs browser/table-empty.mjs browser/table-footer-text.mjs browser/month-mode.mjs browser/page-columns.mjs browser/table-hover.mjs browser/harness-contract.mjs",
100
100
  "prepack": "node tokens/build.mjs",
101
101
  "version": "node tokens/build.mjs && git add dist",
102
102
  "typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit",
package/src/base.css CHANGED
@@ -11,7 +11,7 @@ body {
11
11
  -webkit-font-smoothing: antialiased;
12
12
  }
13
13
  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; }
14
- a { color: var(--color-primary); }
14
+ a { color: var(--color-primary-strong); }
15
15
  :focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-width); border-radius: var(--radius-xs); }
16
16
  :where(button, input, select, textarea) { font: inherit; }
17
17
  /* Hidden from sight, kept for assistive tech. `clip` hides PAINTING, not LAYOUT: the box still
@@ -10,7 +10,7 @@
10
10
  .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);}
11
11
  .fdy-accordion__item[open] > summary::after{transform:rotate(225deg);margin-top:.1rem;}
12
12
  .fdy-accordion__item > summary:hover{background:var(--color-surface-2);}
13
- .fdy-accordion__item > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
13
+ .fdy-accordion__item > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px rgba(var(--color-primary-rgb),.45);}
14
14
  .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);}
15
15
  /* Content reveal on open (fade + rise). Native <details> can't animate its own height cross-
16
16
  * browser without JS, so we animate the revealed panel instead, cheap, safe, reduced-motion gated. */
@@ -7,12 +7,12 @@
7
7
  .fdy-alert__text{color:var(--color-text);line-height:var(--leading-normal);}
8
8
  .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);}
9
9
  .fdy-alert__close:hover{color:var(--color-text);}
10
- .fdy-alert__close:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
11
- .fdy-alert--info{background:var(--color-info-soft);border-color:color-mix(in srgb,var(--color-info) 26%,transparent);}
10
+ .fdy-alert__close:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
11
+ .fdy-alert--info{background:var(--color-info-soft);border-color:rgba(var(--color-info-rgb),.26);}
12
12
  .fdy-alert--info .fdy-alert__title,.fdy-alert--info .fdy-alert__icon{color:var(--color-info-strong);}
13
- .fdy-alert--success{background:var(--color-success-soft);border-color:color-mix(in srgb,var(--color-success) 26%,transparent);}
13
+ .fdy-alert--success{background:var(--color-success-soft);border-color:rgba(var(--color-success-rgb),.26);}
14
14
  .fdy-alert--success .fdy-alert__title,.fdy-alert--success .fdy-alert__icon{color:var(--color-success-strong);}
15
- .fdy-alert--warning{background:var(--color-warning-soft);border-color:color-mix(in srgb,var(--color-warning) 26%,transparent);}
15
+ .fdy-alert--warning{background:var(--color-warning-soft);border-color:rgba(var(--color-warning-rgb),.26);}
16
16
  .fdy-alert--warning .fdy-alert__title,.fdy-alert--warning .fdy-alert__icon{color:var(--color-warning-strong);}
17
- .fdy-alert--danger{background:var(--color-danger-soft);border-color:color-mix(in srgb,var(--color-danger) 26%,transparent);}
17
+ .fdy-alert--danger{background:var(--color-danger-soft);border-color:rgba(var(--color-danger-rgb),.26);}
18
18
  .fdy-alert--danger .fdy-alert__title,.fdy-alert--danger .fdy-alert__icon{color:var(--color-danger-strong);}
@@ -44,7 +44,7 @@
44
44
  .fdy-nav__item:hover{background:var(--color-surface-2);color:var(--color-text);}
45
45
  /* Active indicator stays in the primary ramp so it never clashes with a re-themed accent. */
46
46
  .fdy-nav__item[aria-current="page"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
47
- .fdy-nav__item:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
47
+ .fdy-nav__item:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
48
48
  /* Sidebar-menu extras: per-node icon (leading), count badge (trailing), and a
49
49
  * text label that truncates. Any of these are optional, a bare text item works. */
50
50
  .fdy-nav__icon{flex:none;display:inline-flex;width:1.15rem;height:1.15rem;}
@@ -62,7 +62,7 @@
62
62
  .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);}
63
63
  .fdy-nav__grouplabel::-webkit-details-marker{display:none;}
64
64
  .fdy-nav__grouplabel:hover{color:var(--color-text-muted);}
65
- .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);}
65
+ .fdy-nav__grouplabel:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
66
66
  .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;}
67
67
  .fdy-nav__group[open] > .fdy-nav__grouplabel::after{transform:rotate(45deg);}
68
68
  /* --flat: calmer, non-collapsible grouped nav, hide the caret, drop the
@@ -15,10 +15,10 @@
15
15
  is the same component whether the nav is a sidebar column or a top row. */
16
16
  /* Controls on a coloured app bar go on-colour (icons stay legible, hover is a light wash) */
17
17
  .fdy-appbar--primary .fdy-btn--ghost{background:transparent;border-color:transparent;color:var(--color-on-primary);box-shadow:none;}
18
- .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;}
18
+ .fdy-appbar--primary .fdy-btn--ghost:hover{background:rgba(var(--color-on-primary-rgb),.16);color:var(--color-on-primary);transform:none;}
19
19
  /* Nav links on a coloured bar: full on-colour ink in every state (a dimmed variant would trade away
20
20
  the contrast the bar's own token pair guarantees). The current page is carried by the background
21
21
  wash plus the semibold weight the base rule already applies, and by `aria-current` itself. */
22
22
  .fdy-appbar--primary .fdy-nav__item{color:var(--color-on-primary);}
23
- .fdy-appbar--primary .fdy-nav__item:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);}
24
- .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);}
23
+ .fdy-appbar--primary .fdy-nav__item:hover{background:rgba(var(--color-on-primary-rgb),.16);color:var(--color-on-primary);}
24
+ .fdy-appbar--primary .fdy-nav__item[aria-current="page"]{background:rgba(var(--color-on-primary-rgb),.24);color:var(--color-on-primary);}
@@ -17,12 +17,18 @@
17
17
  * text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
18
18
  * test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
19
19
  * hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
20
- .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));}
21
- .fdy-avatar--tone-1{--_fdy-avatar-tone:var(--tone-1);}
22
- .fdy-avatar--tone-2{--_fdy-avatar-tone:var(--tone-2);}
23
- .fdy-avatar--tone-3{--_fdy-avatar-tone:var(--tone-3);}
24
- .fdy-avatar--tone-4{--_fdy-avatar-tone:var(--tone-4);}
25
- .fdy-avatar--tone-5{--_fdy-avatar-tone:var(--tone-5);}
26
- .fdy-avatar--tone-6{--_fdy-avatar-tone:var(--tone-6);}
27
- .fdy-avatar--tone-7{--_fdy-avatar-tone:var(--tone-7);}
28
- .fdy-avatar--tone-8{--_fdy-avatar-tone:var(--tone-8);}
20
+ /* One flat token per half, computed in advance by tokens/build.mjs: --tone-N-bg is 18% of the
21
+ * tone over --color-surface, --tone-N-ink is 50% of it toward --color-text, both per theme.
22
+ * They USED to be mixed here, with the hue injected per element through a private custom
23
+ * property — which put var() inside color-mix(), the one shape that cannot be rescued: valid at
24
+ * parse time, invalid at computed-value time, and there `background` and `color` drop to their
25
+ * INITIAL values, not to any fallback. @supports answered it for exactly as long as somebody
26
+ * remembered to write the counterpart; a precomputed token removes the question. */
27
+ .fdy-avatar--tone-1{background:var(--tone-1-bg);color:var(--tone-1-ink);}
28
+ .fdy-avatar--tone-2{background:var(--tone-2-bg);color:var(--tone-2-ink);}
29
+ .fdy-avatar--tone-3{background:var(--tone-3-bg);color:var(--tone-3-ink);}
30
+ .fdy-avatar--tone-4{background:var(--tone-4-bg);color:var(--tone-4-ink);}
31
+ .fdy-avatar--tone-5{background:var(--tone-5-bg);color:var(--tone-5-ink);}
32
+ .fdy-avatar--tone-6{background:var(--tone-6-bg);color:var(--tone-6-ink);}
33
+ .fdy-avatar--tone-7{background:var(--tone-7-bg);color:var(--tone-7-ink);}
34
+ .fdy-avatar--tone-8{background:var(--tone-8-bg);color:var(--tone-8-ink);}
@@ -1,9 +1,9 @@
1
1
  /* Freeday. Badge */
2
- .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);}
3
- .fdy-badge--success{color:var(--color-success-strong);background:var(--color-success-soft);border-color:color-mix(in srgb,var(--color-success) 26%,transparent);}
4
- .fdy-badge--warning{color:var(--color-warning-strong);background:var(--color-warning-soft);border-color:color-mix(in srgb,var(--color-warning) 26%,transparent);}
5
- .fdy-badge--danger{color:var(--color-danger-strong);background:var(--color-danger-soft);border-color:color-mix(in srgb,var(--color-danger) 26%,transparent);}
6
- .fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:color-mix(in srgb,var(--color-info) 26%,transparent);}
2
+ .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);}
3
+ .fdy-badge--success{color:var(--color-success-strong);background:var(--color-success-soft);border-color:rgba(var(--color-success-rgb),.26);}
4
+ .fdy-badge--warning{color:var(--color-warning-strong);background:var(--color-warning-soft);border-color:rgba(var(--color-warning-rgb),.26);}
5
+ .fdy-badge--danger{color:var(--color-danger-strong);background:var(--color-danger-soft);border-color:rgba(var(--color-danger-rgb),.26);}
6
+ .fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:rgba(var(--color-info-rgb),.26);}
7
7
  .fdy-badge--outline{background:transparent;border-color:var(--color-border-strong);color:var(--color-text-muted);box-shadow:none;}
8
8
 
9
9
  /* Categorical tone badges, a status vocabulary larger than the semantic palette, from the
@@ -15,15 +15,19 @@
15
15
  * cannot. A back-office document list carrying Draft, Submitted, Approved, Completed, Settled,
16
16
  * Closed, Transferred, InDeclaration, Open and Rejected in ONE column collapsed to three colours,
17
17
  * which is the report this shipped for. */
18
- .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);}
19
- .fdy-badge--tone-1{--_fdy-badge-tone:var(--tone-1);}
20
- .fdy-badge--tone-2{--_fdy-badge-tone:var(--tone-2);}
21
- .fdy-badge--tone-3{--_fdy-badge-tone:var(--tone-3);}
22
- .fdy-badge--tone-4{--_fdy-badge-tone:var(--tone-4);}
23
- .fdy-badge--tone-5{--_fdy-badge-tone:var(--tone-5);}
24
- .fdy-badge--tone-6{--_fdy-badge-tone:var(--tone-6);}
25
- .fdy-badge--tone-7{--_fdy-badge-tone:var(--tone-7);}
26
- .fdy-badge--tone-8{--_fdy-badge-tone:var(--tone-8);}
18
+ /* Precomputed tone tokens, the same --tone-N-bg / --tone-N-ink the avatar and the chip read, so
19
+ * one contrast assertion still covers all three. --tone-N-border-soft is the badge's own edge:
20
+ * 26% of the tone over transparent, which is a translucent colour and therefore stays rgba()
21
+ * rather than flattening to a hex. See avatar.css for why none of this is mixed in the
22
+ * component any more, and tokens/build.mjs for where the mixing moved to. */
23
+ .fdy-badge--tone-1{background:var(--tone-1-bg);color:var(--tone-1-ink);border-color:var(--tone-1-border-soft);}
24
+ .fdy-badge--tone-2{background:var(--tone-2-bg);color:var(--tone-2-ink);border-color:var(--tone-2-border-soft);}
25
+ .fdy-badge--tone-3{background:var(--tone-3-bg);color:var(--tone-3-ink);border-color:var(--tone-3-border-soft);}
26
+ .fdy-badge--tone-4{background:var(--tone-4-bg);color:var(--tone-4-ink);border-color:var(--tone-4-border-soft);}
27
+ .fdy-badge--tone-5{background:var(--tone-5-bg);color:var(--tone-5-ink);border-color:var(--tone-5-border-soft);}
28
+ .fdy-badge--tone-6{background:var(--tone-6-bg);color:var(--tone-6-ink);border-color:var(--tone-6-border-soft);}
29
+ .fdy-badge--tone-7{background:var(--tone-7-bg);color:var(--tone-7-ink);border-color:var(--tone-7-border-soft);}
30
+ .fdy-badge--tone-8{background:var(--tone-8-bg);color:var(--tone-8-ink);border-color:var(--tone-8-border-soft);}
27
31
 
28
32
  /* Overlay badge, anchors a small count/dot/icon badge to a corner of any element
29
33
  * (icon, button, text). Wrap the content in .fdy-badge-wrap; add .fdy-badge-ov as a
@@ -4,6 +4,6 @@
4
4
  .fdy-breadcrumb__item{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--color-text-muted);}
5
5
  .fdy-breadcrumb__link{color:var(--color-text-muted);text-decoration:none;border-radius:var(--radius-sm);}
6
6
  .fdy-breadcrumb__link:hover{color:var(--color-primary-strong);text-decoration:underline;}
7
- .fdy-breadcrumb__link:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
7
+ .fdy-breadcrumb__link:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
8
8
  .fdy-breadcrumb__item[aria-current="page"]{color:var(--color-text);font-weight:var(--weight-medium);}
9
9
  .fdy-breadcrumb__sep{color:var(--color-text-subtle);user-select:none;}
@@ -1,14 +1,22 @@
1
1
  /* Freeday. Button */
2
+ /* The fill is a FLAT colour with a black veil laid over it, not a gradient between two shades.
3
+ * It used to be `linear-gradient(primary, color-mix(in srgb, #000 10%, primary))`, and that is the
4
+ * same colour at every stop — veiling a solid base with black 0 -> .1 darkens it by exactly 10% —
5
+ * but the mix form carries var(), so on Safari <= 16.1 the whole declaration failed at
6
+ * computed-value time and the button lost its fill rather than falling back. Measured identical to
7
+ * the mix it replaces (within the 1/255 the gradient's own 8-bit rounding costs); see
8
+ * test/color-mix.test.mjs for the rule and tokens/build.mjs for why none of these may use
9
+ * color-mix(). Pressed states veil from the TOP, which is the same trick read upside down. */
2
10
  .fdy-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
3
11
  height:var(--control-h);padding:0 var(--space-4);
4
12
  font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-semibold);
5
13
  line-height:1;white-space:nowrap;cursor:pointer;text-decoration:none;color:var(--color-on-primary);
6
- background:linear-gradient(180deg,var(--color-primary),color-mix(in srgb,#000 10%,var(--color-primary)));
14
+ background:var(--color-primary) linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.1));
7
15
  border:var(--bw) solid transparent;border-radius:var(--radius-md);
8
- box-shadow:0 2px 9px -1px color-mix(in srgb,var(--color-primary) 50%,transparent),inset 0 1px 0 rgba(255,255,255,.26);
16
+ box-shadow:0 2px 9px -1px rgba(var(--color-primary-rgb),.5),inset 0 1px 0 rgba(255,255,255,.26);
9
17
  transition:background var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard),transform var(--dur-fast) var(--ease-standard);}
10
- .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);}
11
- .fdy-btn:active{transform:translateY(1px);box-shadow:0 1px 4px -1px color-mix(in srgb,var(--color-primary) 46%,transparent);}
18
+ .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);}
19
+ .fdy-btn:active{transform:translateY(1px);box-shadow:0 1px 4px -1px rgba(var(--color-primary-rgb),.46);}
12
20
  .fdy-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none;}
13
21
  /* Pressed / toggle. `aria-pressed` is already the right attribute for a toggle button, so this adds
14
22
  no class and no new markup contract, and it is what makes .fdy-btn-group a complete segmented
@@ -16,8 +24,8 @@
16
24
  defines its OWN pressed look below; a single shared rule cannot work, because a gradient is a
17
25
  background-IMAGE and would blank the ghost variant's background-color. Solid: invert the gradient
18
26
  and sink the shadow inward, so a held button reads as depressed, not merely primary-coloured. */
19
- .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);}
20
- .fdy-btn[aria-pressed="true"]:hover{transform:none;box-shadow:inset 0 2px 6px color-mix(in srgb,#000 22%,transparent);}
27
+ .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);}
28
+ .fdy-btn[aria-pressed="true"]:hover{transform:none;box-shadow:inset 0 2px 6px rgba(0,0,0,.22);}
21
29
 
22
30
  .fdy-btn--ghost{background:var(--color-surface);color:var(--color-primary-strong);border-color:var(--color-border-strong);box-shadow:var(--shadow-1);}
23
31
  .fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
@@ -29,15 +37,15 @@
29
37
  screen. `.fdy-menu__item--danger` has always modelled the quiet form (red text, not a red block);
30
38
  the pairing rules below bring it to the button. They are more specific than this one and come
31
39
  after it, so this rule needs no :not() guard of its own, one mechanism, not two. */
32
- .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);}
33
- .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);}
40
+ .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);}
41
+ .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);}
34
42
  .fdy-btn--danger:active{transform:translateY(1px);}
35
43
  /* Quiet destructive: the variant keeps its own ground and only the ink turns. */
36
- .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;}
44
+ .fdy-btn--ghost.fdy-btn--danger{background:transparent;color:var(--color-danger-strong);border-color:rgba(var(--color-danger-rgb),.4);box-shadow:none;}
37
45
  .fdy-btn--ghost.fdy-btn--danger:hover{background:var(--color-danger-soft);border-color:var(--color-danger);color:var(--color-danger-strong);}
38
46
  .fdy-btn--text.fdy-btn--danger{background:none;color:var(--color-danger-strong);border-color:transparent;box-shadow:none;}
39
47
  .fdy-btn--text.fdy-btn--danger:hover{background:var(--color-danger-soft);color:var(--color-danger-strong);}
40
- .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);}
48
+ .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);}
41
49
 
42
50
  .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);}
43
51
  .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);}
@@ -86,7 +94,7 @@
86
94
  .fdy-btn-group .fdy-btn:last-child{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md);}
87
95
 
88
96
  /* FAB, circular elevated action button (icon), optional extended pill with label */
89
- .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);}
97
+ .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);}
90
98
  .fdy-fab:hover{transform:translateY(-1px);box-shadow:var(--shadow-4);}
91
99
  .fdy-fab:active{transform:translateY(1px);}
92
100
  .fdy-fab:focus-visible{border-radius:var(--radius-full);}
@@ -95,5 +103,5 @@
95
103
  .fdy-fab--sm{width:2.5rem;height:2.5rem;}
96
104
  .fdy-fab--sm svg{width:1.25rem;height:1.25rem;}
97
105
  .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);}
98
- .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);}
99
- .fdy-fab--accent{background:linear-gradient(180deg,var(--color-accent),color-mix(in srgb,#000 10%,var(--color-accent)));color:var(--color-on-accent);}
106
+ .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);}
107
+ .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);}
@@ -6,9 +6,9 @@
6
6
  .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;}
7
7
  .fdy-carousel__viewport::-webkit-scrollbar{display:none;}
8
8
  .fdy-carousel__slide{flex:0 0 100%;min-width:0;scroll-snap-align:center;}
9
- .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);}
9
+ .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);}
10
10
  .fdy-carousel__arrow:hover{background:var(--color-surface);}
11
- .fdy-carousel__arrow:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 40%,transparent);}
11
+ .fdy-carousel__arrow:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.4);}
12
12
  .fdy-carousel__arrow:disabled{opacity:.4;cursor:not-allowed;}
13
13
  .fdy-carousel__arrow--prev{left:var(--space-3);}
14
14
  .fdy-carousel__arrow--next{right:var(--space-3);}
@@ -16,5 +16,5 @@
16
16
  .fdy-carousel__dots{display:flex;justify-content:center;gap:var(--space-2);margin-top:var(--space-3);}
17
17
  .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);}
18
18
  .fdy-carousel__dot[aria-current="true"]{background:var(--color-primary);width:1.25rem;}
19
- .fdy-carousel__dot:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 40%,transparent);}
19
+ .fdy-carousel__dot:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.4);}
20
20
  @media (prefers-reduced-motion:reduce){.fdy-carousel__viewport{scroll-behavior:auto;}}
@@ -7,14 +7,14 @@
7
7
  .fdy-cascade{position:relative;display:inline-block;width:100%;max-width:22rem;}
8
8
  .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);}
9
9
  .fdy-cascade__trigger:hover{border-color:var(--color-text-muted);}
10
- .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);}
11
- .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);}
10
+ .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);}
11
+ .fdy-cascade__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
12
12
  .fdy-cascade__trigger:disabled,.fdy-cascade__trigger[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
13
13
  .fdy-cascade__trigger[aria-readonly="true"]{background:var(--color-surface-2);border-color:var(--color-border);cursor:default;}
14
14
  .fdy-cascade__trigger[aria-readonly="true"]:hover{border-color:var(--color-border);}
15
15
  .fdy-cascade__trigger[aria-readonly="true"]:focus-visible{border-color:var(--color-border-strong);box-shadow:none;}
16
16
  .fdy-cascade--error .fdy-cascade__trigger,.fdy-cascade__trigger[aria-invalid="true"]{border-color:var(--color-danger);}
17
- .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);}
17
+ .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);}
18
18
  .fdy-cascade__value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
19
19
  .fdy-cascade__value--placeholder{color:var(--color-text-subtle);}
20
20
  .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);}
@@ -26,7 +26,7 @@
26
26
  .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);}
27
27
  .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;}
28
28
  .fdy-cascade__back:hover{background:var(--color-surface-3);color:var(--color-text);}
29
- .fdy-cascade__back:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
29
+ .fdy-cascade__back:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
30
30
  .fdy-cascade__back[hidden]{display:none;}
31
31
  .fdy-cascade__back svg{display:block;width:1rem;height:1rem;}
32
32
  .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;}
@@ -1,29 +1,47 @@
1
1
  /* Freeday. Chip (tag / removable filter) */
2
2
  .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);}
3
- .fdy-chip--primary{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:color-mix(in srgb,var(--color-primary) 22%,transparent);}
3
+ .fdy-chip--primary{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:rgba(var(--color-primary-rgb),.22);}
4
4
  /* Categorical tone chips, non-semantic category tags / legend swatches, from the general
5
5
  * --tone-1..8 palette. Same validated AA formula as .fdy-avatar--tone-* (text >=4.5:1 both themes,
6
6
  * gated by test/contrast.test.mjs). */
7
- .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));}
8
- .fdy-chip--tone-1{--_fdy-chip-tone:var(--tone-1);}
9
- .fdy-chip--tone-2{--_fdy-chip-tone:var(--tone-2);}
10
- .fdy-chip--tone-3{--_fdy-chip-tone:var(--tone-3);}
11
- .fdy-chip--tone-4{--_fdy-chip-tone:var(--tone-4);}
12
- .fdy-chip--tone-5{--_fdy-chip-tone:var(--tone-5);}
13
- .fdy-chip--tone-6{--_fdy-chip-tone:var(--tone-6);}
14
- .fdy-chip--tone-7{--_fdy-chip-tone:var(--tone-7);}
15
- .fdy-chip--tone-8{--_fdy-chip-tone:var(--tone-8);}
16
- .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;}
17
- .fdy-chip__remove:hover{opacity:1;background:color-mix(in srgb,currentColor 16%,transparent);}
18
- .fdy-chip__remove:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
7
+ /* Precomputed tone tokens, the same --tone-N-bg / --tone-N-ink the avatar and the badge read.
8
+ * --tone-N-border is the chip's edge: 30% of the tone over --color-border, opaque, which is a
9
+ * different edge from the badge's translucent one and so a different token. See avatar.css for
10
+ * why the mix left the component, and tokens/build.mjs for where it went. */
11
+ .fdy-chip--tone-1{background:var(--tone-1-bg);color:var(--tone-1-ink);border-color:var(--tone-1-border);}
12
+ .fdy-chip--tone-2{background:var(--tone-2-bg);color:var(--tone-2-ink);border-color:var(--tone-2-border);}
13
+ .fdy-chip--tone-3{background:var(--tone-3-bg);color:var(--tone-3-ink);border-color:var(--tone-3-border);}
14
+ .fdy-chip--tone-4{background:var(--tone-4-bg);color:var(--tone-4-ink);border-color:var(--tone-4-border);}
15
+ .fdy-chip--tone-5{background:var(--tone-5-bg);color:var(--tone-5-ink);border-color:var(--tone-5-border);}
16
+ .fdy-chip--tone-6{background:var(--tone-6-bg);color:var(--tone-6-ink);border-color:var(--tone-6-border);}
17
+ .fdy-chip--tone-7{background:var(--tone-7-bg);color:var(--tone-7-ink);border-color:var(--tone-7-border);}
18
+ .fdy-chip--tone-8{background:var(--tone-8-bg);color:var(--tone-8-ink);border-color:var(--tone-8-border);}
19
+ .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;}
20
+ /* The hover wash is 16% of the chip's OWN ink, and the chip's ink is whatever the variant set:
21
+ * --color-text, --color-primary-strong, --tone-N-ink. So no token can stand in for it, which is why
22
+ * this was the last color-mix() in the kit — and the only one whose two-line fallback actually
23
+ * worked, the mix carrying no var() and therefore being rejected at parse time.
24
+ *
25
+ * It is a VEIL now, the same trick the button gradients use (button.css): currentColor at .16 over
26
+ * the chip, which is algebraically the colour the mix produced — `F·.16 + (1-.16)·ground` either
27
+ * way, including at the glyph's antialiased edges, where the veil's colour equals the glyph's and so
28
+ * composites back to itself. No @supports, no fallback line, and nothing newer than Safari 4.
29
+ *
30
+ * Absolutely positioned, so it paints above the × as well as the background; `border-radius:inherit`
31
+ * keeps it inside the pill. No transition: the mix had none, and a veil that fades is a different
32
+ * component. */
33
+ .fdy-chip__remove::before{content:"";position:absolute;inset:0;border-radius:inherit;background:currentColor;opacity:0;}
34
+ .fdy-chip__remove:hover{opacity:1;}
35
+ .fdy-chip__remove:hover::before{opacity:.16;}
36
+ .fdy-chip__remove:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
19
37
  /* Group wrapper for a row of chips (choice / filter). */
20
38
  .fdy-chips{display:flex;flex-wrap:wrap;gap:var(--space-2);}
21
39
  /* Interactive chips: choice (toggle) + filter (toggle with a check when on).
22
40
  * Rendered as <button aria-pressed> and enhanced by freeday-chip.js. */
23
41
  .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);}
24
42
  .fdy-chip--choice:hover,.fdy-chip--filter:hover{background:var(--color-surface-3);border-color:var(--color-text-subtle);}
25
- .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);}
26
- .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);}
43
+ .fdy-chip--choice:focus-visible,.fdy-chip--filter:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
44
+ .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);}
27
45
  .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;}
28
46
  .fdy-chip__check{display:none;flex:none;width:.85rem;height:.85rem;margin-left:calc(var(--space-1)*-1);}
29
47
  .fdy-chip__check svg{display:block;width:100%;height:100%;}
@@ -2,8 +2,8 @@
2
2
  .fdy-combo{position:relative;display:block;max-width:22rem;}
3
3
  .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);}
4
4
  .fdy-combo__button:hover{border-color:var(--color-text-muted);}
5
- .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);}
6
- .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);}
5
+ .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);}
6
+ .fdy-combo__button.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
7
7
  .fdy-combo__button:disabled,.fdy-combo__button[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
8
8
  /* Read-only: stays focusable & shows its value at full contrast but can't open, muted surface,
9
9
  softened border, default cursor, no focus-ring escalation (matches input[readonly], #39). */
@@ -11,7 +11,7 @@
11
11
  .fdy-combo__button[aria-readonly="true"]:hover{border-color:var(--color-border);}
12
12
  .fdy-combo__button[aria-readonly="true"]:focus-visible{border-color:var(--color-border-strong);box-shadow:none;}
13
13
  .fdy-combo--error .fdy-combo__button,.fdy-combo__button[aria-invalid="true"]{border-color:var(--color-danger);}
14
- .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);}
14
+ .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);}
15
15
  .fdy-combo__value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
16
16
  .fdy-combo__value--placeholder{color:var(--color-text-subtle);}
17
17
  .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);}
@@ -26,14 +26,14 @@
26
26
  .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;}
27
27
  .fdy-combo__option.is-highlighted{background:var(--color-surface-3);}
28
28
  .fdy-combo__option[aria-selected="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
29
- .fdy-combo__option.is-highlighted[aria-selected="true"]{box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 55%,transparent);}
29
+ .fdy-combo__option.is-highlighted[aria-selected="true"]{box-shadow:inset 0 0 0 2px rgba(var(--color-primary-rgb),.55);}
30
30
  /* The tick is CSS, not content (#004 §2). Inside role="option" it joined the option's ACCESSIBLE
31
31
  NAME, so the selected option announced "✓August" while every other announced "August": the state
32
32
  was read twice, once as aria-selected, once as decoration, and a test asking for an option by
33
33
  its own label stopped matching the one that was selected. The `/ ""` is alt text: the glyph is
34
34
  painted, the accessibility tree gets nothing. Same reasoning as .fdy-label--required (1.29.0).
35
35
  The empty box still reserves its width, so labels stay aligned whether selected or not. */
36
- .fdy-combo__check{display:inline-flex;justify-content:center;width:var(--space-4);flex:none;font-weight:var(--weight-bold);color:var(--color-primary);}
36
+ .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);}
37
37
  .fdy-combo__option[aria-selected="true"] .fdy-combo__check::after{content:"✓" / "";}
38
38
  @media (prefers-reduced-motion:no-preference){.fdy-combo__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
39
39
  @keyframes fdy-combo-in{from{opacity:0;transform:translateY(-4px);}to{opacity:1;transform:none;}}
@@ -9,10 +9,10 @@
9
9
  .fdy-field>.fdy-datepicker,.fdy-field>.fdy-timepicker,.fdy-field>.fdy-daterange{max-width:none;}
10
10
  .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);}
11
11
  .fdy-datepicker__trigger:hover{border-color:var(--color-text-muted);}
12
- .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);}
13
- .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);}
12
+ .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);}
13
+ .fdy-datepicker__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
14
14
  .fdy-datepicker--error .fdy-datepicker__trigger,.fdy-datepicker__trigger[aria-invalid="true"]{border-color:var(--color-danger);}
15
- .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);}
15
+ .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);}
16
16
  .fdy-datepicker__trigger:disabled,.fdy-datepicker__trigger[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
17
17
  .fdy-datepicker__trigger[aria-readonly="true"]{background:var(--color-surface-2);border-color:var(--color-border);cursor:default;}
18
18
  .fdy-datepicker__trigger[aria-readonly="true"]:hover{border-color:var(--color-border);}
@@ -27,7 +27,7 @@
27
27
  .fdy-datepicker__icon.is-hidden{visibility:hidden;}
28
28
  .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);}
29
29
  .fdy-datepicker__clear:hover{color:var(--color-text);background:var(--color-surface-2);}
30
- .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);}
30
+ .fdy-datepicker__clear:focus-visible{outline:none;color:var(--color-text);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
31
31
  .fdy-datepicker__clear svg{display:block;width:.9rem;height:.9rem;}
32
32
 
33
33
  .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);}
@@ -39,16 +39,16 @@
39
39
  reset and the same hover/focus treatment as the arrows beside it. */
40
40
  .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;}
41
41
  .fdy-cal__title:hover{background:var(--color-surface-2);}
42
- .fdy-cal__title:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
42
+ .fdy-cal__title:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
43
43
  .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;}
44
44
  .fdy-cal__month:hover,.fdy-cal__year:hover{background:var(--color-surface-2);}
45
- .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);}
45
+ .fdy-cal__month:focus-visible,.fdy-cal__year:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
46
46
  .fdy-cal__month:disabled,.fdy-cal__year:disabled{opacity:.4;cursor:not-allowed;background:transparent;}
47
47
  .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);}
48
48
  .fdy-cal__month.is-selected,.fdy-cal__year.is-selected{background:var(--color-primary);color:var(--color-on-primary);font-weight:var(--weight-semibold);}
49
49
  .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;}
50
50
  .fdy-cal__nav:hover{background:var(--color-surface-2);color:var(--color-text);}
51
- .fdy-cal__nav:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
51
+ .fdy-cal__nav:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
52
52
  .fdy-cal__grid{display:grid;grid-template-columns:repeat(7,2rem);gap:2px;}
53
53
  /* Month grid. 3×4 over the same track width as the 7-column day grid, so the panel does not resize
54
54
  when you drill and the pointer does not have to travel.
@@ -60,7 +60,7 @@
60
60
  .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;}
61
61
  .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;}
62
62
  .fdy-cal__day:hover{background:var(--color-surface-2);}
63
- .fdy-cal__day:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
63
+ .fdy-cal__day:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
64
64
  .fdy-cal__day.is-outside{color:var(--color-text-subtle);opacity:.55;}
65
65
  .fdy-cal__day.is-today{font-weight:var(--weight-bold);box-shadow:inset 0 0 0 1.5px var(--color-border-strong);}
66
66
  .fdy-cal__day.in-range{background:var(--color-primary-soft);color:var(--color-primary-strong);border-radius:0;}
@@ -2,11 +2,11 @@
2
2
  * Enhanced by freeday-upload.js; states: idle · drag-over · uploading · done · error. */
3
3
  .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);}
4
4
  .fdy-dropzone:hover{border-color:var(--color-text-muted);}
5
- .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);}
5
+ .fdy-dropzone:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
6
6
  .fdy-dropzone.is-dragover{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary-strong);}
7
7
  .fdy-dropzone[aria-disabled="true"],.fdy-dropzone.is-disabled{opacity:.55;cursor:not-allowed;pointer-events:none;}
8
8
  .fdy-dropzone__icon{color:var(--color-text-subtle);}
9
- .fdy-dropzone.is-dragover .fdy-dropzone__icon{color:var(--color-primary);}
9
+ .fdy-dropzone.is-dragover .fdy-dropzone__icon{color:var(--color-primary-strong);}
10
10
  .fdy-dropzone__icon svg{display:block;width:2rem;height:2rem;}
11
11
  .fdy-dropzone__title{font-size:inherit;font-weight:var(--weight-medium);color:var(--color-text);margin:0;}
12
12
  .fdy-dropzone__hint{font-size:var(--text-xs);}
@@ -33,8 +33,13 @@
33
33
  .fdy-file__progress{margin-top:var(--space-2);}
34
34
  .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;}
35
35
  .fdy-file__remove:hover{background:var(--color-surface-2);color:var(--color-text);}
36
- .fdy-file__remove:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
36
+ .fdy-file__remove:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
37
37
  .fdy-file--success .fdy-file__icon{color:var(--color-success);}
38
- .fdy-file--error{border-color:color-mix(in srgb,var(--color-danger) 40%,var(--color-border));}
38
+ /* 40% of --color-danger over --color-border, as a token rather than as a mix written here: it blends
39
+ * two ROLES, so no channel triple can state it, and mixing it in the component carried var() into
40
+ * color-mix() — the shape that goes invalid at computed-value time and drops border-color to its
41
+ * initial currentColor, which is the black box an iPhone 7 drew around a failed upload. The blend is
42
+ * now computed per theme by tokens/build.mjs. */
43
+ .fdy-file--error{border-color:var(--color-danger-border);}
39
44
  .fdy-file--error .fdy-file__icon{color:var(--color-danger);}
40
45
  .fdy-file--error .fdy-file__sub{color:var(--color-danger-strong);}
@@ -1,7 +1,7 @@
1
1
  /* Freeday. Input group (prefix/suffix addons, e.g. Rp / % / icon) */
2
2
  .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);}
3
3
  .fdy-input-group:hover{border-color:var(--color-text-muted);}
4
- .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);}
4
+ .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);}
5
5
  /* The group owns the height; the input fills whatever the border leaves. It used to subtract 3px
6
6
  for two 1.5px borders, which is arithmetic the engine is free to round: at dpr 1 each border
7
7
  resolves to 1px, so the group rendered 1px short of every control beside it (#018). Stretching
@@ -30,4 +30,4 @@
30
30
  .fdy-input-group__btn:disabled:hover{background:var(--color-surface-2);color:var(--color-text-muted);}
31
31
  /* Error state must win over the neutralizing rules above, promote to the wrapper. */
32
32
  .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);}
33
- .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);}
33
+ .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);}
@@ -32,7 +32,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
32
32
  .fdy-input::placeholder,.fdy-textarea::placeholder{color:var(--color-text-subtle);}
33
33
  .fdy-textarea{height:auto;min-height:5.5rem;padding:var(--space-3);line-height:var(--leading-normal);resize:vertical;}
34
34
  .fdy-input:hover,.fdy-textarea:hover{border-color:var(--color-text-muted);}
35
- .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);}
35
+ .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);}
36
36
  .fdy-input:disabled,.fdy-textarea:disabled,.fdy-input[aria-disabled="true"],.fdy-textarea[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
37
37
  /* Read-only (#39): the value is real and may be read/copied, only not edited. Full contrast (unlike
38
38
  :disabled which dims to placeholder-grey), muted surface + softened border so it still reads as
@@ -49,7 +49,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
49
49
  .fdy-input[type="number"]{appearance:textfield;-webkit-appearance:textfield;}
50
50
  .fdy-input[type="number"]::-webkit-outer-spin-button,.fdy-input[type="number"]::-webkit-inner-spin-button{appearance:none;-webkit-appearance:none;margin:0;}
51
51
  .fdy-input--error,.fdy-input[aria-invalid="true"],.fdy-textarea[aria-invalid="true"]{border-color:var(--color-danger);}
52
- .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);}
52
+ .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);}
53
53
  /* Required marker. The control already carries `required`, so the asterisk is decoration: the
54
54
  `/ ""` is CSS alt text, the glyph is painted but exposes NOTHING to the accessibility tree, so a
55
55
  screen reader does not read "star" after every label. That is stricter than a <span> an app has to
@@ -8,7 +8,7 @@
8
8
  /* Focus is not hover. The enhancer moves real DOM focus (freeday-menu.js `.focus()`), so this is
9
9
  the only thing a keyboard user has to go on, with the hover fill alone, arrowing through the
10
10
  menu is invisible. Same ring as .fdy-nav__item, which got this right. Reported twice. */
11
- .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);}
11
+ .fdy-menu__item:focus-visible{outline:none;background:var(--color-surface-3);box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
12
12
  .fdy-menu__item:disabled{opacity:.5;cursor:not-allowed;background:transparent;}
13
13
  .fdy-menu__item--danger{color:var(--color-danger-strong);}
14
14
  .fdy-menu__item svg{display:block;width:1rem;height:1rem;flex:none;}
@@ -25,7 +25,7 @@
25
25
  .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;}
26
26
  /* Hairline divider between the halves (outranks the segment rule above). */
27
27
  .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);}
28
- .fdy-btn-split:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px color-mix(in srgb,var(--color-primary) 55%,transparent);}
29
- .fdy-btn-split:active{transform:translateY(1px);box-shadow:0 1px 4px -1px color-mix(in srgb,var(--color-primary) 46%,transparent);}
28
+ .fdy-btn-split:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px rgba(var(--color-primary-rgb),.55);}
29
+ .fdy-btn-split:active{transform:translateY(1px);box-shadow:0 1px 4px -1px rgba(var(--color-primary-rgb),.46);}
30
30
  .fdy-btn-split__toggle:hover,.fdy-btn-split__toggle:focus-visible{z-index:1;}
31
31
  .fdy-btn-split__toggle svg{display:block;width:1rem;height:1rem;}