@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
@@ -137,6 +137,14 @@ Recent additions most likely to replace something an app hand-rolled (all detail
137
137
  | `size="cfl"` on `FdyModal` | writing `.fdy-modal--cfl` by hand to reach the 46rem width |
138
138
  | `dialogOnly` + `open()` on `FdyCfl` | a hand-rolled picker, because the trigger is a chip or a table cell and not a 22rem field |
139
139
  | `.fdy-field--full` | `max-width: none` on the form container, once per form, because the fields are a flex column and not a `.fdy-form-grid` |
140
+ | `.fdy-page__columns` | a hand-rolled 8/4 or 3/6/3 grid per screen, with a summary rail that falls under every section on a phone |
141
+ | `.fdy-dl--grid` | a facts grid whose columns follow the window, so a side rail gets two 110px columns |
142
+ | `.fdy-card__head` | a card title row with its "View all" link at the bottom, moving every time the content grows |
143
+ | `settled` on `FdyDateRange` | reloading a list on each end of a range, the first time with the start alone |
144
+ | `{ filtered, clearFilters }` on `FdyTable`'s empty state | one "No data" sentence for both "nothing matches" and "nothing yet" |
145
+ | `Freeday.confirm()` | a confirm dialog assembled from a modal, two buttons and a flag so it answers only once |
146
+ | `[data-fdy-search]` | a search box that only searches on Enter, so emptying it leaves the old rows on screen |
147
+ | `[data-fdy-copy]` | selecting an ID by hand, or a copy button that silently does nothing over http |
140
148
  | `.fdy-nav--flat` | undoing the caret, the pointer cursor and the group divider yourself, on a sidebar whose group labels never collapse |
141
149
  | `--fdy-app-sidebar-w` | overriding `width` on `.fdy-app__sidebar`, which is also the rule that animates the collapse |
142
150
  | `@media (--fdy-filterbar-stacked)` / `(--fdy-nav-drawer)`, from `@cahyo-dimas/freeday/media` | a literal `640px` or `720px` retyped into your stylesheet, which no longer matches the kit's rule the day the kit retunes it — and which you could only have found by reading `filterbar.css` |
@@ -191,7 +191,7 @@ live docs also have a copy button per component.
191
191
  ```bash
192
192
  npm i @cahyo-dimas/freeday
193
193
  ```
194
- Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.5.0"` (public npm package). `dist/` is
194
+ Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.7.0"` (public npm package). `dist/` is
195
195
  committed and published → no build step; `npm ci` runs without auth.
196
196
 
197
197
  ### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cahyo-dimas/freeday",
3
- "version": "3.5.0",
3
+ "version": "3.7.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/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/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",
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);}
@@ -8,6 +8,8 @@
8
8
  .fdy-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--text-xs);}
9
9
  .fdy-avatar--sm{width:2rem;height:2rem;font-size:var(--text-xs);}
10
10
  .fdy-avatar--lg{width:3.5rem;height:3.5rem;font-size:var(--text-lg);}
11
+ /* Square: a mark that is not a person (a company logo, a card's icon chip, #070). */
12
+ .fdy-avatar--square{border-radius:var(--radius-md);}
11
13
  .fdy-avatar-group{display:inline-flex;}
12
14
  .fdy-avatar-group .fdy-avatar{border:2px solid var(--color-surface);margin-left:calc(var(--space-2)*-1);}
13
15
  .fdy-avatar-group .fdy-avatar:first-child{margin-left:0;}
@@ -15,12 +17,18 @@
15
17
  * text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
16
18
  * test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
17
19
  * hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
18
- .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));}
19
- .fdy-avatar--tone-1{--_fdy-avatar-tone:var(--tone-1);}
20
- .fdy-avatar--tone-2{--_fdy-avatar-tone:var(--tone-2);}
21
- .fdy-avatar--tone-3{--_fdy-avatar-tone:var(--tone-3);}
22
- .fdy-avatar--tone-4{--_fdy-avatar-tone:var(--tone-4);}
23
- .fdy-avatar--tone-5{--_fdy-avatar-tone:var(--tone-5);}
24
- .fdy-avatar--tone-6{--_fdy-avatar-tone:var(--tone-6);}
25
- .fdy-avatar--tone-7{--_fdy-avatar-tone:var(--tone-7);}
26
- .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);}
@@ -32,6 +32,13 @@
32
32
  absolutely positioned dismiss button back into the flow. Do not merge these two rules. */
33
33
  :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;}
34
34
  .fdy-card__body{padding:var(--space-5);}
35
+ /* Header band (#070): an identity mark, the title, and the card's way out pinned right. Beside the
36
+ title the action sits in one place on every card; under the content it moved each time the
37
+ content grew. Outside __body's padding, so its rule spans the card edge to edge. The mark is an
38
+ .fdy-avatar (--sm --square --tone-N), so its tints are the ones the contrast gate already covers. */
39
+ .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);}
40
+ .fdy-card__head .fdy-card__title{flex:1 1 auto;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
41
+ .fdy-card__action{flex:none;display:inline-flex;align-items:center;gap:var(--space-2);}
35
42
  .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);}
36
43
  .fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
37
44
  .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;}
@@ -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;}}
@@ -95,3 +95,39 @@
95
95
  .fdy-text-success{color:var(--color-success-strong);}
96
96
  .fdy-text-warning{color:var(--color-warning-strong);}
97
97
  .fdy-text-danger{color:var(--color-danger-strong);}
98
+ /* Document columns (#065): a form or record page with an optional identity rail on the left
99
+ * (__lead) and a summary rail on the right (__aside). The widths follow which rails are PRESENT,
100
+ * so the layout is decided once here and not again by every screen:
101
+ * __main alone one column
102
+ * __main + __aside 8 / 4 (from md)
103
+ * __lead + __main 3 / 9 (from md)
104
+ * all three 3 / 6 / 3 (from lg; between md and lg both rails share the left)
105
+ * Stacked, the summary rail comes FIRST: last, it sits under every section, and its button with it.
106
+ * From md each rail is sticky and scrolls on its own, so the total and the status stay answerable
107
+ * while the main column scrolls with the page, and a rail taller than the screen never hangs its
108
+ * end off it. Screen only: a printed page is not held to a window. */
109
+ .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;}
110
+ /* Under the shell the sticky topbar is in the way; --fdy-page-sticky-top overrides either default. */
111
+ .fdy-app .fdy-page__columns{--_fdy-rail-top:var(--fdy-page-sticky-top,calc(var(--space-16) + var(--space-4)));}
112
+ .fdy-page__columns > *{min-width:0;}
113
+ @media (max-width:959.98px){.fdy-page__aside{order:-1;}}
114
+ @media (min-width:960px){
115
+ .fdy-page__columns:has(> .fdy-page__aside){grid-template-columns:minmax(0,2fr) minmax(0,1fr);}
116
+ .fdy-page__columns:has(> .fdy-page__lead){grid-template-columns:minmax(0,1fr) minmax(0,3fr);}
117
+ /* Both rails, two columns: they stack on the left while __main spans both rows on the right. */
118
+ .fdy-page__columns:has(> .fdy-page__lead):has(> .fdy-page__aside) > .fdy-page__main{grid-row:span 2;}
119
+ }
120
+ @media (min-width:1280px){
121
+ .fdy-page__columns:has(> .fdy-page__lead):has(> .fdy-page__aside){grid-template-columns:minmax(0,1fr) minmax(0,2fr) minmax(0,1fr);}
122
+ .fdy-page__columns:has(> .fdy-page__lead):has(> .fdy-page__aside) > .fdy-page__main{grid-row:auto;}
123
+ }
124
+ /* The padding, taken back by the margin, is room for a focus ring at a card's edge: a scrolling box
125
+ clips whatever draws outside it. */
126
+ @media screen and (min-width:960px){
127
+ .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);}
128
+ }
129
+ /* Two sticky rails in ONE column would slide over each other: between md and lg, with both rails
130
+ present, they scroll with the page instead. */
131
+ @media screen and (min-width:960px) and (max-width:1279.98px){
132
+ .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;}
133
+ }
@@ -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;}