@waveinno/ui 0.0.0-stage → 0.1.1
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 +39 -0
- package/LICENSE +5 -0
- package/README.md +64 -2
- package/dist/brand/waveinno-logo.svg +1 -0
- package/dist/components/brand/index.d.ts +3 -0
- package/dist/components/brand/index.js +1 -0
- package/dist/components/brand/logo.d.ts +89 -0
- package/dist/components/brand/logo.js +1 -0
- package/dist/components/brand/waveinno-logo.d.ts +52 -0
- package/dist/components/brand/waveinno-logo.js +1 -0
- package/dist/components/charts/bar-chart.d.ts +20 -0
- package/dist/components/charts/bar-chart.js +1 -0
- package/dist/components/charts/bar-list.d.ts +27 -0
- package/dist/components/charts/bar-list.js +1 -0
- package/dist/components/charts/chart-card.d.ts +32 -0
- package/dist/components/charts/chart-card.js +1 -0
- package/dist/components/charts/donut-chart.d.ts +29 -0
- package/dist/components/charts/donut-chart.js +1 -0
- package/dist/components/charts/gauge-chart.d.ts +21 -0
- package/dist/components/charts/gauge-chart.js +1 -0
- package/dist/components/charts/heatmap.d.ts +23 -0
- package/dist/components/charts/heatmap.js +1 -0
- package/dist/components/charts/index.d.ts +10 -0
- package/dist/components/charts/index.js +1 -0
- package/dist/components/charts/line-chart.d.ts +24 -0
- package/dist/components/charts/line-chart.js +1 -0
- package/dist/components/charts/palette.d.ts +39 -0
- package/dist/components/charts/palette.js +1 -0
- package/dist/components/charts/trend-chart.d.ts +22 -0
- package/dist/components/charts/trend-chart.js +1 -0
- package/dist/components/patterns/activity-feed.d.ts +28 -0
- package/dist/components/patterns/activity-feed.js +1 -0
- package/dist/components/patterns/data-table-cells.d.ts +39 -0
- package/dist/components/patterns/data-table-cells.js +1 -0
- package/dist/components/patterns/data-table-edit.d.ts +56 -0
- package/dist/components/patterns/data-table-edit.js +1 -0
- package/dist/components/patterns/data-table-filters.d.ts +84 -0
- package/dist/components/patterns/data-table-filters.js +1 -0
- package/dist/components/patterns/data-table.d.ts +76 -0
- package/dist/components/patterns/data-table.js +2 -0
- package/dist/components/patterns/index.d.ts +16 -0
- package/dist/components/patterns/index.js +1 -0
- package/dist/components/patterns/kanban.d.ts +28 -0
- package/dist/components/patterns/kanban.js +1 -0
- package/dist/components/patterns/kpi-card.d.ts +22 -0
- package/dist/components/patterns/kpi-card.js +1 -0
- package/dist/components/patterns/money.d.ts +28 -0
- package/dist/components/patterns/money.js +1 -0
- package/dist/components/patterns/page-header.d.ts +38 -0
- package/dist/components/patterns/page-header.js +1 -0
- package/dist/components/patterns/page-hero.d.ts +50 -0
- package/dist/components/patterns/page-hero.js +1 -0
- package/dist/components/patterns/page-shell.d.ts +27 -0
- package/dist/components/patterns/page-shell.js +1 -0
- package/dist/components/patterns/stat-card.d.ts +56 -0
- package/dist/components/patterns/stat-card.js +1 -0
- package/dist/components/patterns/states.d.ts +27 -0
- package/dist/components/patterns/states.js +1 -0
- package/dist/components/patterns/timeline.d.ts +100 -0
- package/dist/components/patterns/timeline.js +1 -0
- package/dist/components/ui/avatar.d.ts +37 -0
- package/dist/components/ui/avatar.js +1 -0
- package/dist/components/ui/badge.d.ts +41 -0
- package/dist/components/ui/badge.js +1 -0
- package/dist/components/ui/button.d.ts +25 -0
- package/dist/components/ui/button.js +1 -0
- package/dist/components/ui/calendar.d.ts +42 -0
- package/dist/components/ui/calendar.js +1 -0
- package/dist/components/ui/card.d.ts +11 -0
- package/dist/components/ui/card.js +1 -0
- package/dist/components/ui/checkbox.d.ts +3 -0
- package/dist/components/ui/checkbox.js +1 -0
- package/dist/components/ui/combobox.d.ts +47 -0
- package/dist/components/ui/combobox.js +1 -0
- package/dist/components/ui/confirm-dialog.d.ts +34 -0
- package/dist/components/ui/confirm-dialog.js +1 -0
- package/dist/components/ui/date-picker.d.ts +80 -0
- package/dist/components/ui/date-picker.js +1 -0
- package/dist/components/ui/dialog.d.ts +54 -0
- package/dist/components/ui/dialog.js +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +44 -0
- package/dist/components/ui/dropdown-menu.js +1 -0
- package/dist/components/ui/form-field.d.ts +27 -0
- package/dist/components/ui/form-field.js +1 -0
- package/dist/components/ui/index.d.ts +26 -0
- package/dist/components/ui/index.js +1 -0
- package/dist/components/ui/input.d.ts +21 -0
- package/dist/components/ui/input.js +1 -0
- package/dist/components/ui/label.d.ts +5 -0
- package/dist/components/ui/label.js +1 -0
- package/dist/components/ui/popover.d.ts +22 -0
- package/dist/components/ui/popover.js +1 -0
- package/dist/components/ui/progress.d.ts +43 -0
- package/dist/components/ui/progress.js +1 -0
- package/dist/components/ui/segmented.d.ts +42 -0
- package/dist/components/ui/segmented.js +1 -0
- package/dist/components/ui/select.d.ts +10 -0
- package/dist/components/ui/select.js +1 -0
- package/dist/components/ui/separator.d.ts +3 -0
- package/dist/components/ui/separator.js +1 -0
- package/dist/components/ui/sheet.d.ts +19 -0
- package/dist/components/ui/sheet.js +1 -0
- package/dist/components/ui/skeleton.d.ts +10 -0
- package/dist/components/ui/skeleton.js +1 -0
- package/dist/components/ui/switch.d.ts +3 -0
- package/dist/components/ui/switch.js +1 -0
- package/dist/components/ui/tabs.d.ts +19 -0
- package/dist/components/ui/tabs.js +1 -0
- package/dist/components/ui/time-picker.d.ts +52 -0
- package/dist/components/ui/time-picker.js +1 -0
- package/dist/components/ui/tooltip.d.ts +13 -0
- package/dist/components/ui/tooltip.js +1 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/use-hotkey.d.ts +16 -0
- package/dist/hooks/use-hotkey.js +1 -0
- package/dist/hooks/use-media-query.d.ts +4 -0
- package/dist/hooks/use-media-query.js +1 -0
- package/dist/hooks/use-mounted.d.ts +2 -0
- package/dist/hooks/use-mounted.js +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +1 -0
- package/dist/lib/boot-script.d.ts +15 -0
- package/dist/lib/boot-script.js +12 -0
- package/dist/lib/currency.d.ts +55 -0
- package/dist/lib/currency.js +1 -0
- package/dist/lib/format.d.ts +28 -0
- package/dist/lib/format.js +1 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/utils.d.ts +7 -0
- package/dist/lib/utils.js +1 -0
- package/dist/shell/app-shell.d.ts +18 -0
- package/dist/shell/app-shell.js +1 -0
- package/dist/shell/app-switcher.d.ts +9 -0
- package/dist/shell/app-switcher.js +1 -0
- package/dist/shell/appearance-controls.d.ts +13 -0
- package/dist/shell/appearance-controls.js +1 -0
- package/dist/shell/command-palette.d.ts +9 -0
- package/dist/shell/command-palette.js +1 -0
- package/dist/shell/config.d.ts +66 -0
- package/dist/shell/config.js +1 -0
- package/dist/shell/icon-rail.d.ts +2 -0
- package/dist/shell/icon-rail.js +1 -0
- package/dist/shell/index.d.ts +7 -0
- package/dist/shell/index.js +1 -0
- package/dist/shell/mobile-nav.d.ts +7 -0
- package/dist/shell/mobile-nav.js +1 -0
- package/dist/shell/module-sidebar.d.ts +9 -0
- package/dist/shell/module-sidebar.js +1 -0
- package/dist/shell/notifications.d.ts +5 -0
- package/dist/shell/notifications.js +1 -0
- package/dist/shell/placeholder-screen.d.ts +11 -0
- package/dist/shell/placeholder-screen.js +1 -0
- package/dist/shell/record-drawer.d.ts +32 -0
- package/dist/shell/record-drawer.js +1 -0
- package/dist/shell/registry.d.ts +24 -0
- package/dist/shell/registry.js +1 -0
- package/dist/shell/topbar.d.ts +9 -0
- package/dist/shell/topbar.js +1 -0
- package/dist/shell/use-active.d.ts +21 -0
- package/dist/shell/use-active.js +1 -0
- package/dist/shell/user-menu.d.ts +5 -0
- package/dist/shell/user-menu.js +1 -0
- package/dist/store/app-store.d.ts +10 -0
- package/dist/store/app-store.js +1 -0
- package/dist/store/drawer-store.d.ts +22 -0
- package/dist/store/drawer-store.js +1 -0
- package/dist/store/index.d.ts +5 -0
- package/dist/store/index.js +1 -0
- package/dist/store/settings-store.d.ts +22 -0
- package/dist/store/settings-store.js +1 -0
- package/dist/store/ui-store.d.ts +28 -0
- package/dist/store/ui-store.js +1 -0
- package/dist/styles.css +658 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.js +0 -0
- package/package.json +98 -4
package/dist/styles.css
ADDED
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
|
|
2
|
+
/* @waveinno/ui — import after tailwindcss:
|
|
3
|
+
@import "tailwindcss";
|
|
4
|
+
@import "@waveinno/ui/styles.css";
|
|
5
|
+
@source "<path to node_modules>/@waveinno/ui/src";
|
|
6
|
+
The consumer owns the Tailwind entry so it controls @source and plugins. */
|
|
7
|
+
|
|
8
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
9
|
+
WAVEINNO DESIGN TOKENS
|
|
10
|
+
Semantic slots only. Dark mode redefines the slots, never the components.
|
|
11
|
+
Ported verbatim from the waveinnoTempaltes master template (docs/DESIGN-SYSTEM.md §4).
|
|
12
|
+
══════════════════════════════════════════════════════════════════════════ */
|
|
13
|
+
|
|
14
|
+
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
|
|
15
|
+
|
|
16
|
+
:root {
|
|
17
|
+
/* ── surfaces ── */
|
|
18
|
+
--canvas: #ffffff;
|
|
19
|
+
--surface: #f8f9fc;
|
|
20
|
+
--raised: #ffffff;
|
|
21
|
+
--hover: #f1f3f9;
|
|
22
|
+
--selected: #eef2ff;
|
|
23
|
+
|
|
24
|
+
/* ── lines ── */
|
|
25
|
+
--border: #e2e5f0;
|
|
26
|
+
--border2: #cbd0e2;
|
|
27
|
+
|
|
28
|
+
/* ── ink ── */
|
|
29
|
+
--text: #1a113e;
|
|
30
|
+
--body: #3b3759;
|
|
31
|
+
--muted: #6b7396;
|
|
32
|
+
--placeholder: #9aa1bd;
|
|
33
|
+
|
|
34
|
+
/* ── brand ── */
|
|
35
|
+
--primary: #4f46e5;
|
|
36
|
+
--primary-hover: #4338ca;
|
|
37
|
+
--primary-subtle: #eef2ff;
|
|
38
|
+
--primary-muted: #c7d2fe;
|
|
39
|
+
--accent: #7c3aed;
|
|
40
|
+
|
|
41
|
+
/* ── intent ── */
|
|
42
|
+
--success: #009767;
|
|
43
|
+
--success-subtle: #d0fae5;
|
|
44
|
+
--warning: #b45309;
|
|
45
|
+
--warning-subtle: #fef3c7;
|
|
46
|
+
--danger: #dc2626;
|
|
47
|
+
--danger-subtle: #fee2e2;
|
|
48
|
+
--info: #4f46e5;
|
|
49
|
+
--info-subtle: #eef2ff;
|
|
50
|
+
|
|
51
|
+
/* ── elevation — two levels only ── */
|
|
52
|
+
--elev2: 0 4px 12px rgba(5, 8, 22, 0.08);
|
|
53
|
+
--elev3: 0 16px 48px rgba(5, 8, 22, 0.16);
|
|
54
|
+
--lit: 0 0 #0000;
|
|
55
|
+
|
|
56
|
+
/* ── radius ── */
|
|
57
|
+
--r-ctl: 8px;
|
|
58
|
+
--r-card: 10px;
|
|
59
|
+
--r-dlg: 14px;
|
|
60
|
+
|
|
61
|
+
/* ── shell geometry ── */
|
|
62
|
+
--rail: 72px;
|
|
63
|
+
--panel: 240px;
|
|
64
|
+
--topbar: 56px;
|
|
65
|
+
/* 480 could not fit six tabs or an email address without collision.
|
|
66
|
+
A record drawer is a reading surface, not a sidebar. */
|
|
67
|
+
--drawer: 560px;
|
|
68
|
+
|
|
69
|
+
/* ── density (default) ── */
|
|
70
|
+
--row-h: 40px;
|
|
71
|
+
--cell-py: 8px;
|
|
72
|
+
--cell-px: 12px;
|
|
73
|
+
--ctl-h: 36px;
|
|
74
|
+
--panel-item: 38px;
|
|
75
|
+
|
|
76
|
+
/* ── type families — §4.2 ── */
|
|
77
|
+
--font-inter: "Inter";
|
|
78
|
+
--font-jakarta: "Plus Jakarta Sans";
|
|
79
|
+
--font-jetbrains: "JetBrains Mono";
|
|
80
|
+
|
|
81
|
+
/* ── motion ── */
|
|
82
|
+
--dur: 220ms;
|
|
83
|
+
--ease: cubic-bezier(0.32, 0.72, 0, 1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
[data-theme="dark"] {
|
|
87
|
+
/* Four distinguishable steps, not two.
|
|
88
|
+
The first dark theme had --hover and --raised at the same value, so a
|
|
89
|
+
menu never looked lifted off the card it sat on and every surface read as
|
|
90
|
+
one flat sheet of navy. Each step below is a visible move. */
|
|
91
|
+
--canvas: #050816;
|
|
92
|
+
--surface: #0b1020;
|
|
93
|
+
--hover: #151b30;
|
|
94
|
+
--raised: #171d33;
|
|
95
|
+
--selected: rgba(99, 102, 241, 0.16);
|
|
96
|
+
|
|
97
|
+
--border: rgba(255, 255, 255, 0.09);
|
|
98
|
+
--border2: rgba(255, 255, 255, 0.17);
|
|
99
|
+
|
|
100
|
+
--text: #f5f6fa;
|
|
101
|
+
--body: #a3abc9;
|
|
102
|
+
/* Was #6b7396 — 3.9:1 on the surface, which fails AA for the 11px labels it
|
|
103
|
+
is used on. This measures 5.7:1. */
|
|
104
|
+
--muted: #858ead;
|
|
105
|
+
--placeholder: #4a5273;
|
|
106
|
+
|
|
107
|
+
/* Indigo at #4f46e5 sits too close to a near-black canvas to carry a button.
|
|
108
|
+
Dark gets its own steps rather than the light ones reused. */
|
|
109
|
+
--primary: #6366f1;
|
|
110
|
+
--primary-hover: #818cf8;
|
|
111
|
+
--primary-subtle: rgba(99, 102, 241, 0.16);
|
|
112
|
+
--primary-muted: rgba(165, 180, 252, 0.38);
|
|
113
|
+
--accent: #a78bfa;
|
|
114
|
+
|
|
115
|
+
--success: #00bb7f;
|
|
116
|
+
--success-subtle: rgba(0, 187, 127, 0.14);
|
|
117
|
+
--warning: #f59e0b;
|
|
118
|
+
--warning-subtle: rgba(245, 158, 11, 0.14);
|
|
119
|
+
--danger: #ef4444;
|
|
120
|
+
--danger-subtle: rgba(239, 68, 68, 0.14);
|
|
121
|
+
--info-subtle: rgba(79, 70, 229, 0.14);
|
|
122
|
+
|
|
123
|
+
--elev2: 0 4px 12px rgba(0, 0, 0, 0.45);
|
|
124
|
+
--elev3: 0 18px 52px rgba(0, 0, 0, 0.66);
|
|
125
|
+
|
|
126
|
+
/* The single detail that separates a flat dark theme from an expensive one:
|
|
127
|
+
a one-pixel highlight along the top edge, as though the surface were lit
|
|
128
|
+
from above. Absent in light, where real shadow does the same job. */
|
|
129
|
+
--lit: inset 0 1px 0 0 rgba(255, 255, 255, 0.055);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
[data-density="compact"] {
|
|
133
|
+
--row-h: 32px;
|
|
134
|
+
--cell-py: 6px;
|
|
135
|
+
--cell-px: 10px;
|
|
136
|
+
--ctl-h: 32px;
|
|
137
|
+
--panel-item: 34px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
[data-density="comfortable"] {
|
|
141
|
+
--row-h: 48px;
|
|
142
|
+
--cell-py: 12px;
|
|
143
|
+
--cell-px: 16px;
|
|
144
|
+
--ctl-h: 44px;
|
|
145
|
+
--panel-item: 42px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
149
|
+
TAILWIND THEME BRIDGE
|
|
150
|
+
`inline` keeps utilities pointing at the live var(), so data-theme and
|
|
151
|
+
data-density switch at runtime with no rebuild and no class swap.
|
|
152
|
+
══════════════════════════════════════════════════════════════════════════ */
|
|
153
|
+
|
|
154
|
+
@theme inline {
|
|
155
|
+
--color-canvas: var(--canvas);
|
|
156
|
+
--color-surface: var(--surface);
|
|
157
|
+
--color-raised: var(--raised);
|
|
158
|
+
--color-hover: var(--hover);
|
|
159
|
+
--color-selected: var(--selected);
|
|
160
|
+
|
|
161
|
+
--color-border: var(--border);
|
|
162
|
+
--color-border2: var(--border2);
|
|
163
|
+
|
|
164
|
+
--color-text: var(--text);
|
|
165
|
+
--color-body: var(--body);
|
|
166
|
+
--color-muted: var(--muted);
|
|
167
|
+
--color-placeholder: var(--placeholder);
|
|
168
|
+
|
|
169
|
+
--color-primary: var(--primary);
|
|
170
|
+
--color-primary-hover: var(--primary-hover);
|
|
171
|
+
--color-primary-subtle: var(--primary-subtle);
|
|
172
|
+
--color-primary-muted: var(--primary-muted);
|
|
173
|
+
--color-accent: var(--accent);
|
|
174
|
+
|
|
175
|
+
--color-success: var(--success);
|
|
176
|
+
--color-success-subtle: var(--success-subtle);
|
|
177
|
+
--color-warning: var(--warning);
|
|
178
|
+
--color-warning-subtle: var(--warning-subtle);
|
|
179
|
+
--color-danger: var(--danger);
|
|
180
|
+
--color-danger-subtle: var(--danger-subtle);
|
|
181
|
+
--color-info: var(--info);
|
|
182
|
+
--color-info-subtle: var(--info-subtle);
|
|
183
|
+
|
|
184
|
+
--radius-ctl: var(--r-ctl);
|
|
185
|
+
--radius-card: var(--r-card);
|
|
186
|
+
--radius-dlg: var(--r-dlg);
|
|
187
|
+
|
|
188
|
+
--shadow-elev2: var(--elev2);
|
|
189
|
+
--shadow-elev3: var(--elev3);
|
|
190
|
+
|
|
191
|
+
--spacing-rail: var(--rail);
|
|
192
|
+
--spacing-panel: var(--panel);
|
|
193
|
+
--spacing-topbar: var(--topbar);
|
|
194
|
+
--spacing-drawer: var(--drawer);
|
|
195
|
+
--spacing-row: var(--row-h);
|
|
196
|
+
--spacing-ctl: var(--ctl-h);
|
|
197
|
+
--spacing-item: var(--panel-item);
|
|
198
|
+
--spacing-cellx: var(--cell-px);
|
|
199
|
+
--spacing-celly: var(--cell-py);
|
|
200
|
+
|
|
201
|
+
--font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
|
|
202
|
+
--font-display: var(--font-jakarta), var(--font-inter), sans-serif;
|
|
203
|
+
--font-mono: var(--font-jetbrains), ui-monospace, monospace;
|
|
204
|
+
|
|
205
|
+
--ease-wave: cubic-bezier(0.32, 0.72, 0, 1);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
209
|
+
BASE
|
|
210
|
+
══════════════════════════════════════════════════════════════════════════ */
|
|
211
|
+
|
|
212
|
+
@layer base {
|
|
213
|
+
*,
|
|
214
|
+
::after,
|
|
215
|
+
::before {
|
|
216
|
+
border-color: var(--border);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
html,
|
|
220
|
+
body {
|
|
221
|
+
height: 100%;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* Inside the app shell the page is exactly one viewport tall and <main> is the
|
|
225
|
+
only scroller; without this the page itself scrolls and the rail slides out of
|
|
226
|
+
view. Pages outside the shell (sign-in, shared reports, view-as) scroll normally. */
|
|
227
|
+
body:has(.shell-grid) {
|
|
228
|
+
overflow: hidden;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
body {
|
|
232
|
+
background: var(--canvas);
|
|
233
|
+
color: var(--body);
|
|
234
|
+
font-family: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
|
|
235
|
+
font-size: 14px;
|
|
236
|
+
line-height: 20px;
|
|
237
|
+
-webkit-font-smoothing: antialiased;
|
|
238
|
+
text-rendering: optimizeLegibility;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
:focus-visible {
|
|
242
|
+
outline: 2px solid var(--primary);
|
|
243
|
+
outline-offset: 2px;
|
|
244
|
+
border-radius: 3px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
::selection {
|
|
248
|
+
background: var(--primary-subtle);
|
|
249
|
+
color: var(--text);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Scrollbars read as part of the chrome, not the OS. */
|
|
253
|
+
* {
|
|
254
|
+
scrollbar-width: thin;
|
|
255
|
+
scrollbar-color: var(--border2) transparent;
|
|
256
|
+
}
|
|
257
|
+
::-webkit-scrollbar {
|
|
258
|
+
width: 10px;
|
|
259
|
+
height: 10px;
|
|
260
|
+
}
|
|
261
|
+
::-webkit-scrollbar-track {
|
|
262
|
+
background: transparent;
|
|
263
|
+
}
|
|
264
|
+
::-webkit-scrollbar-thumb {
|
|
265
|
+
background: var(--border2);
|
|
266
|
+
border: 3px solid transparent;
|
|
267
|
+
background-clip: content-box;
|
|
268
|
+
border-radius: 999px;
|
|
269
|
+
}
|
|
270
|
+
::-webkit-scrollbar-thumb:hover {
|
|
271
|
+
background: var(--muted);
|
|
272
|
+
border: 3px solid transparent;
|
|
273
|
+
background-clip: content-box;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
278
|
+
TYPOGRAPHIC PRIMITIVES — the prototype's .num / .disp / .mono / .ov / .cur
|
|
279
|
+
══════════════════════════════════════════════════════════════════════════ */
|
|
280
|
+
|
|
281
|
+
@layer components {
|
|
282
|
+
/* Tabular figures. Mandatory on every numeric cell — see §2.5. */
|
|
283
|
+
.num {
|
|
284
|
+
font-variant-numeric: tabular-nums lining-nums slashed-zero;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.disp {
|
|
288
|
+
font-family: var(--font-jakarta), var(--font-inter), sans-serif;
|
|
289
|
+
color: var(--text);
|
|
290
|
+
letter-spacing: -0.02em;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.mono {
|
|
294
|
+
font-family: var(--font-jetbrains), ui-monospace, monospace;
|
|
295
|
+
font-size: 13px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* Currency symbols recede so the magnitude leads. */
|
|
299
|
+
.cur {
|
|
300
|
+
color: var(--muted);
|
|
301
|
+
font-weight: 500;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.ov {
|
|
305
|
+
font-size: 11px;
|
|
306
|
+
line-height: 14px;
|
|
307
|
+
font-weight: 600;
|
|
308
|
+
letter-spacing: 0.06em;
|
|
309
|
+
text-transform: uppercase;
|
|
310
|
+
color: var(--muted);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
315
|
+
MOTION — one place to honour the accessibility contract
|
|
316
|
+
══════════════════════════════════════════════════════════════════════════ */
|
|
317
|
+
|
|
318
|
+
@media (prefers-reduced-motion: reduce) {
|
|
319
|
+
*,
|
|
320
|
+
::before,
|
|
321
|
+
::after {
|
|
322
|
+
animation-duration: 0.01ms !important;
|
|
323
|
+
animation-iteration-count: 1 !important;
|
|
324
|
+
transition-duration: 0.01ms !important;
|
|
325
|
+
scroll-behavior: auto !important;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
@keyframes wave-in {
|
|
330
|
+
from {
|
|
331
|
+
opacity: 0;
|
|
332
|
+
transform: translateY(4px);
|
|
333
|
+
}
|
|
334
|
+
to {
|
|
335
|
+
opacity: 1;
|
|
336
|
+
transform: none;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
@keyframes wave-shimmer {
|
|
341
|
+
100% {
|
|
342
|
+
transform: translateX(100%);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.anim-in {
|
|
347
|
+
animation: wave-in var(--dur) var(--ease) both;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* Skeletons sweep rather than pulse — reads as loading, not as broken. */
|
|
351
|
+
.shimmer {
|
|
352
|
+
position: relative;
|
|
353
|
+
overflow: hidden;
|
|
354
|
+
background: var(--hover);
|
|
355
|
+
}
|
|
356
|
+
.shimmer::after {
|
|
357
|
+
content: "";
|
|
358
|
+
position: absolute;
|
|
359
|
+
inset: 0;
|
|
360
|
+
transform: translateX(-100%);
|
|
361
|
+
background: linear-gradient(
|
|
362
|
+
90deg,
|
|
363
|
+
transparent,
|
|
364
|
+
color-mix(in srgb, var(--border2) 55%, transparent),
|
|
365
|
+
transparent
|
|
366
|
+
);
|
|
367
|
+
animation: wave-shimmer 1.6s infinite;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
371
|
+
SHELL GEOMETRY
|
|
372
|
+
Collapse is a token change, not a DOM change: --panel goes to 0 and the CSS
|
|
373
|
+
grid animates. The boot script can set the attribute before paint, so a
|
|
374
|
+
returning user never sees the panel flash open and then shut.
|
|
375
|
+
══════════════════════════════════════════════════════════════════════════ */
|
|
376
|
+
|
|
377
|
+
[data-panel-collapsed] {
|
|
378
|
+
--panel: 0px;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
[data-panel-collapsed] [data-shell-panel] {
|
|
382
|
+
visibility: hidden;
|
|
383
|
+
pointer-events: none;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.shell-grid {
|
|
387
|
+
display: grid;
|
|
388
|
+
grid-template-columns: var(--rail) var(--panel) minmax(0, 1fr);
|
|
389
|
+
grid-template-rows: var(--topbar) minmax(0, 1fr);
|
|
390
|
+
height: 100dvh;
|
|
391
|
+
transition: grid-template-columns 150ms var(--ease);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
@media (max-width: 1023.98px) {
|
|
395
|
+
.shell-grid {
|
|
396
|
+
grid-template-columns: 0 0 minmax(0, 1fr);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* Zeroing the columns is not enough: padding and borders do not collapse, so
|
|
400
|
+
the rail and panel kept eating 22px off the left of every phone screen
|
|
401
|
+
while producing no scrollbar to give it away. Below lg they leave the
|
|
402
|
+
layout entirely — the nav sheet carries them instead. */
|
|
403
|
+
[data-shell-rail],
|
|
404
|
+
[data-shell-panel] {
|
|
405
|
+
display: none;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
410
|
+
OVERLAY MOTION — §4.5
|
|
411
|
+
Drawer slides, dialog scales, scrim fades. Durations differ on purpose: the
|
|
412
|
+
drawer travels further, so it gets the full 220ms; the dialog appears in
|
|
413
|
+
place and 180ms keeps it from feeling sluggish.
|
|
414
|
+
══════════════════════════════════════════════════════════════════════════ */
|
|
415
|
+
|
|
416
|
+
@keyframes scrim-in {
|
|
417
|
+
from { opacity: 0 }
|
|
418
|
+
to { opacity: 1 }
|
|
419
|
+
}
|
|
420
|
+
@keyframes scrim-out {
|
|
421
|
+
from { opacity: 1 }
|
|
422
|
+
to { opacity: 0 }
|
|
423
|
+
}
|
|
424
|
+
/* Only `transform` and `opacity`: the dialog is placed with the separate
|
|
425
|
+
`translate` property (Tailwind's -translate-*). The old keyframes animated a
|
|
426
|
+
translate(-50%, -50%) of their own, which the browser added to the placement —
|
|
427
|
+
the dialog flashed off-centre for 180ms, then jumped. That was the "blink". */
|
|
428
|
+
@keyframes dlg-in {
|
|
429
|
+
from { opacity: 0; transform: translateY(8px) scale(.975) }
|
|
430
|
+
to { opacity: 1; transform: none }
|
|
431
|
+
}
|
|
432
|
+
@keyframes dlg-out {
|
|
433
|
+
from { opacity: 1; transform: none }
|
|
434
|
+
to { opacity: 0; transform: translateY(6px) scale(.985) }
|
|
435
|
+
}
|
|
436
|
+
/* One keyframe served both sides, so a left-pinned drawer animated in from
|
|
437
|
+
translateX(100%) — off the right edge, across the whole screen, to the left.
|
|
438
|
+
That single line was most of why the mobile menu felt broken. */
|
|
439
|
+
@keyframes sheet-in-right {
|
|
440
|
+
from { transform: translateX(100%); opacity: .6 }
|
|
441
|
+
to { transform: translateX(0); opacity: 1 }
|
|
442
|
+
}
|
|
443
|
+
@keyframes sheet-out-right {
|
|
444
|
+
from { transform: translateX(0); opacity: 1 }
|
|
445
|
+
to { transform: translateX(100%); opacity: .6 }
|
|
446
|
+
}
|
|
447
|
+
@keyframes sheet-in-left {
|
|
448
|
+
from { transform: translateX(-100%) }
|
|
449
|
+
to { transform: translateX(0) }
|
|
450
|
+
}
|
|
451
|
+
@keyframes sheet-out-left {
|
|
452
|
+
from { transform: translateX(0) }
|
|
453
|
+
to { transform: translateX(-100%) }
|
|
454
|
+
}
|
|
455
|
+
@keyframes pop-in {
|
|
456
|
+
from { opacity: 0; transform: scale(.97) translateY(-2px) }
|
|
457
|
+
to { opacity: 1; transform: none }
|
|
458
|
+
}
|
|
459
|
+
@keyframes pop-out {
|
|
460
|
+
from { opacity: 1; transform: none }
|
|
461
|
+
to { opacity: 0; transform: scale(.97) translateY(-2px) }
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
[data-scrim][data-state="open"] { animation: scrim-in 240ms var(--ease) }
|
|
465
|
+
[data-scrim][data-state="closed"] { animation: scrim-out 200ms var(--ease) both }
|
|
466
|
+
|
|
467
|
+
[data-dialog][data-state="open"] { animation: dlg-in 240ms var(--ease) }
|
|
468
|
+
[data-dialog][data-state="closed"] { animation: dlg-out 170ms cubic-bezier(0.4, 0, 1, 1) both }
|
|
469
|
+
|
|
470
|
+
[data-sheet="right"][data-state="open"] { animation: sheet-in-right 320ms var(--ease) }
|
|
471
|
+
[data-sheet="right"][data-state="closed"] { animation: sheet-out-right 220ms cubic-bezier(0.4, 0, 1, 1) both }
|
|
472
|
+
[data-sheet="left"][data-state="open"] { animation: sheet-in-left var(--dur) var(--ease) }
|
|
473
|
+
[data-sheet="left"][data-state="closed"] { animation: sheet-out-left 190ms var(--ease) }
|
|
474
|
+
|
|
475
|
+
@keyframes sheet-content-in {
|
|
476
|
+
from { opacity: 0 }
|
|
477
|
+
to { opacity: 1 }
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/* The panel carries the movement; its contents only resolve, a beat later.
|
|
481
|
+
Giving the children their own translate would fight the slide and read as
|
|
482
|
+
two things moving instead of one object arriving. */
|
|
483
|
+
[data-sheet][data-state="open"] > * {
|
|
484
|
+
animation: sheet-content-in 240ms var(--ease) both;
|
|
485
|
+
animation-delay: 80ms;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/* Lifted surfaces carry the top highlight. One rule, every overlay. */
|
|
489
|
+
[data-dialog],
|
|
490
|
+
[data-sheet],
|
|
491
|
+
[data-pop] {
|
|
492
|
+
box-shadow: var(--lit), var(--elev3);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
[data-pop][data-state="open"] { animation: pop-in 140ms var(--ease) }
|
|
496
|
+
[data-pop][data-state="closed"] { animation: pop-out 110ms var(--ease) }
|
|
497
|
+
|
|
498
|
+
/* Nineteen modules do not fit a laptop rail, so it scrolls. The fade is the
|
|
499
|
+
only affordance that says so without stealing width for a scrollbar. */
|
|
500
|
+
.rail-fade {
|
|
501
|
+
scrollbar-width: none;
|
|
502
|
+
mask-image: linear-gradient(to bottom, #000 calc(100% - 18px), transparent);
|
|
503
|
+
}
|
|
504
|
+
.rail-fade::-webkit-scrollbar {
|
|
505
|
+
width: 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/* A horizontal scrollbar across a tab strip reads as a broken layout. The
|
|
509
|
+
strip still scrolls when it must; it just stops announcing it. */
|
|
510
|
+
.no-bar {
|
|
511
|
+
scrollbar-width: none;
|
|
512
|
+
}
|
|
513
|
+
.no-bar::-webkit-scrollbar {
|
|
514
|
+
height: 0;
|
|
515
|
+
width: 0;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
520
|
+
MOTION LAYER
|
|
521
|
+
The shell was correct but inert: every state change happened instantly, so
|
|
522
|
+
the product felt like a series of screenshots rather than one surface. These
|
|
523
|
+
are the smallest moves that buy continuity — nothing here exceeds 220ms, and
|
|
524
|
+
all of it is already behind the reduced-motion rule above.
|
|
525
|
+
══════════════════════════════════════════════════════════════════════════ */
|
|
526
|
+
|
|
527
|
+
@keyframes page-in {
|
|
528
|
+
from {
|
|
529
|
+
opacity: 0;
|
|
530
|
+
transform: translateY(6px);
|
|
531
|
+
}
|
|
532
|
+
to {
|
|
533
|
+
opacity: 1;
|
|
534
|
+
transform: none;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
@keyframes page-fade {
|
|
539
|
+
from { opacity: 0 }
|
|
540
|
+
to { opacity: 1 }
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/* Opacity only, and short.
|
|
544
|
+
The first version slid the whole page up 6px over 260ms on every route
|
|
545
|
+
change — including between sibling screens of one module. Moving the text a
|
|
546
|
+
reader is already reaching for does not feel smooth, it feels like waiting.
|
|
547
|
+
The navigation itself now carries the motion (the rail and sidebar
|
|
548
|
+
indicators travel); the content only has to resolve. */
|
|
549
|
+
.page-in {
|
|
550
|
+
animation: page-fade 150ms var(--ease) both;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/* Cards and rows settle in sequence on first paint. Capped at eight, because
|
|
554
|
+
a stagger long enough to notice is a stagger that wastes the user's time. */
|
|
555
|
+
.stagger > * {
|
|
556
|
+
animation: page-in 300ms var(--ease) both;
|
|
557
|
+
}
|
|
558
|
+
.stagger > *:nth-child(1) { animation-delay: 0ms }
|
|
559
|
+
.stagger > *:nth-child(2) { animation-delay: 28ms }
|
|
560
|
+
.stagger > *:nth-child(3) { animation-delay: 56ms }
|
|
561
|
+
.stagger > *:nth-child(4) { animation-delay: 84ms }
|
|
562
|
+
.stagger > *:nth-child(5) { animation-delay: 112ms }
|
|
563
|
+
.stagger > *:nth-child(6) { animation-delay: 140ms }
|
|
564
|
+
.stagger > *:nth-child(7) { animation-delay: 168ms }
|
|
565
|
+
.stagger > *:nth-child(n + 8) { animation-delay: 196ms }
|
|
566
|
+
|
|
567
|
+
/* Press feedback. A control that does not move under the pointer feels dead,
|
|
568
|
+
and 1.5% is enough to register without becoming a bounce. */
|
|
569
|
+
button:not(:disabled):active,
|
|
570
|
+
[role="button"]:not([aria-disabled="true"]):active {
|
|
571
|
+
transform: scale(0.985);
|
|
572
|
+
}
|
|
573
|
+
button,
|
|
574
|
+
[role="button"] {
|
|
575
|
+
transition: transform 90ms var(--ease);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/* Anything that changes colour does so on the same curve. */
|
|
579
|
+
a,
|
|
580
|
+
button,
|
|
581
|
+
[role="button"],
|
|
582
|
+
input,
|
|
583
|
+
textarea,
|
|
584
|
+
select {
|
|
585
|
+
transition-timing-function: var(--ease);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
589
|
+
BRAND MARK MOTION — <WaveMark animate="…">
|
|
590
|
+
Slow and rare on purpose: a shine every few seconds says "alive", a loop
|
|
591
|
+
that never rests says "loading forever". All of it stops for people who ask
|
|
592
|
+
for less motion.
|
|
593
|
+
══════════════════════════════════════════════════════════════════════════ */
|
|
594
|
+
|
|
595
|
+
@keyframes wm-shine {
|
|
596
|
+
0%, 55% { transform: translateX(-160%) skewX(-20deg) }
|
|
597
|
+
100% { transform: translateX(260%) skewX(-20deg) }
|
|
598
|
+
}
|
|
599
|
+
@keyframes wm-float {
|
|
600
|
+
0%, 100% { transform: translateY(0) }
|
|
601
|
+
50% { transform: translateY(-7%) }
|
|
602
|
+
}
|
|
603
|
+
@keyframes wm-pulse {
|
|
604
|
+
0% { transform: scale(1); opacity: .5 }
|
|
605
|
+
100% { transform: scale(1.55); opacity: 0 }
|
|
606
|
+
}
|
|
607
|
+
@keyframes wm-flip {
|
|
608
|
+
0%, 35% { transform: perspective(500px) rotateY(0) }
|
|
609
|
+
65%, 100% { transform: perspective(500px) rotateY(360deg) }
|
|
610
|
+
}
|
|
611
|
+
.wm-shine { animation: wm-shine 3.4s cubic-bezier(.4, 0, .2, 1) infinite }
|
|
612
|
+
.wm-float { animation: wm-float 3.8s ease-in-out infinite }
|
|
613
|
+
.wm-pulse { animation: wm-pulse 1.9s cubic-bezier(0, 0, .2, 1) infinite }
|
|
614
|
+
.wm-pulse-late { animation: wm-pulse 1.9s cubic-bezier(0, 0, .2, 1) .95s infinite }
|
|
615
|
+
.wm-flip { animation: wm-flip 3.6s cubic-bezier(.65, 0, .25, 1) infinite }
|
|
616
|
+
|
|
617
|
+
@media (prefers-reduced-motion: reduce) {
|
|
618
|
+
.wm-shine, .wm-float, .wm-pulse, .wm-pulse-late, .wm-flip { animation: none }
|
|
619
|
+
.wm-shine { opacity: 0 }
|
|
620
|
+
.wm-pulse, .wm-pulse-late { display: none }
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/* ── field and progress motion ── */
|
|
624
|
+
@keyframes wm-shake {
|
|
625
|
+
0%, 100% { transform: translateX(0) }
|
|
626
|
+
20%, 60% { transform: translateX(-4px) }
|
|
627
|
+
40%, 80% { transform: translateX(4px) }
|
|
628
|
+
}
|
|
629
|
+
@keyframes wm-stripes { to { background-position: 28px 0 } }
|
|
630
|
+
@keyframes wm-sheen {
|
|
631
|
+
0% { left: -40% }
|
|
632
|
+
60%, 100% { left: 110% }
|
|
633
|
+
}
|
|
634
|
+
@keyframes wm-indeterminate {
|
|
635
|
+
0% { left: -40% }
|
|
636
|
+
100% { left: 100% }
|
|
637
|
+
}
|
|
638
|
+
/* Once, when the error appears — never a loop. */
|
|
639
|
+
.wm-shake { animation: wm-shake 360ms var(--ease) 1 }
|
|
640
|
+
.wm-stripes {
|
|
641
|
+
background-image: linear-gradient(45deg, rgba(255,255,255,.22) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.22) 50%, rgba(255,255,255,.22) 75%, transparent 75%);
|
|
642
|
+
background-size: 28px 28px;
|
|
643
|
+
animation: wm-stripes 900ms linear infinite;
|
|
644
|
+
}
|
|
645
|
+
.wm-sheen { animation: wm-sheen 2.6s var(--ease) infinite }
|
|
646
|
+
.wm-indeterminate { animation: wm-indeterminate 1.3s cubic-bezier(.65, 0, .35, 1) infinite }
|
|
647
|
+
|
|
648
|
+
@media (prefers-reduced-motion: reduce) {
|
|
649
|
+
.wm-shake, .wm-stripes, .wm-sheen { animation: none }
|
|
650
|
+
.wm-indeterminate { animation-duration: 3s }
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/* A cell that was just saved: one green flash, then back to normal. */
|
|
654
|
+
@keyframes wm-saved {
|
|
655
|
+
0% { background-color: var(--success-subtle) }
|
|
656
|
+
100% { background-color: transparent }
|
|
657
|
+
}
|
|
658
|
+
.wm-saved { animation: wm-saved 900ms var(--ease) 1 }
|