@cahyo-dimas/freeday 2.2.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +251 -0
- package/COMPONENTS.md +183 -7
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/USAGE.md +1 -1
- package/adapters/blazor/FdyAppShell.razor +1 -1
- package/adapters/blazor/FdyAppShell.razor.cs +35 -0
- package/adapters/blazor/FdyTable.razor +44 -4
- package/adapters/blazor/FdyTable.razor.cs +110 -0
- package/adapters/blazor/FdyTableFooter.razor +1 -1
- package/adapters/blazor/freeday-blazor.js +8 -0
- package/adapters/react/components/FdyAppShell.tsx +52 -11
- package/adapters/react/components/FdyTable.tsx +123 -2
- package/adapters/react/components/FdyTableFooter.tsx +1 -1
- package/adapters/vue/components/FdyAppShell.vue +41 -11
- package/adapters/vue/components/FdyTable.vue +125 -4
- package/adapters/vue/components/FdyTableFooter.vue +1 -1
- package/dist/freeday-app-shell.js +23 -5
- package/dist/freeday-busy.js +168 -0
- package/dist/freeday-stepper.js +54 -4
- package/dist/freeday-table.js +7 -0
- package/dist/freeday.bundle.css +652 -43
- package/dist/freeday.css +129 -15
- package/dist/freeday.js +253 -9
- package/dist/freeday.tokens.css +523 -28
- package/docs/agent-onboarding.md +4 -0
- package/docs/getting-started.md +1 -1
- package/package.json +4 -3
- package/src/components/app-shell.css +29 -5
- package/src/components/appbar.css +2 -2
- package/src/components/busy.css +42 -0
- package/src/components/card.css +1 -1
- package/src/components/composition.css +15 -2
- package/src/components/drawer.css +1 -1
- package/src/components/filterbar.css +3 -2
- package/src/components/input.css +9 -0
- package/src/components/menu.css +1 -1
- package/src/components/modal.css +1 -1
- package/src/components/stepper.css +11 -0
- package/src/components/table.css +13 -0
- package/tokens/tokens.json +54 -10
package/docs/getting-started.md
CHANGED
|
@@ -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": "^
|
|
194
|
+
Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.1.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
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Freeday: token-driven, framework-agnostic UI KIT (design source-of-truth).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -92,11 +92,12 @@
|
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "node tokens/build.mjs",
|
|
94
94
|
"test": "node --test",
|
|
95
|
-
"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/cfl-multi.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/picker-states.mjs",
|
|
95
|
+
"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/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs browser/picker-states.mjs browser/harness-contract.mjs",
|
|
96
96
|
"prepack": "node tokens/build.mjs",
|
|
97
97
|
"version": "node tokens/build.mjs && git add dist",
|
|
98
98
|
"typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit",
|
|
99
|
-
"test:blazor": "dotnet test test/blazor -v quiet --nologo"
|
|
99
|
+
"test:blazor": "dotnet test test/blazor -v quiet --nologo",
|
|
100
|
+
"test:blazor-server": "node --test browser/blazor-server.mjs"
|
|
100
101
|
},
|
|
101
102
|
"devDependencies": {
|
|
102
103
|
"@types/react": "^19.2.17",
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
|
|
7
7
|
* topbar a tall containing block so it can travel, a bare grid cell can't. */
|
|
8
8
|
.fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
|
|
9
|
-
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:15.5rem;box-sizing:border-box;background:var(--color-surface);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
9
|
+
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:15.5rem;box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
10
10
|
.fdy-app__content{flex:1;min-width:0;display:flex;flex-direction:column;}
|
|
11
11
|
/* Brand is a fixed header, same height as the topbar so they align, and only
|
|
12
12
|
* the nav below it scrolls. Bare text (no children) renders as one bold
|
|
@@ -79,7 +79,23 @@
|
|
|
79
79
|
/* Desktop collapse: animate width -> 0 (content clips via overflow:hidden; the content area
|
|
80
80
|
* flexes in to fill). Not display:none, so the collapse/expand slides instead of snapping. */
|
|
81
81
|
@media (min-width:721px){.fdy-app--nav-collapsed .fdy-app__sidebar{width:0;border-right-width:0;}}
|
|
82
|
-
|
|
82
|
+
/* Desktop OVERLAY: the nav floats over the content instead of displacing it, the arrangement a
|
|
83
|
+
* "menu mode" preference offers on a wide screen. The declarations below are the mobile off-canvas
|
|
84
|
+
* ones repeated, and that repetition is not an oversight: the two conditions are
|
|
85
|
+
* `(max-width:720px)` and `(min-width:721px) and .fdy-app--nav-overlay`, which no single selector
|
|
86
|
+
* can express, and the mobile path has to keep working with no JS and no class at all. What the
|
|
87
|
+
* modifier changes is the LAYOUT; every behaviour (backdrop, Escape, close-on-follow, focus return,
|
|
88
|
+
* inert, the Tab trap) is the same code path the drawer already used. */
|
|
89
|
+
@media (min-width:721px){
|
|
90
|
+
.fdy-app--nav-overlay .fdy-app__sidebar{position:fixed;top:0;left:0;bottom:0;height:100dvh;width:16rem;z-index:60;transform:translateX(-100%);box-shadow:var(--shadow-4);transition:transform var(--dur-base) var(--ease-standard);}
|
|
91
|
+
.fdy-app--nav-overlay.fdy-app--nav-open .fdy-app__sidebar{transform:translateX(0);}
|
|
92
|
+
.fdy-app--nav-overlay .fdy-app__backdrop{display:block;position:fixed;inset:0;z-index:55;background:var(--color-scrim);opacity:0;visibility:hidden;pointer-events:none;transition:opacity var(--dur-base) var(--ease-standard),visibility var(--dur-base) var(--ease-standard);}
|
|
93
|
+
.fdy-app--nav-overlay.fdy-app--nav-open .fdy-app__backdrop{opacity:1;visibility:visible;pointer-events:auto;}
|
|
94
|
+
/* In overlay mode the sidebar is out of flow, so the collapse-to-zero-width rule above must not
|
|
95
|
+
also apply — it would fight the transform for the same panel. */
|
|
96
|
+
.fdy-app--nav-overlay.fdy-app--nav-collapsed .fdy-app__sidebar{width:16rem;border-right-width:var(--bw);}
|
|
97
|
+
}
|
|
98
|
+
.fdy-app__topbar{position:sticky;top:0;z-index:20;min-height:var(--space-16);display:flex;align-items:center;gap:var(--space-3);padding:0 var(--space-8);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-bottom:var(--bw) solid var(--color-border);}
|
|
83
99
|
.fdy-app__title{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);color:var(--color-text);margin:0 auto 0 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
84
100
|
.fdy-app__main{flex:1;padding:var(--space-8);}
|
|
85
101
|
.fdy-skip{position:absolute;left:var(--space-2);top:var(--space-2);background:var(--color-primary);color:var(--color-on-primary);padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);z-index:100;transform:translateY(-250%);transition:transform var(--dur-fast) var(--ease-standard);}
|
|
@@ -88,13 +104,21 @@
|
|
|
88
104
|
* drop sticky so nothing escapes the box; sidebar stretches to fill it. */
|
|
89
105
|
.fdy-app--static .fdy-app__sidebar{position:static;height:auto;align-self:stretch;}
|
|
90
106
|
.fdy-app--static .fdy-app__topbar{position:static;}
|
|
91
|
-
/*
|
|
107
|
+
/* `pointer-events` on the backdrop is load-bearing, not a belt-and-braces extra. `visibility` is a
|
|
108
|
+
* TRANSITIONED property here, so a backdrop on its way out stays `visible` for the whole duration
|
|
109
|
+
* and keeps swallowing clicks aimed at the topbar underneath it — and switching INTO overlay mode is
|
|
110
|
+
* worse, because the element goes display:none -> display:block with its visibility animating from
|
|
111
|
+
* the initial `visible`, so a mode switch alone hands the page ~200ms of dead clicks. Measured: with
|
|
112
|
+
* only `visibility:hidden`, elementFromPoint over the nav toggle returned the backdrop.
|
|
113
|
+
* Mobile: the sidebar becomes an off-canvas drawer opened by the topbar toggle. */
|
|
92
114
|
.fdy-app__backdrop{display:none;}
|
|
93
115
|
@media (max-width:720px){
|
|
94
116
|
.fdy-app__sidebar{position:fixed;top:0;left:0;bottom:0;height:100dvh;width:16rem;z-index:60;transform:translateX(-100%);transition:transform var(--dur-base) var(--ease-standard);box-shadow:var(--shadow-4);}
|
|
95
117
|
.fdy-app--nav-open .fdy-app__sidebar{transform:translateX(0);}
|
|
96
|
-
|
|
97
|
-
|
|
118
|
+
/* --color-scrim, not a literal: this was the last raw colour left in a component stylesheet, and
|
|
119
|
+
it also means the drawer behind the nav dims exactly like every other overlay in the kit. */
|
|
120
|
+
.fdy-app__backdrop{display:block;position:fixed;inset:0;z-index:55;background:var(--color-scrim);opacity:0;visibility:hidden;pointer-events:none;transition:opacity var(--dur-base) var(--ease-standard),visibility var(--dur-base) var(--ease-standard);}
|
|
121
|
+
.fdy-app--nav-open .fdy-app__backdrop{opacity:1;visibility:visible;pointer-events:auto;}
|
|
98
122
|
.fdy-app__main{padding:var(--space-5);}
|
|
99
123
|
.fdy-app__topbar{padding:0 var(--space-4);}
|
|
100
124
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/* Freeday. App bar (standalone top bar: brand · actions · spacer).
|
|
2
2
|
* Distinct from .fdy-app__topbar (which is bound to the shell grid). Use anywhere;
|
|
3
3
|
* sticky / elevated / dense / coloured via modifiers. Pure CSS. */
|
|
4
|
-
.fdy-appbar{display:flex;align-items:center;gap:var(--space-3);height:var(--space-16);padding:0 var(--space-4);background:var(--color-surface);color:var(--color-text);border-bottom:var(--bw) solid var(--color-border);}
|
|
4
|
+
.fdy-appbar{display:flex;align-items:center;gap:var(--space-3);height:var(--space-16);padding:0 var(--space-4);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);border-bottom:var(--bw) solid var(--color-border);}
|
|
5
5
|
.fdy-appbar--sticky{position:sticky;top:0;z-index:90;}
|
|
6
|
-
.fdy-appbar--elevated{border-bottom:0;box-shadow:var(--shadow-2);}
|
|
6
|
+
.fdy-appbar--elevated{border-bottom:0;box-shadow:var(--shadow-2),var(--surface-inset);}
|
|
7
7
|
.fdy-appbar--dense{height:var(--space-12);}
|
|
8
8
|
.fdy-appbar--primary{background:var(--color-primary);color:var(--color-on-primary);border-bottom-color:transparent;}
|
|
9
9
|
.fdy-appbar__brand{display:inline-flex;align-items:center;gap:var(--space-2);font-family:var(--font-display);font-weight:var(--weight-bold);font-size:var(--text-lg);color:inherit;white-space:nowrap;}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* Freeday. Busy overlay */
|
|
2
|
+
/* The blocking "the app is working" state, for an operation the reader must not interrupt or
|
|
3
|
+
* double-submit. There is deliberately NO markup contract to hand-write: it is created by
|
|
4
|
+
* `Freeday.busy()` and removed by `Freeday.idle()`, because two of these on screen at once — with
|
|
5
|
+
* two different captions, from two components that each thought they owned it — is the exact bug
|
|
6
|
+
* this component exists to prevent. One owner, enforced by there being only one way in.
|
|
7
|
+
*
|
|
8
|
+
* It is not a dialog. Nothing here asks a question and nothing can be dismissed; interaction is
|
|
9
|
+
* removed with `inert` on everything else, not with a focus trap. */
|
|
10
|
+
|
|
11
|
+
/* `display` is gated on `.is-open` rather than on `:popover-open`, and that is load-bearing. The UA
|
|
12
|
+
* sheet hides a popover with `[popover]:not(:popover-open){display:none}`, but AUTHOR rules beat UA
|
|
13
|
+
* rules whatever the specificity, so a bare `.fdy-busy{display:flex}` would paint the overlay
|
|
14
|
+
* permanently the moment the element existed. Gating on a class instead also means the overlay
|
|
15
|
+
* still works where the Popover API does not: it falls back to a plain fixed layer at this
|
|
16
|
+
* z-index, which cannot clear an open <dialog> but does everything else. */
|
|
17
|
+
.fdy-busy{display:none;position:fixed;inset:0;width:100%;height:100%;max-width:none;max-height:none;margin:0;padding:0;border:0;overflow:hidden;background:var(--color-scrim);z-index:190;}
|
|
18
|
+
.fdy-busy.is-open{display:flex;align-items:center;justify-content:center;}
|
|
19
|
+
/* The popover's own backdrop would double the dim; this element paints its own. */
|
|
20
|
+
.fdy-busy::backdrop{background:transparent;}
|
|
21
|
+
|
|
22
|
+
/* The caption sits on a solid surface, not on the scrim. A scrim is semi-transparent over unknown
|
|
23
|
+
* content, so text on it has no computable contrast ratio and no gate could ever cover it. On
|
|
24
|
+
* --color-surface it is the same pair the contrast suite already checks.
|
|
25
|
+
*
|
|
26
|
+
* THIS IS ALSO WHY THIS PANEL DOES NOT JOIN THE GLASS AXIS, and the omission is a decision rather
|
|
27
|
+
* than an oversight. Every other raised panel reads --color-surface-raised and frosts under
|
|
28
|
+
* [data-style="glass"]; this one stays opaque. The glass gate asserts its ink against the panel
|
|
29
|
+
* composited over the PAGE GROUND (--color-surface-2), which is what sits behind a card or a modal.
|
|
30
|
+
* Behind this one is the scrim, over arbitrary content — the very case that has no computable
|
|
31
|
+
* ratio. A frosted busy panel would be the one surface in the kit whose caption nothing could
|
|
32
|
+
* verify, while it is up precisely because the app is doing something the reader must not
|
|
33
|
+
* interrupt. Left opaque on purpose; do not "fix" it for consistency. */
|
|
34
|
+
.fdy-busy__panel{display:flex;flex-direction:column;align-items:center;gap:var(--space-4);padding:var(--space-6) var(--space-8);border-radius:var(--radius-lg);background:var(--color-surface);box-shadow:var(--shadow-lift);max-width:min(20rem,calc(100vw - var(--space-8)));}
|
|
35
|
+
.fdy-busy__mark{display:flex;align-items:center;justify-content:center;min-height:2.5rem;}
|
|
36
|
+
.fdy-busy__caption{margin:0;font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text);text-align:center;}
|
|
37
|
+
|
|
38
|
+
/* Nothing travels under reduced motion. The spinner keeps turning (slowed by spinner.css): a
|
|
39
|
+
* loading indicator that stops moving reads as "finished", which is the opposite of the message. */
|
|
40
|
+
@media (prefers-reduced-motion:reduce){
|
|
41
|
+
.fdy-busy{transition:none;}
|
|
42
|
+
}
|
package/src/components/card.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Freeday. Card */
|
|
2
2
|
/* position:relative, containing block for out-of-flow card content (see base.css); it also gives
|
|
3
3
|
consumers the anchor a corner ribbon/badge needs. */
|
|
4
|
-
.fdy-card{position:relative;background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-lift);transition:box-shadow var(--dur-base) var(--ease-standard),transform var(--dur-base) var(--ease-standard);}
|
|
4
|
+
.fdy-card{position:relative;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-lift),var(--surface-inset);transition:box-shadow var(--dur-base) var(--ease-standard),transform var(--dur-base) var(--ease-standard);}
|
|
5
5
|
.fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
|
|
6
6
|
.fdy-card--interactive{cursor:pointer;}
|
|
7
7
|
.fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
|
|
@@ -60,9 +60,22 @@
|
|
|
60
60
|
grid track, `container-type:inline-size` on a standalone .fdy-stat would stop it sizing to its
|
|
61
61
|
own content. The unconditional rule above stays as the fallback wherever @container is missing. */
|
|
62
62
|
@supports (container-type:inline-size){
|
|
63
|
-
.fdy-stats>.fdy-stat{container-type:inline-size;}
|
|
64
|
-
.fdy-stats>.fdy-stat .fdy-stat__value{font-size:clamp(var(--text-xl),11cqw,var(--text-3xl));}
|
|
63
|
+
.fdy-stats:not(.fdy-stats--inline)>.fdy-stat{container-type:inline-size;}
|
|
64
|
+
.fdy-stats:not(.fdy-stats--inline)>.fdy-stat .fdy-stat__value{font-size:clamp(var(--text-xl),11cqw,var(--text-3xl));}
|
|
65
65
|
}
|
|
66
|
+
/* The price of that container, and the reason --inline opts out of it (#050). `container-type`
|
|
67
|
+
applies inline-size CONTAINMENT: the tile's min-content and max-content contributions are both
|
|
68
|
+
ZERO. So the 11rem floor above is not a taste choice, it is the only thing giving these tracks a
|
|
69
|
+
size, and re-tracking the grid on content (`auto`, `max-content`, `minmax(0,…)`) resolves every
|
|
70
|
+
track to 0. `justify-items` is the sharper edge: anything but the default `stretch` sizes the
|
|
71
|
+
tile to that zero contribution even when the track is correctly wide, and three labels paint on
|
|
72
|
+
top of each other. Measured from IDU_EMATE_ACCT_WEB's page header, where a hugging strip came out
|
|
73
|
+
40px wide, all of it gap.
|
|
74
|
+
--inline is the shape that header wanted: a strip that hugs its numbers and sits beside a page
|
|
75
|
+
title, instead of a full-width band whose 11rem floor collapses to one column at 390px. It cannot
|
|
76
|
+
have both, so it drops the container and keeps the flat --text-3xl the base rule already sets;
|
|
77
|
+
a track sized to its own content is never the narrow track the clamp exists for. */
|
|
78
|
+
.fdy-stats--inline{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:minmax(0,max-content);}
|
|
66
79
|
.fdy-stats--boxed{background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);gap:0;}
|
|
67
80
|
.fdy-stats--boxed .fdy-stat{padding:var(--space-5);border-right:var(--bw) solid var(--color-border-muted);}
|
|
68
81
|
.fdy-stats--boxed .fdy-stat:last-child{border-right:0;}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/* display is gated behind [open] so the native <dialog> stays hidden until opened.
|
|
5
5
|
* An unconditional display here would beat the UA `dialog:not([open]){display:none}`
|
|
6
6
|
* rule (author origin wins over user-agent) and leave the drawer visible on load. */
|
|
7
|
-
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;max-height:none;width:min(20rem,82vw);flex-direction:column;border:0;background:var(--color-surface);color:var(--color-text);box-shadow:var(--shadow-4);overflow:hidden;transform:translateX(-100%);transition:transform var(--dur-slow) var(--ease-standard),overlay var(--dur-slow) allow-discrete,display var(--dur-slow) allow-discrete;}
|
|
7
|
+
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;max-height:none;width:min(20rem,82vw);flex-direction:column;border:0;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-4),var(--surface-inset);overflow:hidden;transform:translateX(-100%);transition:transform var(--dur-slow) var(--ease-standard),overlay var(--dur-slow) allow-discrete,display var(--dur-slow) allow-discrete;}
|
|
8
8
|
.fdy-drawer[open]{display:flex;transform:none;}
|
|
9
9
|
.fdy-drawer--right{inset:0 0 0 auto;transform:translateX(100%);}
|
|
10
10
|
.fdy-drawer::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition:opacity var(--dur-slow) var(--ease-standard),overlay var(--dur-slow) allow-discrete,display var(--dur-slow) allow-discrete;}
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
/* Fields are direct children. Drop the standalone field's 22rem cap; default to the
|
|
14
14
|
* medium column width with no unbounded growth (that's what caused the raggedness). */
|
|
15
15
|
.fdy-filterbar>.fdy-field{max-width:none;flex:0 0 auto;width:12rem;}
|
|
16
|
-
/* Controls fill their field's column
|
|
17
|
-
|
|
16
|
+
/* Controls fill their field's column: released in input.css for every field, not just this one.
|
|
17
|
+
The rule used to live here, naming the input group and the combo only, which is why an
|
|
18
|
+
.fdy-autocomplete or an .fdy-cascade in a --w-2xl field still stopped 48px short of it (#051). */
|
|
18
19
|
/* Column-width rhythm, pick one per field so bars across the app line up. */
|
|
19
20
|
.fdy-filterbar>.fdy-field--w-sm{width:9rem;}
|
|
20
21
|
.fdy-filterbar>.fdy-field--w-lg{width:15rem;}
|
package/src/components/input.css
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/* Freeday. Field & Input */
|
|
2
2
|
.fdy-field{display:flex;flex-direction:column;gap:var(--space-2);max-width:22rem;}
|
|
3
|
+
/* Inside a labelled field, a control fills the field (#051). The 22rem cap those controls carry is
|
|
4
|
+
for one standing on its own, with no field to take a width from; #017 already released the date
|
|
5
|
+
and time pickers here for exactly this reason, and its comment claims a picker then fills its
|
|
6
|
+
field "like every other control". That was not true of these four: a `--full` row in a
|
|
7
|
+
.fdy-form-grid held a 352px combo under a 600px label, and a 26rem toolbar search field hid 64px
|
|
8
|
+
of dead space INSIDE itself, so an author who wrote `gap:var(--space-3)` measured 76px on screen.
|
|
9
|
+
The field's own max-width above is what governs, and it still caps a field nobody has widened, so
|
|
10
|
+
this changes nothing wherever the cap was already doing its job. */
|
|
11
|
+
.fdy-field>.fdy-input-group,.fdy-field>.fdy-combo,.fdy-field>.fdy-autocomplete,.fdy-field>.fdy-cascade{max-width:none;}
|
|
3
12
|
/* A grouped field is legitimately a <fieldset> (it needs a <legend>), e.g. a radio/checkbox group.
|
|
4
13
|
Neutralise the UA groove border / padding / min-inline-size so it lays out identically to the
|
|
5
14
|
<div> form (min-inline-size:min-content would otherwise block the filterbar column widths). */
|
package/src/components/menu.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Freeday. Menu (action popup) + Split button. Enhanced by freeday-menu.js. */
|
|
2
2
|
.fdy-menu-wrap{position:relative;display:inline-flex;}
|
|
3
|
-
.fdy-menu{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;min-width:11rem;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
|
|
3
|
+
.fdy-menu{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;min-width:11rem;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3),var(--surface-inset);}
|
|
4
4
|
.fdy-menu[hidden]{display:none;}
|
|
5
5
|
.fdy-menu--end{left:auto;right:0;}
|
|
6
6
|
.fdy-menu__item{display:flex;align-items:center;gap:var(--space-2);width:100%;padding:var(--space-2) var(--space-3);border:0;background:transparent;border-radius:var(--radius-sm);font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);text-align:left;cursor:pointer;white-space:nowrap;}
|
package/src/components/modal.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Freeday. Modal (native <dialog>) */
|
|
2
|
-
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - var(--space-16));padding:0;border:var(--bw) solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-surface);color:var(--color-text);box-shadow:var(--shadow-lift-hover);overflow:hidden;flex-direction:column;opacity:0;transform:translateY(8px) scale(.98);transition:opacity var(--dur-base) var(--ease-spring),transform var(--dur-base) var(--ease-spring),overlay var(--dur-base) allow-discrete,display var(--dur-base) allow-discrete;}
|
|
2
|
+
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - var(--space-16));padding:0;border:var(--bw) solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-lift-hover),var(--surface-inset);overflow:hidden;flex-direction:column;opacity:0;transform:translateY(8px) scale(.98);transition:opacity var(--dur-base) var(--ease-spring),transform var(--dur-base) var(--ease-spring),overlay var(--dur-base) allow-discrete,display var(--dur-base) allow-discrete;}
|
|
3
3
|
.fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
|
|
4
4
|
.fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
|
|
5
5
|
.fdy-modal--lg{width:min(48rem,calc(100vw - var(--space-8)));}
|
|
@@ -17,7 +17,18 @@
|
|
|
17
17
|
.fdy-step.is-active .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary-strong);}
|
|
18
18
|
.fdy-step.is-complete .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary);color:var(--color-on-primary);}
|
|
19
19
|
.fdy-step.is-error .fdy-step__marker{border-color:var(--color-danger);background:var(--color-danger-btn);color:var(--color-on-danger);}
|
|
20
|
+
/* The error badge. Colour alone cannot carry "this step has a problem" — the marker already turns
|
|
21
|
+
red and that is exactly the signal a colour-blind reader loses — so the badge holds a COUNT and
|
|
22
|
+
the step names it in text. Positioned against `.fdy-step` rather than `__btn`, because the
|
|
23
|
+
indicator-only variant has no `__btn` and the badge has to land in the same place in both.
|
|
24
|
+
It is a SIBLING of the marker, never a child: the enhancer rewrites `__marker.innerHTML` on
|
|
25
|
+
every render, so a badge inside it would be destroyed the first time the reader moved a step. */
|
|
26
|
+
.fdy-step__badge{position:absolute;top:0;left:50%;z-index:2;transform:translate(.45rem,-.3rem);min-width:1.05rem;height:1.05rem;padding:0 .25rem;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--radius-full);background:var(--color-danger-btn);color:var(--color-on-danger);font-size:var(--text-xs);font-weight:var(--weight-semibold);font-variant-numeric:tabular-nums;line-height:1;}
|
|
20
27
|
.fdy-step__label{font-size:var(--text-sm);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;}
|
|
28
|
+
.fdy-step.is-error .fdy-step__label{color:var(--color-danger-strong);}
|
|
29
|
+
/* While a guard is deciding, the whole control is spoken for: the nav buttons are disabled by the
|
|
30
|
+
enhancer, and this dims the header so a second click has nothing to aim at. */
|
|
31
|
+
.fdy-stepper[aria-busy="true"]{opacity:.72;}
|
|
21
32
|
.fdy-step.is-active .fdy-step__label{color:var(--color-text);font-weight:var(--weight-semibold);}
|
|
22
33
|
.fdy-step__btn:focus-visible{outline:none;}
|
|
23
34
|
.fdy-step__btn:focus-visible .fdy-step__marker{box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 30%,transparent);}
|
package/src/components/table.css
CHANGED
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
.fdy-table td{padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);color:var(--color-text);}
|
|
10
10
|
.fdy-table tbody tr:last-child td{border-bottom:0;}
|
|
11
11
|
.fdy-table tbody tr{transition:background-color var(--dur-fast) var(--ease-standard);}
|
|
12
|
+
/* Zebra rows. Sits BEFORE :hover and [aria-selected] on purpose: all three land on the same
|
|
13
|
+
specificity (0,2,2), so source order is what decides, and a stripe that won would make hover
|
|
14
|
+
invisible on every second row. The tint is HALF a surface step rather than --color-surface-2
|
|
15
|
+
itself, for the same reason: hover paints the full step, so the two must not be the same colour.
|
|
16
|
+
Exposed as a Tier-3 token through the var() FALLBACK, never as a declaration — a custom property
|
|
17
|
+
set here would outrank a host's own :root override. */
|
|
18
|
+
.fdy-table--striped tbody tr:nth-child(even){background:var(--fdy-table-stripe,color-mix(in srgb,var(--color-surface-2) 55%,var(--color-surface)));}
|
|
12
19
|
.fdy-table tbody tr:hover{background:var(--color-surface-2);}
|
|
13
20
|
/* Monospace data, ids, codes, IPs, timestamps. Alignment-neutral so it works in any cell
|
|
14
21
|
or inline (`<span class="fdy-mono">`), not just numeric columns. */
|
|
@@ -73,6 +80,12 @@
|
|
|
73
80
|
a hovered row should band unbroken across the freeze line, says so itself. */
|
|
74
81
|
.fdy-table--sticky tbody .fdy-table__freeze{z-index:1;}
|
|
75
82
|
.fdy-table--sticky tbody td.fdy-table__freeze{background:var(--color-surface);}
|
|
83
|
+
/* A frozen cell has to paint an OPAQUE background of its own (it scrolls over its neighbours), so
|
|
84
|
+
on a striped table it would cut a plain column straight through the zebra. Repaint it with the
|
|
85
|
+
stripe — and deliberately WITHOUT `.fdy-table--sticky` in the selector, which would push this to
|
|
86
|
+
(0,4,3) and beat the hover rule below. At (0,3,3) it ties, and the later rule wins, so a hovered
|
|
87
|
+
row still bands unbroken across the freeze line. */
|
|
88
|
+
.fdy-table--striped tbody tr:nth-child(even) td.fdy-table__freeze{background:var(--fdy-table-stripe,color-mix(in srgb,var(--color-surface-2) 55%,var(--color-surface)));}
|
|
76
89
|
.fdy-table--sticky tbody tr:hover td.fdy-table__freeze{background:var(--color-surface-2);}
|
|
77
90
|
/* A frozen header cell is stuck on both axes wherever the head is frozen too, so
|
|
78
91
|
it has to outrank both. */
|
package/tokens/tokens.json
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
"800": { "$value": "#1c398c" }, "900": { "$value": "#1c336e" },
|
|
8
8
|
"950": { "$value": "#131e42" }
|
|
9
9
|
},
|
|
10
|
+
"primary": { "_comment": { "$value": "Alias ramp. The primary semantics read THESE, never a brand ramp directly, so swapping a palette is eight declarations under [data-primary] instead of re-deriving every semantic token twice (once per theme). Only the eight shades the semantics actually consume are aliased." },
|
|
11
|
+
"50": { "$value": "{azure.50}" }, "100": { "$value": "{azure.100}" }, "300": { "$value": "{azure.300}" }, "400": { "$value": "{azure.400}" }, "500": { "$value": "{azure.500}" }, "600": { "$value": "{azure.600}" }, "700": { "$value": "{azure.700}" }, "800": { "$value": "{azure.800}" } },
|
|
10
12
|
"slate": {
|
|
11
13
|
"0": { "$value": "#ffffff" }, "50": { "$value": "#f7f8fa" },
|
|
12
14
|
"100": { "$value": "#eef0f4" }, "200": { "$value": "#e0e3ea" },
|
|
@@ -23,9 +25,47 @@
|
|
|
23
25
|
"800": { "$value": "#004d88" }, "900": { "$value": "#003a67" }
|
|
24
26
|
},
|
|
25
27
|
"red": { "100": { "$value": "#fee2e2" }, "300": { "$value": "#fca5a5" }, "400": { "$value": "#f87171" }, "500": { "$value": "#ef4444" }, "600": { "$value": "#dc2626" }, "700": { "$value": "#b91c1c" } },
|
|
26
|
-
"amber": { "100": { "$value": "#fef3c7" }, "400": { "$value": "#fbbf24" }, "500": { "$value": "#f59e0b" }, "600": { "$value": "#d97706" }, "700": { "$value": "#b45309" }, "800": { "$value": "#92400e" } },
|
|
27
|
-
"green": { "100": { "$value": "#dcfce7" }, "400": { "$value": "#4ade80" }, "500": { "$value": "#22c55e" }, "600": { "$value": "#16a34a" }, "700": { "$value": "#15803d" }, "800": { "$value": "#166534" } },
|
|
28
|
-
"blue": { "100": { "$value": "#dbeafe" }, "300": { "$value": "#93c5fd" }, "400": { "$value": "#60a5fa" }, "500": { "$value": "#3b82f6" }, "600": { "$value": "#2563eb" }, "700": { "$value": "#1d4ed8" } },
|
|
28
|
+
"amber": { "50": { "$value": "#fffbeb" }, "100": { "$value": "#fef3c7" }, "300": { "$value": "#fcd34d" }, "400": { "$value": "#fbbf24" }, "500": { "$value": "#f59e0b" }, "600": { "$value": "#d97706" }, "700": { "$value": "#b45309" }, "800": { "$value": "#92400e" } },
|
|
29
|
+
"green": { "50": { "$value": "#f0fdf4" }, "100": { "$value": "#dcfce7" }, "300": { "$value": "#86efac" }, "400": { "$value": "#4ade80" }, "500": { "$value": "#22c55e" }, "600": { "$value": "#16a34a" }, "700": { "$value": "#15803d" }, "800": { "$value": "#166534" } },
|
|
30
|
+
"blue": { "50": { "$value": "#eff6ff" }, "100": { "$value": "#dbeafe" }, "300": { "$value": "#93c5fd" }, "400": { "$value": "#60a5fa" }, "500": { "$value": "#3b82f6" }, "600": { "$value": "#2563eb" }, "700": { "$value": "#1d4ed8" }, "800": { "$value": "#1e40af" } },
|
|
31
|
+
|
|
32
|
+
"emerald": { "50": { "$value": "#ecfdf5" }, "100": { "$value": "#d1fae5" }, "300": { "$value": "#6ee7b7" }, "400": { "$value": "#34d399" }, "500": { "$value": "#10b981" }, "600": { "$value": "#059669" }, "700": { "$value": "#047857" }, "800": { "$value": "#065f46" } },
|
|
33
|
+
"lime": { "50": { "$value": "#f7fee7" }, "100": { "$value": "#ecfccb" }, "300": { "$value": "#bef264" }, "400": { "$value": "#a3e635" }, "500": { "$value": "#84cc16" }, "600": { "$value": "#65a30d" }, "700": { "$value": "#4d7c0f" }, "800": { "$value": "#3f6212" } },
|
|
34
|
+
"orange": { "50": { "$value": "#fff7ed" }, "100": { "$value": "#ffedd5" }, "300": { "$value": "#fdba74" }, "400": { "$value": "#fb923c" }, "500": { "$value": "#f97316" }, "600": { "$value": "#ea580c" }, "700": { "$value": "#c2410c" }, "800": { "$value": "#9a3412" } },
|
|
35
|
+
"yellow": { "900": { "$value": "#713f12" }, "50": { "$value": "#fefce8" }, "100": { "$value": "#fef9c3" }, "300": { "$value": "#fde047" }, "400": { "$value": "#facc15" }, "500": { "$value": "#eab308" }, "600": { "$value": "#ca8a04" }, "700": { "$value": "#a16207" }, "800": { "$value": "#854d0e" } },
|
|
36
|
+
"teal": { "50": { "$value": "#f0fdfa" }, "100": { "$value": "#ccfbf1" }, "300": { "$value": "#5eead4" }, "400": { "$value": "#2dd4bf" }, "500": { "$value": "#14b8a6" }, "600": { "$value": "#0d9488" }, "700": { "$value": "#0f766e" }, "800": { "$value": "#115e59" } },
|
|
37
|
+
"cyan": { "50": { "$value": "#ecfeff" }, "100": { "$value": "#cffafe" }, "300": { "$value": "#67e8f9" }, "400": { "$value": "#22d3ee" }, "500": { "$value": "#06b6d4" }, "600": { "$value": "#0891b2" }, "700": { "$value": "#0e7490" }, "800": { "$value": "#155e75" } },
|
|
38
|
+
"indigo": { "200": { "$value": "#c7d2fe" }, "50": { "$value": "#eef2ff" }, "100": { "$value": "#e0e7ff" }, "300": { "$value": "#a5b4fc" }, "400": { "$value": "#818cf8" }, "500": { "$value": "#6366f1" }, "600": { "$value": "#4f46e5" }, "700": { "$value": "#4338ca" }, "800": { "$value": "#3730a3" } },
|
|
39
|
+
"violet": { "200": { "$value": "#ddd6fe" }, "50": { "$value": "#f5f3ff" }, "100": { "$value": "#ede9fe" }, "300": { "$value": "#c4b5fd" }, "400": { "$value": "#a78bfa" }, "500": { "$value": "#8b5cf6" }, "600": { "$value": "#7c3aed" }, "700": { "$value": "#6d28d9" }, "800": { "$value": "#5b21b6" } },
|
|
40
|
+
"purple": { "50": { "$value": "#faf5ff" }, "100": { "$value": "#f3e8ff" }, "300": { "$value": "#d8b4fe" }, "400": { "$value": "#c084fc" }, "500": { "$value": "#a855f7" }, "600": { "$value": "#9333ea" }, "700": { "$value": "#7e22ce" }, "800": { "$value": "#6b21a8" } },
|
|
41
|
+
"fuchsia": { "50": { "$value": "#fdf4ff" }, "100": { "$value": "#fae8ff" }, "300": { "$value": "#f0abfc" }, "400": { "$value": "#e879f9" }, "500": { "$value": "#d946ef" }, "600": { "$value": "#c026d3" }, "700": { "$value": "#a21caf" }, "800": { "$value": "#86198f" } },
|
|
42
|
+
"pink": { "50": { "$value": "#fdf2f8" }, "100": { "$value": "#fce7f3" }, "300": { "$value": "#f9a8d4" }, "400": { "$value": "#f472b6" }, "500": { "$value": "#ec4899" }, "600": { "$value": "#db2777" }, "700": { "$value": "#be185d" }, "800": { "$value": "#9d174d" } },
|
|
43
|
+
"rose": { "50": { "$value": "#fff1f2" }, "100": { "$value": "#ffe4e6" }, "300": { "$value": "#fda4af" }, "400": { "$value": "#fb7185" }, "500": { "$value": "#f43f5e" }, "600": { "$value": "#e11d48" }, "700": { "$value": "#be123c" }, "800": { "$value": "#9f1239" } },
|
|
44
|
+
|
|
45
|
+
"$primaries": {
|
|
46
|
+
"_comment": "The primary palette axis. Each entry redefines the eight-shade `primary` ALIAS ramp under [data-primary=\"name\"], so the semantic mapping (which shade is the fill, which is hover, which is the soft wash) is written once and never per palette. `on`/`onDark` are the label ink on a primary fill; both are MEASURED, and every pair is asserted by the contrast suite. `darkShift` pulls the dark aliases one step lighter, for a hue whose 500 sits in the luminance band where neither white nor ink reaches 4.5:1.",
|
|
47
|
+
"azure": { "ramp": "azure", "on": "{slate.0}", "onDark": "{slate.0}" },
|
|
48
|
+
"sky": { "ramp": "sky", "on": "{slate.0}", "onDark": "{slate.950}" },
|
|
49
|
+
"blue": { "ramp": "blue", "on": "{slate.0}", "onDark": "{slate.950}" },
|
|
50
|
+
"indigo": { "ramp": "indigo", "on": "{slate.0}", "onDark": "{slate.950}", "darkShift": true },
|
|
51
|
+
"violet": { "ramp": "violet", "on": "{slate.0}", "onDark": "{slate.950}", "darkShift": true },
|
|
52
|
+
"purple": { "ramp": "purple", "on": "{slate.0}", "onDark": "{slate.950}" },
|
|
53
|
+
"fuchsia": { "ramp": "fuchsia", "on": "{slate.0}", "onDark": "{slate.950}" },
|
|
54
|
+
"pink": { "ramp": "pink", "on": "{slate.0}", "onDark": "{slate.950}" },
|
|
55
|
+
"rose": { "ramp": "rose", "on": "{slate.0}", "onDark": "{slate.950}" },
|
|
56
|
+
"orange": { "ramp": "orange", "on": "{slate.950}", "onDark": "{slate.950}" },
|
|
57
|
+
"amber": { "ramp": "amber", "on": "{slate.950}", "onDark": "{slate.950}" },
|
|
58
|
+
"yellow": { "on": "{slate.0}", "onDark": "{slate.950}",
|
|
59
|
+
"light": { "50": "{yellow.50}", "100": "{yellow.100}", "300": "{yellow.300}", "400": "{yellow.400}", "500": "{yellow.500}", "600": "{yellow.700}", "700": "{yellow.800}", "800": "{yellow.900}" } },
|
|
60
|
+
"lime": { "ramp": "lime", "on": "{slate.950}", "onDark": "{slate.950}" },
|
|
61
|
+
"green": { "ramp": "green", "on": "{slate.950}", "onDark": "{slate.950}" },
|
|
62
|
+
"emerald": { "ramp": "emerald", "on": "{slate.950}", "onDark": "{slate.950}" },
|
|
63
|
+
"teal": { "ramp": "teal", "on": "{slate.950}", "onDark": "{slate.950}" },
|
|
64
|
+
"cyan": { "ramp": "cyan", "on": "{slate.950}", "onDark": "{slate.950}" },
|
|
65
|
+
"noir": { "on": "{slate.0}", "onDark": "{slate.950}",
|
|
66
|
+
"light": { "50": "{slate.100}", "100": "{slate.200}", "300": "{slate.500}", "400": "{slate.700}", "500": "{slate.800}", "600": "{slate.900}", "700": "{slate.800}", "800": "{slate.950}" },
|
|
67
|
+
"dark": { "50": "{slate.800}", "100": "{slate.700}", "300": "{slate.300}", "400": "{slate.200}", "500": "{slate.100}", "600": "{slate.50}", "700": "{slate.200}", "800": "{slate.300}" } }
|
|
68
|
+
},
|
|
29
69
|
|
|
30
70
|
"font": {
|
|
31
71
|
"display": { "$value": "'Sora', ui-sans-serif, system-ui, sans-serif" },
|
|
@@ -61,12 +101,12 @@
|
|
|
61
101
|
"bw": { "$value": "1px" },
|
|
62
102
|
|
|
63
103
|
"color": {
|
|
64
|
-
"primary": { "$value": "{
|
|
65
|
-
"primary-hover": { "$value": "{
|
|
66
|
-
"primary-active": { "$value": "{
|
|
104
|
+
"primary": { "$value": "{primary.600}", "$dark": "{primary.500}" },
|
|
105
|
+
"primary-hover": { "$value": "{primary.700}", "$dark": "{primary.400}" },
|
|
106
|
+
"primary-active": { "$value": "{primary.800}", "$dark": "{primary.300}" },
|
|
67
107
|
"on-primary": { "$value": "{slate.0}", "$dark": "{slate.0}" },
|
|
68
|
-
"primary-soft": { "$value": "{
|
|
69
|
-
"primary-border": { "$value": "{
|
|
108
|
+
"primary-soft": { "$value": "{primary.50}", "$dark": "color-mix(in srgb, {primary.600} 20%, transparent)" },
|
|
109
|
+
"primary-border": { "$value": "{primary.100}", "$dark": "color-mix(in srgb, {primary.400} 28%, transparent)" },
|
|
70
110
|
|
|
71
111
|
"accent": { "$value": "{sky.600}", "$dark": "{sky.400}" },
|
|
72
112
|
"accent-hover": { "$value": "{sky.700}", "$dark": "{sky.300}" },
|
|
@@ -77,6 +117,8 @@
|
|
|
77
117
|
"surface": { "$value": "{slate.0}", "$dark": "{slate.950}" },
|
|
78
118
|
"surface-2": { "$value": "{slate.50}", "$dark": "{slate.900}" },
|
|
79
119
|
"surface-3": { "$value": "{slate.100}", "$dark": "{slate.800}" },
|
|
120
|
+
"surface-raised": { "$comment": "How transparent glass may be is decided by the CONTRAST GATE, not by taste: below .79 (light) / .88 (dark) the muted ink and primary-strong stop clearing 4.5:1 once the panel is composited over an arbitrary ground. Measured, and asserted in test/contrast.test.mjs. The frost people actually see comes from --surface-filter's blur, not from the alpha.", "$value": "{color.surface}", "$dark": "{color.surface}", "$glass": "rgba(255,255,255,.82)", "$glassDark": "rgba(23,27,38,.90)" },
|
|
121
|
+
"scrim": { "$value": "rgba(10,12,25,.5)", "$comment": "Overlay dim. Deliberately the SAME in both themes: a scrim darkens whatever sits behind it, so it does not flip with the palette. The literal matches the modal/drawer ::backdrop, which states it literally because ::backdrop does not inherit a custom property in every engine." },
|
|
80
122
|
|
|
81
123
|
"text": { "$value": "{slate.900}", "$dark": "{slate.100}" },
|
|
82
124
|
"text-muted": { "$value": "{slate.600}", "$dark": "{slate.400}" },
|
|
@@ -98,7 +140,7 @@
|
|
|
98
140
|
"danger": { "$value": "{red.600}", "$dark": "{red.400}" }, "danger-soft": { "$value": "{red.100}", "$dark": "rgba(248,113,113,.18)" },
|
|
99
141
|
"info": { "$value": "{blue.600}", "$dark": "{blue.400}" }, "info-soft": { "$value": "{blue.100}", "$dark": "rgba(96,165,250,.18)" },
|
|
100
142
|
|
|
101
|
-
"primary-strong": { "$value": "{
|
|
143
|
+
"primary-strong": { "$value": "{primary.700}", "$dark": "{primary.300}" },
|
|
102
144
|
"success-strong": { "$value": "{green.800}", "$dark": "{green.400}" },
|
|
103
145
|
"warning-strong": { "$value": "{amber.800}", "$dark": "{amber.400}" },
|
|
104
146
|
"danger-strong": { "$value": "{red.700}", "$dark": "{red.300}" },
|
|
@@ -130,6 +172,8 @@
|
|
|
130
172
|
"8": { "$value": "{chart.8}" }
|
|
131
173
|
},
|
|
132
174
|
|
|
133
|
-
"
|
|
175
|
+
"surface-filter": { "$value": "none", "$glass": "blur(14px) saturate(150%)" },
|
|
176
|
+
"surface-inset": { "$comment": "No-op is a transparent zero-size shadow, NOT `none`: this token is appended to box-shadow lists that already have entries, and `box-shadow: 0 1px 2px rgba(0,0,0,.1), none` is invalid CSS — the whole declaration would be dropped, taking the real shadow with it.", "$value": "inset 0 0 0 0 transparent", "$glass": "inset 0 1px 0 rgba(255,255,255,.55)", "$glassDark": "inset 0 1px 0 rgba(255,255,255,.09)" },
|
|
177
|
+
"focus-ring": { "$value": "{primary.600}", "$dark": "{primary.400}" },
|
|
134
178
|
"focus-ring-width": { "$value": "2px" }
|
|
135
179
|
}
|