@better-forge/ui 0.1.0 → 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.
Files changed (51) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +13 -13
  3. package/icons.json +170 -170
  4. package/package.json +71 -71
  5. package/src/api/client.ts +139 -139
  6. package/src/components/ConfirmIdentityModal.tsx +82 -82
  7. package/src/components/ConfirmModal.tsx +83 -83
  8. package/src/components/CopyButton.tsx +71 -71
  9. package/src/components/CredentialSection.tsx +270 -270
  10. package/src/components/ErrorBoundary.tsx +47 -47
  11. package/src/components/ModalShell.tsx +160 -160
  12. package/src/components/ResetTwoFactorModal.tsx +74 -74
  13. package/src/components/SidePanel.tsx +96 -96
  14. package/src/components/SingleSignOnModal.tsx +161 -161
  15. package/src/components/SsoConfirmation.tsx +96 -96
  16. package/src/components/VaultTypePicker.tsx +131 -131
  17. package/src/components/WhatsNew.tsx +84 -84
  18. package/src/components/formParts.tsx +59 -59
  19. package/src/components/ui/Alert.tsx +45 -45
  20. package/src/components/ui/DataTable.tsx +225 -225
  21. package/src/components/ui/Field.tsx +63 -63
  22. package/src/components/ui/PageHeader.tsx +36 -36
  23. package/src/components/ui/Pagination.tsx +37 -37
  24. package/src/components/ui/QuickStartPanel.tsx +57 -57
  25. package/src/components/ui/StateViews.tsx +135 -135
  26. package/src/components/ui/Switch.tsx +65 -65
  27. package/src/components/ui/Toast.tsx +104 -104
  28. package/src/components/ui/index.ts +9 -9
  29. package/src/config.ts +46 -46
  30. package/src/hooks/useDarkMode.ts +38 -38
  31. package/src/hooks/useDebouncedValue.ts +22 -22
  32. package/src/index.ts +42 -42
  33. package/src/lib/changelog.ts +93 -93
  34. package/src/lib/dateFormat.ts +90 -90
  35. package/src/navigation.ts +6 -6
  36. package/src/pages/ApiTokens.tsx +361 -361
  37. package/src/pages/AuditLog.tsx +388 -388
  38. package/src/pages/Backups.tsx +170 -170
  39. package/src/pages/ChangePassword.tsx +84 -84
  40. package/src/pages/Login.tsx +275 -275
  41. package/src/pages/Settings.tsx +264 -264
  42. package/src/pages/Setup.tsx +438 -438
  43. package/src/pages/SsoConfirm.tsx +26 -26
  44. package/src/pages/SsoTestResult.tsx +104 -104
  45. package/src/pages/Users.tsx +426 -426
  46. package/src/pages/Vault.tsx +1036 -1036
  47. package/src/shell/AdminShell.tsx +256 -256
  48. package/src/shell/guards.tsx +88 -88
  49. package/styles/base.css +521 -521
  50. package/styles/icons.css +4 -4
  51. package/tailwind-preset.js +79 -79
package/styles/base.css CHANGED
@@ -1,521 +1,521 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
-
5
- /* Fonts are bundled with the app (Manrope, Inter, JetBrains Mono: SIL OFL; Material Symbols: Apache 2.0),
6
- so opening the interface makes no request to Google. */
7
- @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/manrope-latin-ext-4.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
8
- @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 600; font-display: swap; src: url('./fonts/manrope-latin-ext-4.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
9
- @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/manrope-latin-ext-4.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
10
- @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 800; font-display: swap; src: url('./fonts/manrope-latin-ext-4.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
11
- @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/manrope-latin-5.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
12
- @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 600; font-display: swap; src: url('./fonts/manrope-latin-5.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
13
- @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/manrope-latin-5.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
14
- @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 800; font-display: swap; src: url('./fonts/manrope-latin-5.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
15
- @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/inter-latin-ext.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
16
- @font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('./fonts/inter-latin-ext.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
17
- @font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('./fonts/inter-latin-ext.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
18
- @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/inter-latin-1.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
19
- @font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('./fonts/inter-latin-1.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
20
- @font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('./fonts/inter-latin-1.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
21
- @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/jetbrains-mono-latin-ext-2.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
22
- @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url('./fonts/jetbrains-mono-latin-ext-2.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
23
- @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/jetbrains-mono-latin-3.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
24
- @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url('./fonts/jetbrains-mono-latin-3.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
25
-
26
- /* ── M3 Color Tokens — Light (exact from mockup) ─────────────────── */
27
- :root {
28
- --m3-surface: #f5f6f9;
29
- --m3-surface-dim: #d7dce5;
30
- --m3-surface-bright: #f5f6f9;
31
- --m3-surface-container-lowest: #fafbfc;
32
- --m3-surface-container-low: #eef0f5;
33
- --m3-surface-container: #e7eaf0;
34
- --m3-surface-container-high: #dfe3ea;
35
- --m3-surface-container-highest:#d7dce5;
36
- --m3-on-surface: #1c2333;
37
- --m3-on-surface-variant: #45464d;
38
- --m3-secondary: #566174;
39
- --m3-outline: #7c828e;
40
- --m3-outline-variant: #d3d7df;
41
- --m3-primary: #000000;
42
- --m3-on-primary: #ffffff;
43
- --m3-on-primary-container: #497cff;
44
- --m3-primary-fixed: #dbe7ff;
45
- --m3-primary-fixed-dim: #b4c5ff;
46
- --m3-primary-container: #00174b;
47
- --m3-surface-tint: #0053db;
48
- --m3-inverse-primary: #b4c5ff;
49
- --m3-secondary-container: #d0e1fb;
50
- --m3-on-secondary-container: #54647a;
51
- --m3-secondary-fixed: #b7c8e1;
52
- --m3-error: #ba1a1a;
53
- --m3-error-container: #ffdad6;
54
- --m3-on-error-container: #93000a;
55
-
56
- /* Status colors matching mockup badges */
57
- --m3-up: #166534;
58
- --m3-up-bg: rgba(34, 197, 94, 0.12);
59
- --m3-up-bar: #22c55e;
60
- --m3-down: #ba1a1a;
61
- --m3-down-bg: #ffdad6;
62
- --m3-degraded: #854d0e;
63
- --m3-degraded-bg: rgba(234, 179, 8, 0.12);
64
- --m3-degraded-bar: #eab308;
65
- --m3-partial: #9a3412;
66
- --m3-partial-bg: rgba(249, 115, 22, 0.12);
67
- --m3-partial-bar: #f97316;
68
-
69
- /* Branding colours; an application may point them at its own tokens. */
70
- --color-primary: var(--m3-primary);
71
- --color-accent: var(--m3-on-primary-container);
72
-
73
- /* Semantic action colors stay neutral and consistent across admin pages. */
74
- --admin-action-primary: #dbe7ff;
75
- --admin-action-primary-hover: #c9d9ff;
76
- --admin-on-action-primary: #172033;
77
- --admin-action-focus: rgba(73, 124, 255, 0.28);
78
- --admin-selection: var(--m3-primary-fixed);
79
- --admin-selection-hover: #c9d9ff;
80
- --admin-on-selection: var(--m3-primary);
81
- --admin-selection-border: #a9bfe6;
82
- --admin-control-accent: #4b5563;
83
- --admin-control-on: #ffffff;
84
- --admin-icon-container: #e4e7ec;
85
- --admin-icon-color: #4b5563;
86
- --audit-create-bg: #d1fae5;
87
- --audit-create-text: #065f46;
88
- --audit-update-bg: #e0e7ff;
89
- --audit-update-text: #3730a3;
90
- --audit-delete-bg: #fee2e2;
91
- --audit-delete-text: #991b1b;
92
- }
93
-
94
- /* ── M3 Color Tokens — Dark ───────────────────────────────────────── */
95
- html.dark {
96
- --m3-surface: #20232a;
97
- --m3-surface-dim: #1a1c22;
98
- --m3-surface-bright: #383c46;
99
- --m3-surface-container-lowest: #252830;
100
- --m3-surface-container-low: #1a1c22;
101
- --m3-surface-container: #282b33;
102
- --m3-surface-container-high: #30343d;
103
- --m3-surface-container-highest:#383c46;
104
- --m3-on-surface: #e3e5ea;
105
- --m3-on-surface-variant: #c5cad3;
106
- --m3-secondary: #aab0bb;
107
- --m3-outline: #80868f;
108
- --m3-outline-variant: #3a3e48;
109
- --m3-primary: #b4c5ff;
110
- --m3-on-primary: #111318;
111
- --m3-on-primary-container: #aac7ff;
112
- --m3-primary-container: #30333b;
113
- --m3-secondary-container: #2a3a4d;
114
- --m3-on-secondary-container: #c0cedd;
115
- --m3-secondary-fixed: #3a4d62;
116
- --m3-error: #ffb4ab;
117
- --m3-error-container: #93000a;
118
- --m3-on-error-container: #ffdad6;
119
- --m3-primary-fixed: #3a3d46;
120
- --m3-primary-fixed-dim: #30333b;
121
- --m3-surface-tint: #b4c5ff;
122
- --m3-inverse-primary: #0053db;
123
-
124
- --admin-action-primary: #e5e7eb;
125
- --admin-action-primary-hover: #ffffff;
126
- --admin-on-action-primary: #111318;
127
- --admin-action-focus: rgba(229, 231, 235, 0.28);
128
- --admin-selection: #3a3d46;
129
- --admin-selection-hover: #454953;
130
- --admin-on-selection: #f0f1f3;
131
- --admin-selection-border: #777e8b;
132
- --admin-control-accent: #c3c8d1;
133
- --admin-control-on: #1a1c22;
134
- --admin-icon-container: #30343d;
135
- --admin-icon-color: #d6d9df;
136
- --audit-create-bg: #164e3b;
137
- --audit-create-text: #a7f3d0;
138
- --audit-update-bg: #37366d;
139
- --audit-update-text: #d8d7ff;
140
- --audit-delete-bg: #692d32;
141
- --audit-delete-text: #fecaca;
142
-
143
- --m3-up: #4ade80;
144
- --m3-up-bg: rgba(74, 222, 128, 0.12);
145
- --m3-up-bar: #4ade80;
146
- --m3-down: #ffb4ab;
147
- --m3-down-bg: rgba(147, 0, 10, 0.25);
148
- --m3-degraded: #ffb950;
149
- --m3-degraded-bg: rgba(234, 179, 8, 0.15);
150
- --m3-degraded-bar: #fbbf24;
151
- --m3-partial: #fb923c;
152
- --m3-partial-bg: rgba(249, 115, 22, 0.15);
153
- --m3-partial-bar: #f97316;
154
- }
155
-
156
- /* ── Base ─────────────────────────────────────────────────────────── */
157
- * { box-sizing: border-box; }
158
-
159
- body {
160
- margin: 0;
161
- font-family: 'Inter', system-ui, sans-serif;
162
- background: var(--m3-surface);
163
- color: var(--m3-on-surface);
164
- }
165
-
166
- input[type='checkbox'],
167
- input[type='radio'] {
168
- accent-color: var(--admin-control-accent);
169
- }
170
-
171
- /*
172
- * Button system. Compose a shape with a variant: `btn btn-primary`, `btn btn-secondary`,
173
- * `btn btn-ghost`, `btn btn-outline`, `btn btn-danger` (filled, for confirmations) or
174
- * `btn btn-danger-outline`; add `btn-sm` for compact rows.
175
- * `btn-icon` is a standalone square icon button. These live in the components layer so page-level
176
- * utilities (e.g. `px-5`, `rounded-full`, `w-full`) still win.
177
- */
178
- @layer components {
179
- .btn {
180
- display: inline-flex;
181
- align-items: center;
182
- justify-content: center;
183
- gap: 0.5rem;
184
- border: 1px solid transparent;
185
- border-radius: 0.75rem;
186
- padding: 0.625rem 1rem;
187
- font-size: 0.875rem;
188
- line-height: 1.25rem;
189
- font-weight: 600;
190
- white-space: nowrap;
191
- cursor: pointer;
192
- transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease,
193
- box-shadow 0.18s ease, opacity 0.18s ease, transform 0.18s ease;
194
- }
195
- .btn:active:not(:disabled) { transform: scale(0.98); }
196
- .btn:disabled { opacity: 0.5; cursor: not-allowed; }
197
- .btn .material-symbols-outlined { font-size: 18px; }
198
-
199
- .btn-sm {
200
- gap: 0.375rem;
201
- border-radius: 0.5rem;
202
- padding: 0.375rem 0.75rem;
203
- font-size: 0.75rem;
204
- line-height: 1rem;
205
- }
206
- .btn-sm .material-symbols-outlined { font-size: 16px; }
207
-
208
- /* Outlined in light mode so the filled primary button is the only filled action. */
209
- .btn-secondary {
210
- background: transparent;
211
- border-color: var(--m3-outline-variant);
212
- color: var(--m3-on-surface);
213
- }
214
- .btn-secondary:hover:not(:disabled) { background: var(--m3-surface-container); }
215
- html.dark .btn-secondary {
216
- background: var(--m3-surface-container-high);
217
- border-color: transparent;
218
- }
219
- html.dark .btn-secondary:hover:not(:disabled) { background: var(--m3-surface-container-highest); }
220
-
221
- .btn-ghost {
222
- background: transparent;
223
- color: var(--m3-secondary);
224
- }
225
- .btn-ghost:hover:not(:disabled) {
226
- background: var(--m3-surface-container);
227
- color: var(--m3-on-surface);
228
- }
229
-
230
- .btn-outline {
231
- background: transparent;
232
- color: var(--m3-secondary);
233
- border-color: var(--m3-outline-variant);
234
- }
235
- .btn-outline:hover:not(:disabled) {
236
- background: var(--m3-surface-container);
237
- color: var(--m3-on-surface);
238
- }
239
-
240
- .btn-danger {
241
- background: var(--m3-error-container);
242
- color: var(--m3-on-error-container);
243
- border-color: color-mix(in srgb, var(--m3-error) 45%, transparent);
244
- }
245
- .btn-danger:hover:not(:disabled) {
246
- border-color: var(--m3-error);
247
- }
248
- .btn-danger:focus-visible {
249
- outline: none;
250
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--m3-down) 25%, transparent);
251
- }
252
-
253
- .btn-icon {
254
- display: inline-flex;
255
- align-items: center;
256
- justify-content: center;
257
- flex-shrink: 0;
258
- width: 2rem;
259
- height: 2rem;
260
- border-radius: 0.5rem;
261
- color: var(--m3-secondary);
262
- background: transparent;
263
- cursor: pointer;
264
- transition: background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
265
- }
266
- .btn-icon:hover:not(:disabled) {
267
- background: var(--m3-surface-container);
268
- color: var(--m3-on-surface);
269
- }
270
- .btn-icon:disabled { opacity: 0.5; cursor: not-allowed; }
271
- .btn-icon .material-symbols-outlined { font-size: 20px; }
272
-
273
- .focus-ring:focus-visible,
274
- .btn-secondary:focus-visible,
275
- .btn-ghost:focus-visible,
276
- .btn-outline:focus-visible,
277
- .btn-icon:focus-visible {
278
- outline: none;
279
- box-shadow: 0 0 0 3px var(--admin-action-focus);
280
- }
281
- }
282
-
283
- /* Primary admin action: one contrast-safe appearance in every feature. */
284
- .btn-primary {
285
- background: var(--admin-action-primary) !important;
286
- color: var(--admin-on-action-primary) !important;
287
- border-color: transparent !important;
288
- transition: background-color 0.18s ease, box-shadow 0.18s ease,
289
- opacity 0.18s ease, transform 0.18s ease;
290
- }
291
- .btn-primary:hover:not(:disabled) {
292
- background: var(--admin-action-primary-hover) !important;
293
- }
294
- .btn-primary:focus-visible {
295
- outline: none;
296
- box-shadow: 0 0 0 3px var(--admin-action-focus);
297
- }
298
- .btn-primary:disabled {
299
- opacity: 0.5 !important;
300
- cursor: not-allowed !important;
301
- }
302
-
303
- .btn-danger-outline {
304
- color: var(--m3-down);
305
- border: 1px solid var(--m3-outline-variant);
306
- transition: background-color 0.18s ease, border-color 0.18s ease,
307
- box-shadow 0.18s ease, transform 0.18s ease;
308
- }
309
- .btn-danger-outline:hover:not(:disabled) {
310
- background: var(--m3-down-bg);
311
- border-color: color-mix(in srgb, var(--m3-down) 45%, var(--m3-outline-variant));
312
- }
313
- .btn-danger-outline:focus-visible {
314
- outline: none;
315
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--m3-down) 25%, transparent);
316
- }
317
- .btn-danger-outline:active:not(:disabled) {
318
- transform: scale(0.98);
319
- }
320
- .btn-danger-outline:disabled {
321
- cursor: not-allowed;
322
- opacity: 0.5;
323
- }
324
-
325
- .copy-feedback-button {
326
- background: var(--m3-surface-container-high);
327
- color: var(--m3-on-surface);
328
- border: 1px solid var(--m3-outline-variant);
329
- transition: background-color 0.18s ease, border-color 0.18s ease,
330
- color 0.18s ease, transform 0.18s ease;
331
- }
332
- .copy-feedback-button:hover {
333
- background: var(--m3-surface-container-highest);
334
- }
335
- .copy-feedback-button[data-copied] {
336
- background: var(--m3-up-bg);
337
- border-color: color-mix(in srgb, var(--m3-up) 35%, var(--m3-outline-variant));
338
- color: var(--m3-up);
339
- }
340
- .copy-feedback-button[data-failed] {
341
- background: var(--m3-down-bg);
342
- border-color: color-mix(in srgb, var(--m3-down) 35%, var(--m3-outline-variant));
343
- color: var(--m3-down);
344
- }
345
- .copy-feedback-button:focus-visible {
346
- outline: none;
347
- box-shadow: 0 0 0 3px var(--admin-action-focus);
348
- }
349
- .copy-feedback-button:active {
350
- transform: scale(0.98);
351
- }
352
-
353
- .selection-active {
354
- background: var(--admin-selection) !important;
355
- color: var(--admin-on-selection) !important;
356
- border-color: var(--admin-selection-border) !important;
357
- }
358
- .selection-active:hover {
359
- background: var(--admin-selection-hover) !important;
360
- }
361
-
362
- html.dark .side-tab-active {
363
- background: var(--admin-selection) !important;
364
- color: var(--admin-on-selection) !important;
365
- }
366
-
367
- html.dark .side-tab-badge-active {
368
- background: var(--admin-on-selection) !important;
369
- color: var(--admin-selection) !important;
370
- }
371
-
372
- /* ── Navigation (sidebar + mobile drawer) ─────────────────────────── */
373
- .admin-nav-item {
374
- display: flex;
375
- width: 100%;
376
- align-items: center;
377
- gap: 0.75rem;
378
- padding: 0.625rem 1rem;
379
- border-radius: 0.75rem;
380
- font-size: 0.875rem;
381
- line-height: 1.25rem;
382
- color: var(--m3-secondary);
383
- text-align: left;
384
- transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
385
- }
386
- .admin-nav-item:hover { transform: translateX(0.25rem); }
387
- .admin-nav-item:not([aria-current='page']):hover {
388
- background: rgba(0, 0, 0, 0.04);
389
- color: var(--m3-on-surface);
390
- }
391
- html.dark .admin-nav-item:not([aria-current='page']):hover { background: rgba(255, 255, 255, 0.08); }
392
- .admin-nav-item[aria-current='page'] {
393
- background: var(--m3-surface-container-lowest);
394
- color: var(--m3-on-surface);
395
- font-weight: 600;
396
- box-shadow: 0 1px 4px rgba(19, 27, 46, 0.08);
397
- }
398
- .admin-nav-item.admin-nav-danger:hover,
399
- html.dark .admin-nav-item.admin-nav-danger:hover {
400
- background: var(--m3-error-container);
401
- color: var(--m3-on-error-container);
402
- }
403
- .admin-nav-item:focus-visible {
404
- outline: none;
405
- box-shadow: 0 0 0 3px var(--admin-action-focus);
406
- }
407
-
408
- @keyframes drawer-in {
409
- from { transform: translateX(-100%); }
410
- to { transform: translateX(0); }
411
- }
412
- @keyframes overlay-in {
413
- from { opacity: 0; }
414
- to { opacity: 1; }
415
- }
416
- .admin-drawer-panel { animation: drawer-in 0.22s ease-out; }
417
- .admin-drawer-overlay { animation: overlay-in 0.22s ease-out; }
418
-
419
- @keyframes toast-in {
420
- from { opacity: 0; transform: translateY(8px); }
421
- to { opacity: 1; transform: translateY(0); }
422
- }
423
- .admin-toast { animation: toast-in 0.2s ease-out; }
424
-
425
- @media (prefers-reduced-motion: reduce) {
426
- .admin-drawer-panel, .admin-drawer-overlay, .admin-toast { animation: none; }
427
- .admin-nav-item:hover { transform: none; }
428
- }
429
-
430
- /* ── Input style ──────────────────────────────────────────────────── */
431
- .input-m3 {
432
- width: 100%;
433
- background: var(--m3-surface-container-lowest);
434
- border: 1.5px solid var(--m3-outline-variant);
435
- border-radius: 10px;
436
- padding: 9px 13px;
437
- color: var(--m3-on-surface);
438
- font-family: 'Inter', system-ui, sans-serif;
439
- font-size: 14px;
440
- outline: none;
441
- transition: border-color 0.18s ease, box-shadow 0.18s ease;
442
- }
443
- .input-m3::placeholder { color: var(--m3-secondary); }
444
- .input-m3:focus {
445
- border-color: var(--m3-primary);
446
- box-shadow: 0 0 0 3px rgba(0, 83, 219, 0.12);
447
- }
448
- html.dark .input-m3:focus {
449
- box-shadow: 0 0 0 3px rgba(180, 197, 255, 0.15);
450
- }
451
- .input-m3:disabled { opacity: 0.45; cursor: not-allowed; }
452
- select.input-m3 {
453
- appearance: none;
454
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2376777d' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
455
- background-repeat: no-repeat;
456
- background-position: right 12px center;
457
- padding-right: 32px;
458
- }
459
-
460
- input.input-m3:is([type='date'], [type='time'], [type='datetime-local']),
461
- input.input-sig:is([type='date'], [type='time'], [type='datetime-local']) {
462
- color-scheme: light;
463
- }
464
-
465
- html.dark input.input-m3:is([type='date'], [type='time'], [type='datetime-local']),
466
- html.dark input.input-sig:is([type='date'], [type='time'], [type='datetime-local']) {
467
- color-scheme: dark;
468
- }
469
-
470
- input.input-m3:is([type='date'], [type='time'], [type='datetime-local'])::-webkit-calendar-picker-indicator,
471
- input.input-sig:is([type='date'], [type='time'], [type='datetime-local'])::-webkit-calendar-picker-indicator {
472
- cursor: pointer;
473
- opacity: 1;
474
- }
475
-
476
- /* Legacy alias */
477
- .input-sig { @apply input-m3; }
478
-
479
- /* ── Entrance animation ───────────────────────────────────────────── */
480
- @keyframes fadeUp {
481
- from { opacity: 0; transform: translateY(10px); }
482
- to { opacity: 1; transform: translateY(0); }
483
- }
484
- .fade-up { opacity: 0; animation: fadeUp 0.4s ease forwards; }
485
-
486
- /* ── Card hover ───────────────────────────────────────────────────── */
487
- .card-hover {
488
- transition: box-shadow 0.2s ease, border-color 0.2s ease;
489
- }
490
- .card-hover:hover {
491
- box-shadow: 0 2px 12px rgba(0, 83, 219, 0.08);
492
- border-color: var(--m3-outline);
493
- }
494
- html.dark .card-hover:hover {
495
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
496
- }
497
-
498
- /* ── Material Symbols ─────────────────────────────────────────────── */
499
- .material-symbols-outlined {
500
- font-family: 'Material Symbols Outlined';
501
- font-weight: normal;
502
- font-style: normal;
503
- display: inline-block;
504
- letter-spacing: normal;
505
- text-transform: none;
506
- white-space: nowrap;
507
- word-wrap: normal;
508
- direction: ltr;
509
- font-feature-settings: 'liga';
510
- -webkit-font-smoothing: antialiased;
511
- font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
512
- font-size: inherit;
513
- line-height: 1;
514
- vertical-align: middle;
515
- }
516
-
517
- /* ── Scrollbar ────────────────────────────────────────────────────── */
518
- ::-webkit-scrollbar { width: 6px; }
519
- ::-webkit-scrollbar-track { background: transparent; }
520
- ::-webkit-scrollbar-thumb { background: var(--m3-outline-variant); border-radius: 3px; }
521
-
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /* Fonts are bundled with the app (Manrope, Inter, JetBrains Mono: SIL OFL; Material Symbols: Apache 2.0),
6
+ so opening the interface makes no request to Google. */
7
+ @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/manrope-latin-ext-4.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
8
+ @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 600; font-display: swap; src: url('./fonts/manrope-latin-ext-4.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
9
+ @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/manrope-latin-ext-4.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
10
+ @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 800; font-display: swap; src: url('./fonts/manrope-latin-ext-4.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
11
+ @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/manrope-latin-5.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
12
+ @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 600; font-display: swap; src: url('./fonts/manrope-latin-5.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
13
+ @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/manrope-latin-5.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
14
+ @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 800; font-display: swap; src: url('./fonts/manrope-latin-5.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
15
+ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/inter-latin-ext.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
16
+ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('./fonts/inter-latin-ext.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
17
+ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('./fonts/inter-latin-ext.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
18
+ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/inter-latin-1.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
19
+ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('./fonts/inter-latin-1.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
20
+ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('./fonts/inter-latin-1.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
21
+ @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/jetbrains-mono-latin-ext-2.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
22
+ @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url('./fonts/jetbrains-mono-latin-ext-2.woff2') format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
23
+ @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/jetbrains-mono-latin-3.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
24
+ @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url('./fonts/jetbrains-mono-latin-3.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
25
+
26
+ /* ── M3 Color Tokens — Light (exact from mockup) ─────────────────── */
27
+ :root {
28
+ --m3-surface: #f5f6f9;
29
+ --m3-surface-dim: #d7dce5;
30
+ --m3-surface-bright: #f5f6f9;
31
+ --m3-surface-container-lowest: #fafbfc;
32
+ --m3-surface-container-low: #eef0f5;
33
+ --m3-surface-container: #e7eaf0;
34
+ --m3-surface-container-high: #dfe3ea;
35
+ --m3-surface-container-highest:#d7dce5;
36
+ --m3-on-surface: #1c2333;
37
+ --m3-on-surface-variant: #45464d;
38
+ --m3-secondary: #566174;
39
+ --m3-outline: #7c828e;
40
+ --m3-outline-variant: #d3d7df;
41
+ --m3-primary: #000000;
42
+ --m3-on-primary: #ffffff;
43
+ --m3-on-primary-container: #497cff;
44
+ --m3-primary-fixed: #dbe7ff;
45
+ --m3-primary-fixed-dim: #b4c5ff;
46
+ --m3-primary-container: #00174b;
47
+ --m3-surface-tint: #0053db;
48
+ --m3-inverse-primary: #b4c5ff;
49
+ --m3-secondary-container: #d0e1fb;
50
+ --m3-on-secondary-container: #54647a;
51
+ --m3-secondary-fixed: #b7c8e1;
52
+ --m3-error: #ba1a1a;
53
+ --m3-error-container: #ffdad6;
54
+ --m3-on-error-container: #93000a;
55
+
56
+ /* Status colors matching mockup badges */
57
+ --m3-up: #166534;
58
+ --m3-up-bg: rgba(34, 197, 94, 0.12);
59
+ --m3-up-bar: #22c55e;
60
+ --m3-down: #ba1a1a;
61
+ --m3-down-bg: #ffdad6;
62
+ --m3-degraded: #854d0e;
63
+ --m3-degraded-bg: rgba(234, 179, 8, 0.12);
64
+ --m3-degraded-bar: #eab308;
65
+ --m3-partial: #9a3412;
66
+ --m3-partial-bg: rgba(249, 115, 22, 0.12);
67
+ --m3-partial-bar: #f97316;
68
+
69
+ /* Branding colours; an application may point them at its own tokens. */
70
+ --color-primary: var(--m3-primary);
71
+ --color-accent: var(--m3-on-primary-container);
72
+
73
+ /* Semantic action colors stay neutral and consistent across admin pages. */
74
+ --admin-action-primary: #dbe7ff;
75
+ --admin-action-primary-hover: #c9d9ff;
76
+ --admin-on-action-primary: #172033;
77
+ --admin-action-focus: rgba(73, 124, 255, 0.28);
78
+ --admin-selection: var(--m3-primary-fixed);
79
+ --admin-selection-hover: #c9d9ff;
80
+ --admin-on-selection: var(--m3-primary);
81
+ --admin-selection-border: #a9bfe6;
82
+ --admin-control-accent: #4b5563;
83
+ --admin-control-on: #ffffff;
84
+ --admin-icon-container: #e4e7ec;
85
+ --admin-icon-color: #4b5563;
86
+ --audit-create-bg: #d1fae5;
87
+ --audit-create-text: #065f46;
88
+ --audit-update-bg: #e0e7ff;
89
+ --audit-update-text: #3730a3;
90
+ --audit-delete-bg: #fee2e2;
91
+ --audit-delete-text: #991b1b;
92
+ }
93
+
94
+ /* ── M3 Color Tokens — Dark ───────────────────────────────────────── */
95
+ html.dark {
96
+ --m3-surface: #20232a;
97
+ --m3-surface-dim: #1a1c22;
98
+ --m3-surface-bright: #383c46;
99
+ --m3-surface-container-lowest: #252830;
100
+ --m3-surface-container-low: #1a1c22;
101
+ --m3-surface-container: #282b33;
102
+ --m3-surface-container-high: #30343d;
103
+ --m3-surface-container-highest:#383c46;
104
+ --m3-on-surface: #e3e5ea;
105
+ --m3-on-surface-variant: #c5cad3;
106
+ --m3-secondary: #aab0bb;
107
+ --m3-outline: #80868f;
108
+ --m3-outline-variant: #3a3e48;
109
+ --m3-primary: #b4c5ff;
110
+ --m3-on-primary: #111318;
111
+ --m3-on-primary-container: #aac7ff;
112
+ --m3-primary-container: #30333b;
113
+ --m3-secondary-container: #2a3a4d;
114
+ --m3-on-secondary-container: #c0cedd;
115
+ --m3-secondary-fixed: #3a4d62;
116
+ --m3-error: #ffb4ab;
117
+ --m3-error-container: #93000a;
118
+ --m3-on-error-container: #ffdad6;
119
+ --m3-primary-fixed: #3a3d46;
120
+ --m3-primary-fixed-dim: #30333b;
121
+ --m3-surface-tint: #b4c5ff;
122
+ --m3-inverse-primary: #0053db;
123
+
124
+ --admin-action-primary: #e5e7eb;
125
+ --admin-action-primary-hover: #ffffff;
126
+ --admin-on-action-primary: #111318;
127
+ --admin-action-focus: rgba(229, 231, 235, 0.28);
128
+ --admin-selection: #3a3d46;
129
+ --admin-selection-hover: #454953;
130
+ --admin-on-selection: #f0f1f3;
131
+ --admin-selection-border: #777e8b;
132
+ --admin-control-accent: #c3c8d1;
133
+ --admin-control-on: #1a1c22;
134
+ --admin-icon-container: #30343d;
135
+ --admin-icon-color: #d6d9df;
136
+ --audit-create-bg: #164e3b;
137
+ --audit-create-text: #a7f3d0;
138
+ --audit-update-bg: #37366d;
139
+ --audit-update-text: #d8d7ff;
140
+ --audit-delete-bg: #692d32;
141
+ --audit-delete-text: #fecaca;
142
+
143
+ --m3-up: #4ade80;
144
+ --m3-up-bg: rgba(74, 222, 128, 0.12);
145
+ --m3-up-bar: #4ade80;
146
+ --m3-down: #ffb4ab;
147
+ --m3-down-bg: rgba(147, 0, 10, 0.25);
148
+ --m3-degraded: #ffb950;
149
+ --m3-degraded-bg: rgba(234, 179, 8, 0.15);
150
+ --m3-degraded-bar: #fbbf24;
151
+ --m3-partial: #fb923c;
152
+ --m3-partial-bg: rgba(249, 115, 22, 0.15);
153
+ --m3-partial-bar: #f97316;
154
+ }
155
+
156
+ /* ── Base ─────────────────────────────────────────────────────────── */
157
+ * { box-sizing: border-box; }
158
+
159
+ body {
160
+ margin: 0;
161
+ font-family: 'Inter', system-ui, sans-serif;
162
+ background: var(--m3-surface);
163
+ color: var(--m3-on-surface);
164
+ }
165
+
166
+ input[type='checkbox'],
167
+ input[type='radio'] {
168
+ accent-color: var(--admin-control-accent);
169
+ }
170
+
171
+ /*
172
+ * Button system. Compose a shape with a variant: `btn btn-primary`, `btn btn-secondary`,
173
+ * `btn btn-ghost`, `btn btn-outline`, `btn btn-danger` (filled, for confirmations) or
174
+ * `btn btn-danger-outline`; add `btn-sm` for compact rows.
175
+ * `btn-icon` is a standalone square icon button. These live in the components layer so page-level
176
+ * utilities (e.g. `px-5`, `rounded-full`, `w-full`) still win.
177
+ */
178
+ @layer components {
179
+ .btn {
180
+ display: inline-flex;
181
+ align-items: center;
182
+ justify-content: center;
183
+ gap: 0.5rem;
184
+ border: 1px solid transparent;
185
+ border-radius: 0.75rem;
186
+ padding: 0.625rem 1rem;
187
+ font-size: 0.875rem;
188
+ line-height: 1.25rem;
189
+ font-weight: 600;
190
+ white-space: nowrap;
191
+ cursor: pointer;
192
+ transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease,
193
+ box-shadow 0.18s ease, opacity 0.18s ease, transform 0.18s ease;
194
+ }
195
+ .btn:active:not(:disabled) { transform: scale(0.98); }
196
+ .btn:disabled { opacity: 0.5; cursor: not-allowed; }
197
+ .btn .material-symbols-outlined { font-size: 18px; }
198
+
199
+ .btn-sm {
200
+ gap: 0.375rem;
201
+ border-radius: 0.5rem;
202
+ padding: 0.375rem 0.75rem;
203
+ font-size: 0.75rem;
204
+ line-height: 1rem;
205
+ }
206
+ .btn-sm .material-symbols-outlined { font-size: 16px; }
207
+
208
+ /* Outlined in light mode so the filled primary button is the only filled action. */
209
+ .btn-secondary {
210
+ background: transparent;
211
+ border-color: var(--m3-outline-variant);
212
+ color: var(--m3-on-surface);
213
+ }
214
+ .btn-secondary:hover:not(:disabled) { background: var(--m3-surface-container); }
215
+ html.dark .btn-secondary {
216
+ background: var(--m3-surface-container-high);
217
+ border-color: transparent;
218
+ }
219
+ html.dark .btn-secondary:hover:not(:disabled) { background: var(--m3-surface-container-highest); }
220
+
221
+ .btn-ghost {
222
+ background: transparent;
223
+ color: var(--m3-secondary);
224
+ }
225
+ .btn-ghost:hover:not(:disabled) {
226
+ background: var(--m3-surface-container);
227
+ color: var(--m3-on-surface);
228
+ }
229
+
230
+ .btn-outline {
231
+ background: transparent;
232
+ color: var(--m3-secondary);
233
+ border-color: var(--m3-outline-variant);
234
+ }
235
+ .btn-outline:hover:not(:disabled) {
236
+ background: var(--m3-surface-container);
237
+ color: var(--m3-on-surface);
238
+ }
239
+
240
+ .btn-danger {
241
+ background: var(--m3-error-container);
242
+ color: var(--m3-on-error-container);
243
+ border-color: color-mix(in srgb, var(--m3-error) 45%, transparent);
244
+ }
245
+ .btn-danger:hover:not(:disabled) {
246
+ border-color: var(--m3-error);
247
+ }
248
+ .btn-danger:focus-visible {
249
+ outline: none;
250
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--m3-down) 25%, transparent);
251
+ }
252
+
253
+ .btn-icon {
254
+ display: inline-flex;
255
+ align-items: center;
256
+ justify-content: center;
257
+ flex-shrink: 0;
258
+ width: 2rem;
259
+ height: 2rem;
260
+ border-radius: 0.5rem;
261
+ color: var(--m3-secondary);
262
+ background: transparent;
263
+ cursor: pointer;
264
+ transition: background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
265
+ }
266
+ .btn-icon:hover:not(:disabled) {
267
+ background: var(--m3-surface-container);
268
+ color: var(--m3-on-surface);
269
+ }
270
+ .btn-icon:disabled { opacity: 0.5; cursor: not-allowed; }
271
+ .btn-icon .material-symbols-outlined { font-size: 20px; }
272
+
273
+ .focus-ring:focus-visible,
274
+ .btn-secondary:focus-visible,
275
+ .btn-ghost:focus-visible,
276
+ .btn-outline:focus-visible,
277
+ .btn-icon:focus-visible {
278
+ outline: none;
279
+ box-shadow: 0 0 0 3px var(--admin-action-focus);
280
+ }
281
+ }
282
+
283
+ /* Primary admin action: one contrast-safe appearance in every feature. */
284
+ .btn-primary {
285
+ background: var(--admin-action-primary) !important;
286
+ color: var(--admin-on-action-primary) !important;
287
+ border-color: transparent !important;
288
+ transition: background-color 0.18s ease, box-shadow 0.18s ease,
289
+ opacity 0.18s ease, transform 0.18s ease;
290
+ }
291
+ .btn-primary:hover:not(:disabled) {
292
+ background: var(--admin-action-primary-hover) !important;
293
+ }
294
+ .btn-primary:focus-visible {
295
+ outline: none;
296
+ box-shadow: 0 0 0 3px var(--admin-action-focus);
297
+ }
298
+ .btn-primary:disabled {
299
+ opacity: 0.5 !important;
300
+ cursor: not-allowed !important;
301
+ }
302
+
303
+ .btn-danger-outline {
304
+ color: var(--m3-down);
305
+ border: 1px solid var(--m3-outline-variant);
306
+ transition: background-color 0.18s ease, border-color 0.18s ease,
307
+ box-shadow 0.18s ease, transform 0.18s ease;
308
+ }
309
+ .btn-danger-outline:hover:not(:disabled) {
310
+ background: var(--m3-down-bg);
311
+ border-color: color-mix(in srgb, var(--m3-down) 45%, var(--m3-outline-variant));
312
+ }
313
+ .btn-danger-outline:focus-visible {
314
+ outline: none;
315
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--m3-down) 25%, transparent);
316
+ }
317
+ .btn-danger-outline:active:not(:disabled) {
318
+ transform: scale(0.98);
319
+ }
320
+ .btn-danger-outline:disabled {
321
+ cursor: not-allowed;
322
+ opacity: 0.5;
323
+ }
324
+
325
+ .copy-feedback-button {
326
+ background: var(--m3-surface-container-high);
327
+ color: var(--m3-on-surface);
328
+ border: 1px solid var(--m3-outline-variant);
329
+ transition: background-color 0.18s ease, border-color 0.18s ease,
330
+ color 0.18s ease, transform 0.18s ease;
331
+ }
332
+ .copy-feedback-button:hover {
333
+ background: var(--m3-surface-container-highest);
334
+ }
335
+ .copy-feedback-button[data-copied] {
336
+ background: var(--m3-up-bg);
337
+ border-color: color-mix(in srgb, var(--m3-up) 35%, var(--m3-outline-variant));
338
+ color: var(--m3-up);
339
+ }
340
+ .copy-feedback-button[data-failed] {
341
+ background: var(--m3-down-bg);
342
+ border-color: color-mix(in srgb, var(--m3-down) 35%, var(--m3-outline-variant));
343
+ color: var(--m3-down);
344
+ }
345
+ .copy-feedback-button:focus-visible {
346
+ outline: none;
347
+ box-shadow: 0 0 0 3px var(--admin-action-focus);
348
+ }
349
+ .copy-feedback-button:active {
350
+ transform: scale(0.98);
351
+ }
352
+
353
+ .selection-active {
354
+ background: var(--admin-selection) !important;
355
+ color: var(--admin-on-selection) !important;
356
+ border-color: var(--admin-selection-border) !important;
357
+ }
358
+ .selection-active:hover {
359
+ background: var(--admin-selection-hover) !important;
360
+ }
361
+
362
+ html.dark .side-tab-active {
363
+ background: var(--admin-selection) !important;
364
+ color: var(--admin-on-selection) !important;
365
+ }
366
+
367
+ html.dark .side-tab-badge-active {
368
+ background: var(--admin-on-selection) !important;
369
+ color: var(--admin-selection) !important;
370
+ }
371
+
372
+ /* ── Navigation (sidebar + mobile drawer) ─────────────────────────── */
373
+ .admin-nav-item {
374
+ display: flex;
375
+ width: 100%;
376
+ align-items: center;
377
+ gap: 0.75rem;
378
+ padding: 0.625rem 1rem;
379
+ border-radius: 0.75rem;
380
+ font-size: 0.875rem;
381
+ line-height: 1.25rem;
382
+ color: var(--m3-secondary);
383
+ text-align: left;
384
+ transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
385
+ }
386
+ .admin-nav-item:hover { transform: translateX(0.25rem); }
387
+ .admin-nav-item:not([aria-current='page']):hover {
388
+ background: rgba(0, 0, 0, 0.04);
389
+ color: var(--m3-on-surface);
390
+ }
391
+ html.dark .admin-nav-item:not([aria-current='page']):hover { background: rgba(255, 255, 255, 0.08); }
392
+ .admin-nav-item[aria-current='page'] {
393
+ background: var(--m3-surface-container-lowest);
394
+ color: var(--m3-on-surface);
395
+ font-weight: 600;
396
+ box-shadow: 0 1px 4px rgba(19, 27, 46, 0.08);
397
+ }
398
+ .admin-nav-item.admin-nav-danger:hover,
399
+ html.dark .admin-nav-item.admin-nav-danger:hover {
400
+ background: var(--m3-error-container);
401
+ color: var(--m3-on-error-container);
402
+ }
403
+ .admin-nav-item:focus-visible {
404
+ outline: none;
405
+ box-shadow: 0 0 0 3px var(--admin-action-focus);
406
+ }
407
+
408
+ @keyframes drawer-in {
409
+ from { transform: translateX(-100%); }
410
+ to { transform: translateX(0); }
411
+ }
412
+ @keyframes overlay-in {
413
+ from { opacity: 0; }
414
+ to { opacity: 1; }
415
+ }
416
+ .admin-drawer-panel { animation: drawer-in 0.22s ease-out; }
417
+ .admin-drawer-overlay { animation: overlay-in 0.22s ease-out; }
418
+
419
+ @keyframes toast-in {
420
+ from { opacity: 0; transform: translateY(8px); }
421
+ to { opacity: 1; transform: translateY(0); }
422
+ }
423
+ .admin-toast { animation: toast-in 0.2s ease-out; }
424
+
425
+ @media (prefers-reduced-motion: reduce) {
426
+ .admin-drawer-panel, .admin-drawer-overlay, .admin-toast { animation: none; }
427
+ .admin-nav-item:hover { transform: none; }
428
+ }
429
+
430
+ /* ── Input style ──────────────────────────────────────────────────── */
431
+ .input-m3 {
432
+ width: 100%;
433
+ background: var(--m3-surface-container-lowest);
434
+ border: 1.5px solid var(--m3-outline-variant);
435
+ border-radius: 10px;
436
+ padding: 9px 13px;
437
+ color: var(--m3-on-surface);
438
+ font-family: 'Inter', system-ui, sans-serif;
439
+ font-size: 14px;
440
+ outline: none;
441
+ transition: border-color 0.18s ease, box-shadow 0.18s ease;
442
+ }
443
+ .input-m3::placeholder { color: var(--m3-secondary); }
444
+ .input-m3:focus {
445
+ border-color: var(--m3-primary);
446
+ box-shadow: 0 0 0 3px rgba(0, 83, 219, 0.12);
447
+ }
448
+ html.dark .input-m3:focus {
449
+ box-shadow: 0 0 0 3px rgba(180, 197, 255, 0.15);
450
+ }
451
+ .input-m3:disabled { opacity: 0.45; cursor: not-allowed; }
452
+ select.input-m3 {
453
+ appearance: none;
454
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2376777d' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
455
+ background-repeat: no-repeat;
456
+ background-position: right 12px center;
457
+ padding-right: 32px;
458
+ }
459
+
460
+ input.input-m3:is([type='date'], [type='time'], [type='datetime-local']),
461
+ input.input-sig:is([type='date'], [type='time'], [type='datetime-local']) {
462
+ color-scheme: light;
463
+ }
464
+
465
+ html.dark input.input-m3:is([type='date'], [type='time'], [type='datetime-local']),
466
+ html.dark input.input-sig:is([type='date'], [type='time'], [type='datetime-local']) {
467
+ color-scheme: dark;
468
+ }
469
+
470
+ input.input-m3:is([type='date'], [type='time'], [type='datetime-local'])::-webkit-calendar-picker-indicator,
471
+ input.input-sig:is([type='date'], [type='time'], [type='datetime-local'])::-webkit-calendar-picker-indicator {
472
+ cursor: pointer;
473
+ opacity: 1;
474
+ }
475
+
476
+ /* Legacy alias */
477
+ .input-sig { @apply input-m3; }
478
+
479
+ /* ── Entrance animation ───────────────────────────────────────────── */
480
+ @keyframes fadeUp {
481
+ from { opacity: 0; transform: translateY(10px); }
482
+ to { opacity: 1; transform: translateY(0); }
483
+ }
484
+ .fade-up { opacity: 0; animation: fadeUp 0.4s ease forwards; }
485
+
486
+ /* ── Card hover ───────────────────────────────────────────────────── */
487
+ .card-hover {
488
+ transition: box-shadow 0.2s ease, border-color 0.2s ease;
489
+ }
490
+ .card-hover:hover {
491
+ box-shadow: 0 2px 12px rgba(0, 83, 219, 0.08);
492
+ border-color: var(--m3-outline);
493
+ }
494
+ html.dark .card-hover:hover {
495
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
496
+ }
497
+
498
+ /* ── Material Symbols ─────────────────────────────────────────────── */
499
+ .material-symbols-outlined {
500
+ font-family: 'Material Symbols Outlined';
501
+ font-weight: normal;
502
+ font-style: normal;
503
+ display: inline-block;
504
+ letter-spacing: normal;
505
+ text-transform: none;
506
+ white-space: nowrap;
507
+ word-wrap: normal;
508
+ direction: ltr;
509
+ font-feature-settings: 'liga';
510
+ -webkit-font-smoothing: antialiased;
511
+ font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
512
+ font-size: inherit;
513
+ line-height: 1;
514
+ vertical-align: middle;
515
+ }
516
+
517
+ /* ── Scrollbar ────────────────────────────────────────────────────── */
518
+ ::-webkit-scrollbar { width: 6px; }
519
+ ::-webkit-scrollbar-track { background: transparent; }
520
+ ::-webkit-scrollbar-thumb { background: var(--m3-outline-variant); border-radius: 3px; }
521
+