@lovett/ui 0.0.5 → 0.0.7
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/dist/index.d.ts +279 -115
- package/dist/index.js +479 -66
- package/dist/index.js.map +1 -1
- package/dist/styles.css +74 -0
- package/dist/tokens.css +181 -60
- package/package.json +16 -4
- package/src/__tests__/button.test.tsx +137 -0
- package/src/__tests__/card.test.tsx +103 -0
- package/src/__tests__/dead-render.test.tsx +117 -0
- package/src/__tests__/input.test.tsx +134 -0
- package/src/__tests__/modal.test.tsx +154 -0
- package/src/__tests__/page-shell.test.tsx +128 -0
- package/src/__tests__/setup.ts +43 -0
- package/src/__tests__/token-shape.test.ts +193 -0
- package/src/card.tsx +1 -1
- package/src/collapsible-card.tsx +85 -0
- package/src/data-grid/table-body.tsx +8 -1
- package/src/dropdown-menu.tsx +1 -1
- package/src/floating-status-bar.tsx +1 -1
- package/src/folder-tree-picker.tsx +5 -6
- package/src/frame-stack.tsx +27 -10
- package/src/hero-form-card.tsx +2 -2
- package/src/icons/brand.tsx +187 -0
- package/src/index.ts +32 -0
- package/src/lib/clipboard.ts +14 -0
- package/src/lib/color.ts +111 -0
- package/src/meta-cell.tsx +52 -0
- package/src/meta-previews/MetaFeedCarousel.tsx +1 -1
- package/src/meta-previews/MetaFeedPreview.tsx +1 -1
- package/src/modal.tsx +77 -6
- package/src/pill-button.tsx +23 -5
- package/src/profile-section.tsx +40 -9
- package/src/sortable-table.tsx +5 -1
- package/src/styles.css +74 -0
- package/src/tabs.tsx +4 -0
- package/src/tag-chip-input.tsx +1 -1
- package/src/theme-v2.css +466 -0
- package/src/tokens.css +181 -60
- package/src/v2/README.md +208 -0
- package/src/v2/__demo__/showcase.tsx +1045 -0
- package/src/v2/action.tsx +91 -0
- package/src/v2/callout.tsx +76 -0
- package/src/v2/document-section.tsx +82 -0
- package/src/v2/document-shell.tsx +0 -0
- package/src/v2/field-row.tsx +113 -0
- package/src/v2/icons.tsx +165 -0
- package/src/v2/index.ts +147 -0
- package/src/v2/layout.tsx +293 -0
- package/src/v2/progress-track.tsx +89 -0
- package/src/v2/stat-tile.tsx +129 -0
- package/src/v2/states.tsx +271 -0
- package/src/v2/status-pill.tsx +74 -0
- package/src/v2/theme.css +1861 -0
- package/src/v2/timeline.tsx +81 -0
- package/src/v2/tokens.ts +228 -0
- package/src/value-chip.tsx +76 -0
package/src/v2/theme.css
ADDED
|
@@ -0,0 +1,1861 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @lovett/ui v2 — theme.css
|
|
3
|
+
* =========================
|
|
4
|
+
*
|
|
5
|
+
* The v2 design layer. Everything here is namespaced `--lv-*` and `.lv-*`,
|
|
6
|
+
* so this file can load alongside v1's `tokens.css` / `styles.css` without
|
|
7
|
+
* touching a single v1 token. v1 keeps working byte-identically.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* import '@lovett/ui/src/v2/theme.css'
|
|
11
|
+
* <div className="lv-root"> … </div>
|
|
12
|
+
*
|
|
13
|
+
* Appearance:
|
|
14
|
+
* - default follows the OS (`prefers-color-scheme`)
|
|
15
|
+
* - <html data-theme="dark"> forces dark
|
|
16
|
+
* - <html data-theme="light"> forces light
|
|
17
|
+
* Both directions win over the OS, in both directions.
|
|
18
|
+
*
|
|
19
|
+
* Colour is authored in OKLCH. Ramps are constant-hue; chroma is a
|
|
20
|
+
* percentage of each hue's sRGB maximum at that lightness, so no step
|
|
21
|
+
* clips and no hue outranks another by accident.
|
|
22
|
+
*
|
|
23
|
+
* The surface ladder is the spine of the whole system:
|
|
24
|
+
*
|
|
25
|
+
* page → shell → section → card → [inset]
|
|
26
|
+
* ────────────── brighter ──────────────► ◄─ recessed
|
|
27
|
+
*
|
|
28
|
+
* Each tier is one notch brighter than the one behind it. The `inset`
|
|
29
|
+
* tier deliberately goes the other way: a recessed panel *inside* a card
|
|
30
|
+
* is the single most premium move in the system, and recession is what
|
|
31
|
+
* makes it read as recessed.
|
|
32
|
+
*
|
|
33
|
+
* Depth is shadow + a top-edge inner highlight. It is never a 1px border:
|
|
34
|
+
* a hairline used for depth reads as a form field. Hairlines here are
|
|
35
|
+
* strictly structural (dividers, table rules, field outlines).
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
39
|
+
1. RAMPS — appearance-independent. Never consume these directly in a
|
|
40
|
+
component; consume the semantic tokens in §2.
|
|
41
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
42
|
+
|
|
43
|
+
.lv-root,
|
|
44
|
+
:root {
|
|
45
|
+
/* ── Neutral. Hue 255 (navy-tinted), chroma held under 0.023 so it
|
|
46
|
+
never reads as "blue", only as "not-quite-black". A pure-grey ramp
|
|
47
|
+
goes dead next to a warm accent; this one stays alive. ────────── */
|
|
48
|
+
--lv-n-0: oklch(1 0 0);
|
|
49
|
+
--lv-n-25: oklch(0.988 0.002 255);
|
|
50
|
+
--lv-n-50: oklch(0.976 0.003 255);
|
|
51
|
+
--lv-n-100: oklch(0.955 0.005 255);
|
|
52
|
+
--lv-n-150: oklch(0.932 0.006 255);
|
|
53
|
+
--lv-n-200: oklch(0.906 0.008 255);
|
|
54
|
+
--lv-n-300: oklch(0.851 0.011 255);
|
|
55
|
+
--lv-n-400: oklch(0.715 0.017 255);
|
|
56
|
+
--lv-n-500: oklch(0.585 0.021 255);
|
|
57
|
+
--lv-n-600: oklch(0.487 0.022 255);
|
|
58
|
+
--lv-n-700: oklch(0.396 0.022 255);
|
|
59
|
+
--lv-n-750: oklch(0.342 0.022 255);
|
|
60
|
+
--lv-n-800: oklch(0.287 0.021 255);
|
|
61
|
+
--lv-n-850: oklch(0.236 0.019 255);
|
|
62
|
+
--lv-n-900: oklch(0.192 0.017 255);
|
|
63
|
+
--lv-n-950: oklch(0.148 0.014 255);
|
|
64
|
+
--lv-n-975: oklch(0.115 0.012 255);
|
|
65
|
+
--lv-n-1000: oklch(0.09 0.01 255);
|
|
66
|
+
|
|
67
|
+
/* ── Accent. The house red, hue 27.5. sRGB pure red is
|
|
68
|
+
oklch(0.628 0.258 29.2), so the ramp peaks at 500 and every step
|
|
69
|
+
sits at ~85% of that hue's maximum chroma for its lightness. ──── */
|
|
70
|
+
--lv-a-50: oklch(0.972 0.012 27.5);
|
|
71
|
+
--lv-a-100: oklch(0.94 0.026 27.5);
|
|
72
|
+
--lv-a-200: oklch(0.885 0.052 27.5);
|
|
73
|
+
--lv-a-300: oklch(0.806 0.098 27.5);
|
|
74
|
+
--lv-a-400: oklch(0.707 0.17 27.5);
|
|
75
|
+
--lv-a-500: oklch(0.628 0.216 27.5);
|
|
76
|
+
--lv-a-600: oklch(0.554 0.204 27.5);
|
|
77
|
+
--lv-a-700: oklch(0.47 0.176 27.5);
|
|
78
|
+
--lv-a-800: oklch(0.385 0.142 27.5);
|
|
79
|
+
--lv-a-900: oklch(0.305 0.108 27.5);
|
|
80
|
+
--lv-a-950: oklch(0.222 0.074 27.5);
|
|
81
|
+
|
|
82
|
+
/* ── Semantic hues. Held far apart on the wheel so no two states can
|
|
83
|
+
be confused at a glance, and every one of them is ALSO carried by
|
|
84
|
+
an icon and a word in the components that use them.
|
|
85
|
+
|
|
86
|
+
success 150° green
|
|
87
|
+
warning 95° gold
|
|
88
|
+
danger 48° burnt orange
|
|
89
|
+
info 290° violet
|
|
90
|
+
|
|
91
|
+
danger is not red. The brand IS red, and a red brand plus a red
|
|
92
|
+
danger state cannot be told apart when both appear on one screen.
|
|
93
|
+
Moving the meaning is the only fix that works — see README. ──── */
|
|
94
|
+
--lv-ok-text-l: oklch(0.46 0.115 150);
|
|
95
|
+
--lv-ok-text-d: oklch(0.8 0.14 150);
|
|
96
|
+
--lv-ok-mark-l: oklch(0.58 0.145 150);
|
|
97
|
+
--lv-ok-mark-d: oklch(0.72 0.15 150);
|
|
98
|
+
|
|
99
|
+
--lv-wa-text-l: oklch(0.46 0.09 95);
|
|
100
|
+
--lv-wa-text-d: oklch(0.82 0.13 95);
|
|
101
|
+
--lv-wa-mark-l: oklch(0.62 0.115 95);
|
|
102
|
+
--lv-wa-mark-d: oklch(0.76 0.14 95);
|
|
103
|
+
|
|
104
|
+
--lv-da-text-l: oklch(0.47 0.11 48);
|
|
105
|
+
--lv-da-text-d: oklch(0.8 0.13 48);
|
|
106
|
+
--lv-da-mark-l: oklch(0.58 0.135 48);
|
|
107
|
+
--lv-da-mark-d: oklch(0.73 0.15 48);
|
|
108
|
+
|
|
109
|
+
--lv-in-text-l: oklch(0.47 0.19 290);
|
|
110
|
+
--lv-in-text-d: oklch(0.78 0.14 290);
|
|
111
|
+
--lv-in-mark-l: oklch(0.55 0.21 290);
|
|
112
|
+
--lv-in-mark-d: oklch(0.7 0.17 290);
|
|
113
|
+
|
|
114
|
+
/* ── Categorical series. Constant L (0.60) so no series outranks
|
|
115
|
+
another; chroma is per-hue so each is equally vivid rather than
|
|
116
|
+
equally saturated. Ships for charts; never used for state. ───── */
|
|
117
|
+
--lv-series-1: oklch(0.6 0.2 27.5);
|
|
118
|
+
--lv-series-2: oklch(0.6 0.19 290);
|
|
119
|
+
--lv-series-3: oklch(0.6 0.145 150);
|
|
120
|
+
--lv-series-4: oklch(0.6 0.115 95);
|
|
121
|
+
--lv-series-5: oklch(0.6 0.16 245);
|
|
122
|
+
--lv-series-6: oklch(0.6 0.1 195);
|
|
123
|
+
--lv-series-7: oklch(0.6 0.19 340);
|
|
124
|
+
--lv-series-8: oklch(0.6 0.13 128);
|
|
125
|
+
|
|
126
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
127
|
+
SPACING — one 4px scale, honoured everywhere. Off-scale one-off
|
|
128
|
+
values are the single most common reason a nice UI feels "off".
|
|
129
|
+
══════════════════════════════════════════════════════════════════ */
|
|
130
|
+
--lv-space-0: 0px;
|
|
131
|
+
--lv-space-1: 4px;
|
|
132
|
+
--lv-space-2: 8px;
|
|
133
|
+
--lv-space-3: 12px;
|
|
134
|
+
--lv-space-4: 16px;
|
|
135
|
+
--lv-space-5: 20px;
|
|
136
|
+
--lv-space-6: 24px;
|
|
137
|
+
--lv-space-7: 28px;
|
|
138
|
+
--lv-space-8: 32px;
|
|
139
|
+
--lv-space-10: 40px;
|
|
140
|
+
--lv-space-12: 48px;
|
|
141
|
+
--lv-space-14: 56px;
|
|
142
|
+
--lv-space-16: 64px;
|
|
143
|
+
--lv-space-20: 80px;
|
|
144
|
+
--lv-space-24: 96px;
|
|
145
|
+
|
|
146
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
147
|
+
RADIUS — sharp edges. Concentric by construction: a card at `lg`
|
|
148
|
+
(10px) carries a 4px frame, so its tray lands on `md` (6px).
|
|
149
|
+
outer = inner + padding, in both directions.
|
|
150
|
+
══════════════════════════════════════════════════════════════════ */
|
|
151
|
+
--lv-radius-xs: 3px;
|
|
152
|
+
--lv-radius-sm: 4px;
|
|
153
|
+
--lv-radius-md: 6px;
|
|
154
|
+
--lv-radius-lg: 10px;
|
|
155
|
+
--lv-radius-xl: 14px;
|
|
156
|
+
--lv-radius-full: 9999px;
|
|
157
|
+
|
|
158
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
159
|
+
TYPE — named by role, not by size. Deviate from this set as little
|
|
160
|
+
as possible; hard-coded sizes are how a scale dies.
|
|
161
|
+
══════════════════════════════════════════════════════════════════ */
|
|
162
|
+
--lv-font-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Inter,
|
|
163
|
+
'Helvetica Neue', Arial, sans-serif;
|
|
164
|
+
--lv-font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
|
|
165
|
+
'Liberation Mono', monospace;
|
|
166
|
+
|
|
167
|
+
/* Small uppercase labels get positive tracking; large display type
|
|
168
|
+
gets negative. Body copy at reading size gets neither. */
|
|
169
|
+
--lv-text-2xs: 11px;
|
|
170
|
+
--lv-lh-2xs: 1.45;
|
|
171
|
+
--lv-tr-2xs: 0.06em;
|
|
172
|
+
|
|
173
|
+
--lv-text-xs: 12px;
|
|
174
|
+
--lv-lh-xs: 1.45;
|
|
175
|
+
--lv-tr-xs: 0.01em;
|
|
176
|
+
|
|
177
|
+
--lv-text-sm: 13px;
|
|
178
|
+
--lv-lh-sm: 1.5;
|
|
179
|
+
--lv-tr-sm: 0em;
|
|
180
|
+
|
|
181
|
+
--lv-text-base: 14px;
|
|
182
|
+
--lv-lh-base: 1.55;
|
|
183
|
+
--lv-tr-base: 0em;
|
|
184
|
+
|
|
185
|
+
--lv-text-md: 15px;
|
|
186
|
+
--lv-lh-md: 1.6;
|
|
187
|
+
--lv-tr-md: 0em;
|
|
188
|
+
|
|
189
|
+
--lv-text-lg: 17px;
|
|
190
|
+
--lv-lh-lg: 1.4;
|
|
191
|
+
--lv-tr-lg: -0.006em;
|
|
192
|
+
|
|
193
|
+
--lv-text-xl: 20px;
|
|
194
|
+
--lv-lh-xl: 1.3;
|
|
195
|
+
--lv-tr-xl: -0.012em;
|
|
196
|
+
|
|
197
|
+
--lv-text-2xl: 25px;
|
|
198
|
+
--lv-lh-2xl: 1.24;
|
|
199
|
+
--lv-tr-2xl: -0.016em;
|
|
200
|
+
|
|
201
|
+
--lv-text-3xl: 31px;
|
|
202
|
+
--lv-lh-3xl: 1.18;
|
|
203
|
+
--lv-tr-3xl: -0.02em;
|
|
204
|
+
|
|
205
|
+
/* Stat display — always tabular, always in a reserved slot. */
|
|
206
|
+
--lv-text-stat: 28px;
|
|
207
|
+
--lv-lh-stat: 1.1;
|
|
208
|
+
--lv-tr-stat: -0.02em;
|
|
209
|
+
|
|
210
|
+
/* Long-form prose. Briefs and dossiers are read, not scanned. */
|
|
211
|
+
--lv-prose-size: 16px;
|
|
212
|
+
--lv-prose-lh: 1.62;
|
|
213
|
+
--lv-measure: 68ch;
|
|
214
|
+
|
|
215
|
+
--lv-weight-normal: 400;
|
|
216
|
+
--lv-weight-medium: 500;
|
|
217
|
+
--lv-weight-semibold: 600;
|
|
218
|
+
--lv-weight-bold: 680;
|
|
219
|
+
|
|
220
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
221
|
+
MOTION — spring on small, ease on large. Every duration is a token
|
|
222
|
+
so `prefers-reduced-motion` can collapse all of them at once.
|
|
223
|
+
══════════════════════════════════════════════════════════════════ */
|
|
224
|
+
--lv-dur-instant: 80ms;
|
|
225
|
+
--lv-dur-fast: 140ms;
|
|
226
|
+
--lv-dur-base: 200ms;
|
|
227
|
+
--lv-dur-slow: 320ms;
|
|
228
|
+
--lv-ease-out: cubic-bezier(0.2, 0, 0, 1);
|
|
229
|
+
--lv-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
|
230
|
+
/* Small elements only — buttons, toggles, badges. Never on a panel,
|
|
231
|
+
never on a data value: overshoot on a number reads as imprecise. */
|
|
232
|
+
--lv-ease-spring: cubic-bezier(0.34, 1.36, 0.64, 1);
|
|
233
|
+
|
|
234
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
235
|
+
LAYOUT CONSTANTS — mirrored in tokens.ts for JS that needs numbers
|
|
236
|
+
(IntersectionObserver rootMargin, sticky offsets).
|
|
237
|
+
══════════════════════════════════════════════════════════════════ */
|
|
238
|
+
--lv-doc-header-h: 56px;
|
|
239
|
+
--lv-doc-nav-w: 224px;
|
|
240
|
+
--lv-scroll-offset: 88px;
|
|
241
|
+
--lv-dl-label-w: 184px;
|
|
242
|
+
--lv-state-min-h: 216px;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
246
|
+
2. SEMANTIC TOKENS — LIGHT
|
|
247
|
+
These are what components consume. Nothing below §2 touches a ramp.
|
|
248
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
249
|
+
|
|
250
|
+
.lv-root,
|
|
251
|
+
:root {
|
|
252
|
+
color-scheme: light dark;
|
|
253
|
+
|
|
254
|
+
/* Surface ladder — page is the floor, card is the top, inset digs. */
|
|
255
|
+
--lv-surface-page: var(--lv-n-100);
|
|
256
|
+
--lv-surface-shell: var(--lv-n-50);
|
|
257
|
+
--lv-surface-section: var(--lv-n-25);
|
|
258
|
+
--lv-surface-card: var(--lv-n-0);
|
|
259
|
+
--lv-surface-inset: oklch(0.964 0.006 255);
|
|
260
|
+
--lv-surface-raised: var(--lv-n-0);
|
|
261
|
+
--lv-surface-overlay: oklch(0.2 0.017 255 / 0.42);
|
|
262
|
+
/* Ghost/hover wash. Tinted with the neutral hue, never pure black. */
|
|
263
|
+
--lv-surface-hover: oklch(0.585 0.021 255 / 0.06);
|
|
264
|
+
--lv-surface-active: oklch(0.585 0.021 255 / 0.11);
|
|
265
|
+
--lv-surface-selected: oklch(0.628 0.216 27.5 / 0.07);
|
|
266
|
+
|
|
267
|
+
/* Ink. Type carries hierarchy before colour does, so there are only
|
|
268
|
+
four steps here — any more and weight/size stop doing their job. */
|
|
269
|
+
--lv-text-primary: var(--lv-n-950);
|
|
270
|
+
--lv-text-secondary: var(--lv-n-600);
|
|
271
|
+
--lv-text-tertiary: var(--lv-n-500);
|
|
272
|
+
--lv-text-faint: var(--lv-n-400);
|
|
273
|
+
--lv-text-on-accent: oklch(1 0 0);
|
|
274
|
+
--lv-text-disabled: var(--lv-n-400);
|
|
275
|
+
|
|
276
|
+
/* Hairlines — STRUCTURE ONLY. Depth is §elevation. */
|
|
277
|
+
--lv-hairline: oklch(0 0 0 / 0.07);
|
|
278
|
+
--lv-hairline-strong: oklch(0 0 0 / 0.12);
|
|
279
|
+
--lv-border-field: oklch(0 0 0 / 0.14);
|
|
280
|
+
|
|
281
|
+
/* Accent. Scarce by contract: one filled accent surface per view. */
|
|
282
|
+
--lv-accent: var(--lv-a-600);
|
|
283
|
+
--lv-accent-hover: var(--lv-a-700);
|
|
284
|
+
--lv-accent-active: var(--lv-a-800);
|
|
285
|
+
--lv-accent-text: var(--lv-a-600);
|
|
286
|
+
--lv-accent-mark: var(--lv-a-500);
|
|
287
|
+
--lv-accent-soft: oklch(0.628 0.216 27.5 / 0.1);
|
|
288
|
+
--lv-accent-rule: oklch(0.628 0.216 27.5 / 0.42);
|
|
289
|
+
--lv-focus: var(--lv-a-500);
|
|
290
|
+
|
|
291
|
+
/* Semantic states. `-text` is the AA-safe reading colour, `-mark` is
|
|
292
|
+
the dot/icon/track colour (larger, so it may be brighter). */
|
|
293
|
+
--lv-success-text: var(--lv-ok-text-l);
|
|
294
|
+
--lv-success-mark: var(--lv-ok-mark-l);
|
|
295
|
+
--lv-success-soft: oklch(0.58 0.145 150 / 0.1);
|
|
296
|
+
--lv-success-rule: oklch(0.58 0.145 150 / 0.5);
|
|
297
|
+
|
|
298
|
+
--lv-warning-text: var(--lv-wa-text-l);
|
|
299
|
+
--lv-warning-mark: var(--lv-wa-mark-l);
|
|
300
|
+
--lv-warning-soft: oklch(0.62 0.115 95 / 0.14);
|
|
301
|
+
--lv-warning-rule: oklch(0.62 0.115 95 / 0.55);
|
|
302
|
+
|
|
303
|
+
--lv-danger-text: var(--lv-da-text-l);
|
|
304
|
+
--lv-danger-mark: var(--lv-da-mark-l);
|
|
305
|
+
--lv-danger-soft: oklch(0.58 0.135 48 / 0.12);
|
|
306
|
+
--lv-danger-rule: oklch(0.58 0.135 48 / 0.55);
|
|
307
|
+
|
|
308
|
+
--lv-info-text: var(--lv-in-text-l);
|
|
309
|
+
--lv-info-mark: var(--lv-in-mark-l);
|
|
310
|
+
--lv-info-soft: oklch(0.55 0.21 290 / 0.1);
|
|
311
|
+
--lv-info-rule: oklch(0.55 0.21 290 / 0.45);
|
|
312
|
+
|
|
313
|
+
/* Elevation = shadow + a top-edge inner highlight. The highlight is
|
|
314
|
+
what separates "a box with a shadow" from "a lit surface". */
|
|
315
|
+
--lv-highlight: inset 0 1px 0 0 oklch(1 0 0 / 0.9);
|
|
316
|
+
--lv-elev-0: none;
|
|
317
|
+
--lv-elev-1: 0 1px 1px -0.5px oklch(0.2 0.02 255 / 0.06),
|
|
318
|
+
0 1px 2px -1px oklch(0.2 0.02 255 / 0.05), var(--lv-highlight);
|
|
319
|
+
--lv-elev-2: 0 1px 2px -1px oklch(0.2 0.02 255 / 0.08),
|
|
320
|
+
0 3px 6px -2px oklch(0.2 0.02 255 / 0.07), var(--lv-highlight);
|
|
321
|
+
--lv-elev-3: 0 2px 4px -2px oklch(0.2 0.02 255 / 0.09),
|
|
322
|
+
0 8px 18px -6px oklch(0.2 0.02 255 / 0.11), var(--lv-highlight);
|
|
323
|
+
--lv-elev-4: 0 4px 8px -4px oklch(0.2 0.02 255 / 0.1),
|
|
324
|
+
0 20px 44px -12px oklch(0.2 0.02 255 / 0.18), var(--lv-highlight);
|
|
325
|
+
/* The recessed panel. Inner shadow at the top edge + a whisper of a
|
|
326
|
+
containing ring. No outer shadow — it is a hole, not a block. */
|
|
327
|
+
--lv-elev-inset: inset 0 1px 2px 0 oklch(0.2 0.02 255 / 0.07),
|
|
328
|
+
inset 0 0 0 1px oklch(0 0 0 / 0.035);
|
|
329
|
+
|
|
330
|
+
--lv-skeleton-base: oklch(0.585 0.021 255 / 0.1);
|
|
331
|
+
--lv-skeleton-sheen: oklch(0.585 0.021 255 / 0.05);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
335
|
+
3. SEMANTIC TOKENS — DARK
|
|
336
|
+
Not a mechanical inversion. The ladder is rebuilt from different steps
|
|
337
|
+
of the same ramps, then every pair was re-checked: chroma comes UP on
|
|
338
|
+
accents (a light-mode accent goes muddy once its surround darkens) and
|
|
339
|
+
the layered depth shadows come DOWN to a single ring plus one drop,
|
|
340
|
+
because stacked soft shadows are invisible on a dark surface.
|
|
341
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
342
|
+
|
|
343
|
+
@media (prefers-color-scheme: dark) {
|
|
344
|
+
:root:not([data-theme='light']),
|
|
345
|
+
.lv-root:not([data-theme='light']),
|
|
346
|
+
/* A nested .lv-root re-declares the whole light ramp at its own specificity, so without
|
|
347
|
+
this it resets its subtree to light under an OS-dark root. */
|
|
348
|
+
:root:not([data-theme='light']) .lv-root:not([data-theme='light']) {
|
|
349
|
+
color-scheme: dark;
|
|
350
|
+
|
|
351
|
+
--lv-surface-page: var(--lv-n-975);
|
|
352
|
+
--lv-surface-shell: var(--lv-n-900);
|
|
353
|
+
--lv-surface-section: oklch(0.222 0.018 255);
|
|
354
|
+
--lv-surface-card: oklch(0.252 0.019 255);
|
|
355
|
+
--lv-surface-inset: oklch(0.178 0.016 255);
|
|
356
|
+
--lv-surface-raised: oklch(0.298 0.02 255);
|
|
357
|
+
--lv-surface-overlay: oklch(0.09 0.01 255 / 0.7);
|
|
358
|
+
--lv-surface-hover: oklch(1 0 0 / 0.055);
|
|
359
|
+
--lv-surface-active: oklch(1 0 0 / 0.09);
|
|
360
|
+
--lv-surface-selected: oklch(0.707 0.17 27.5 / 0.14);
|
|
361
|
+
|
|
362
|
+
--lv-text-primary: oklch(0.972 0.003 255);
|
|
363
|
+
--lv-text-secondary: oklch(0.78 0.012 255);
|
|
364
|
+
--lv-text-tertiary: oklch(0.655 0.015 255);
|
|
365
|
+
--lv-text-faint: oklch(0.545 0.016 255);
|
|
366
|
+
--lv-text-on-accent: oklch(1 0 0);
|
|
367
|
+
--lv-text-disabled: oklch(0.5 0.015 255);
|
|
368
|
+
|
|
369
|
+
--lv-hairline: oklch(1 0 0 / 0.09);
|
|
370
|
+
--lv-hairline-strong: oklch(1 0 0 / 0.16);
|
|
371
|
+
--lv-border-field: oklch(1 0 0 / 0.18);
|
|
372
|
+
|
|
373
|
+
--lv-accent: var(--lv-a-500);
|
|
374
|
+
--lv-accent-hover: var(--lv-a-400);
|
|
375
|
+
--lv-accent-active: var(--lv-a-300);
|
|
376
|
+
--lv-accent-text: var(--lv-a-400);
|
|
377
|
+
--lv-accent-mark: var(--lv-a-400);
|
|
378
|
+
--lv-accent-soft: oklch(0.707 0.17 27.5 / 0.16);
|
|
379
|
+
--lv-accent-rule: oklch(0.707 0.17 27.5 / 0.5);
|
|
380
|
+
--lv-focus: var(--lv-a-400);
|
|
381
|
+
|
|
382
|
+
--lv-success-text: var(--lv-ok-text-d);
|
|
383
|
+
--lv-success-mark: var(--lv-ok-mark-d);
|
|
384
|
+
--lv-success-soft: oklch(0.72 0.15 150 / 0.16);
|
|
385
|
+
--lv-success-rule: oklch(0.72 0.15 150 / 0.55);
|
|
386
|
+
|
|
387
|
+
--lv-warning-text: var(--lv-wa-text-d);
|
|
388
|
+
--lv-warning-mark: var(--lv-wa-mark-d);
|
|
389
|
+
--lv-warning-soft: oklch(0.76 0.14 95 / 0.16);
|
|
390
|
+
--lv-warning-rule: oklch(0.76 0.14 95 / 0.55);
|
|
391
|
+
|
|
392
|
+
--lv-danger-text: var(--lv-da-text-d);
|
|
393
|
+
--lv-danger-mark: var(--lv-da-mark-d);
|
|
394
|
+
--lv-danger-soft: oklch(0.73 0.15 48 / 0.16);
|
|
395
|
+
--lv-danger-rule: oklch(0.73 0.15 48 / 0.55);
|
|
396
|
+
|
|
397
|
+
--lv-info-text: var(--lv-in-text-d);
|
|
398
|
+
--lv-info-mark: var(--lv-in-mark-d);
|
|
399
|
+
--lv-info-soft: oklch(0.7 0.17 290 / 0.18);
|
|
400
|
+
--lv-info-rule: oklch(0.7 0.17 290 / 0.5);
|
|
401
|
+
|
|
402
|
+
--lv-series-1: oklch(0.72 0.17 27.5);
|
|
403
|
+
--lv-series-2: oklch(0.72 0.15 290);
|
|
404
|
+
--lv-series-3: oklch(0.72 0.15 150);
|
|
405
|
+
--lv-series-4: oklch(0.72 0.13 95);
|
|
406
|
+
--lv-series-5: oklch(0.72 0.14 245);
|
|
407
|
+
--lv-series-6: oklch(0.72 0.1 195);
|
|
408
|
+
--lv-series-7: oklch(0.72 0.16 340);
|
|
409
|
+
--lv-series-8: oklch(0.72 0.14 128);
|
|
410
|
+
|
|
411
|
+
--lv-highlight: inset 0 1px 0 0 oklch(1 0 0 / 0.055);
|
|
412
|
+
--lv-elev-0: none;
|
|
413
|
+
--lv-elev-1: 0 0 0 1px oklch(1 0 0 / 0.06), var(--lv-highlight);
|
|
414
|
+
--lv-elev-2: 0 0 0 1px oklch(1 0 0 / 0.07),
|
|
415
|
+
0 2px 6px -2px oklch(0 0 0 / 0.5), var(--lv-highlight);
|
|
416
|
+
--lv-elev-3: 0 0 0 1px oklch(1 0 0 / 0.08),
|
|
417
|
+
0 8px 20px -6px oklch(0 0 0 / 0.6), var(--lv-highlight);
|
|
418
|
+
--lv-elev-4: 0 0 0 1px oklch(1 0 0 / 0.1),
|
|
419
|
+
0 22px 48px -12px oklch(0 0 0 / 0.7), var(--lv-highlight);
|
|
420
|
+
--lv-elev-inset: inset 0 1px 3px 0 oklch(0 0 0 / 0.45),
|
|
421
|
+
inset 0 0 0 1px oklch(1 0 0 / 0.04);
|
|
422
|
+
|
|
423
|
+
--lv-skeleton-base: oklch(1 0 0 / 0.07);
|
|
424
|
+
--lv-skeleton-sheen: oklch(1 0 0 / 0.04);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* Explicit override. Duplicated on purpose — a media query and an
|
|
429
|
+
attribute selector cannot share a rule, and the attribute must win in
|
|
430
|
+
BOTH directions (forced dark under a light OS, forced light under a
|
|
431
|
+
dark OS). Keep the two dark blocks in sync. */
|
|
432
|
+
:root[data-theme='dark'],
|
|
433
|
+
.lv-root[data-theme='dark'],
|
|
434
|
+
/* Forced dark must reach nested .lv-root subtrees (artifact bodies, the gallery), which
|
|
435
|
+
otherwise stay light because .lv-root re-declares the light tokens unconditionally. */
|
|
436
|
+
:root[data-theme='dark'] .lv-root:not([data-theme='light']) {
|
|
437
|
+
color-scheme: dark;
|
|
438
|
+
|
|
439
|
+
--lv-surface-page: var(--lv-n-975);
|
|
440
|
+
--lv-surface-shell: var(--lv-n-900);
|
|
441
|
+
--lv-surface-section: oklch(0.222 0.018 255);
|
|
442
|
+
--lv-surface-card: oklch(0.252 0.019 255);
|
|
443
|
+
--lv-surface-inset: oklch(0.178 0.016 255);
|
|
444
|
+
--lv-surface-raised: oklch(0.298 0.02 255);
|
|
445
|
+
--lv-surface-overlay: oklch(0.09 0.01 255 / 0.7);
|
|
446
|
+
--lv-surface-hover: oklch(1 0 0 / 0.055);
|
|
447
|
+
--lv-surface-active: oklch(1 0 0 / 0.09);
|
|
448
|
+
--lv-surface-selected: oklch(0.707 0.17 27.5 / 0.14);
|
|
449
|
+
|
|
450
|
+
--lv-text-primary: oklch(0.972 0.003 255);
|
|
451
|
+
--lv-text-secondary: oklch(0.78 0.012 255);
|
|
452
|
+
--lv-text-tertiary: oklch(0.655 0.015 255);
|
|
453
|
+
--lv-text-faint: oklch(0.545 0.016 255);
|
|
454
|
+
--lv-text-on-accent: oklch(1 0 0);
|
|
455
|
+
--lv-text-disabled: oklch(0.5 0.015 255);
|
|
456
|
+
|
|
457
|
+
--lv-hairline: oklch(1 0 0 / 0.09);
|
|
458
|
+
--lv-hairline-strong: oklch(1 0 0 / 0.16);
|
|
459
|
+
--lv-border-field: oklch(1 0 0 / 0.18);
|
|
460
|
+
|
|
461
|
+
--lv-accent: var(--lv-a-500);
|
|
462
|
+
--lv-accent-hover: var(--lv-a-400);
|
|
463
|
+
--lv-accent-active: var(--lv-a-300);
|
|
464
|
+
--lv-accent-text: var(--lv-a-400);
|
|
465
|
+
--lv-accent-mark: var(--lv-a-400);
|
|
466
|
+
--lv-accent-soft: oklch(0.707 0.17 27.5 / 0.16);
|
|
467
|
+
--lv-accent-rule: oklch(0.707 0.17 27.5 / 0.5);
|
|
468
|
+
--lv-focus: var(--lv-a-400);
|
|
469
|
+
|
|
470
|
+
--lv-success-text: var(--lv-ok-text-d);
|
|
471
|
+
--lv-success-mark: var(--lv-ok-mark-d);
|
|
472
|
+
--lv-success-soft: oklch(0.72 0.15 150 / 0.16);
|
|
473
|
+
--lv-success-rule: oklch(0.72 0.15 150 / 0.55);
|
|
474
|
+
|
|
475
|
+
--lv-warning-text: var(--lv-wa-text-d);
|
|
476
|
+
--lv-warning-mark: var(--lv-wa-mark-d);
|
|
477
|
+
--lv-warning-soft: oklch(0.76 0.14 95 / 0.16);
|
|
478
|
+
--lv-warning-rule: oklch(0.76 0.14 95 / 0.55);
|
|
479
|
+
|
|
480
|
+
--lv-danger-text: var(--lv-da-text-d);
|
|
481
|
+
--lv-danger-mark: var(--lv-da-mark-d);
|
|
482
|
+
--lv-danger-soft: oklch(0.73 0.15 48 / 0.16);
|
|
483
|
+
--lv-danger-rule: oklch(0.73 0.15 48 / 0.55);
|
|
484
|
+
|
|
485
|
+
--lv-info-text: var(--lv-in-text-d);
|
|
486
|
+
--lv-info-mark: var(--lv-in-mark-d);
|
|
487
|
+
--lv-info-soft: oklch(0.7 0.17 290 / 0.18);
|
|
488
|
+
--lv-info-rule: oklch(0.7 0.17 290 / 0.5);
|
|
489
|
+
|
|
490
|
+
--lv-series-1: oklch(0.72 0.17 27.5);
|
|
491
|
+
--lv-series-2: oklch(0.72 0.15 290);
|
|
492
|
+
--lv-series-3: oklch(0.72 0.15 150);
|
|
493
|
+
--lv-series-4: oklch(0.72 0.13 95);
|
|
494
|
+
--lv-series-5: oklch(0.72 0.14 245);
|
|
495
|
+
--lv-series-6: oklch(0.72 0.1 195);
|
|
496
|
+
--lv-series-7: oklch(0.72 0.16 340);
|
|
497
|
+
--lv-series-8: oklch(0.72 0.14 128);
|
|
498
|
+
|
|
499
|
+
--lv-highlight: inset 0 1px 0 0 oklch(1 0 0 / 0.055);
|
|
500
|
+
--lv-elev-0: none;
|
|
501
|
+
--lv-elev-1: 0 0 0 1px oklch(1 0 0 / 0.06), var(--lv-highlight);
|
|
502
|
+
--lv-elev-2: 0 0 0 1px oklch(1 0 0 / 0.07), 0 2px 6px -2px oklch(0 0 0 / 0.5),
|
|
503
|
+
var(--lv-highlight);
|
|
504
|
+
--lv-elev-3: 0 0 0 1px oklch(1 0 0 / 0.08), 0 8px 20px -6px oklch(0 0 0 / 0.6),
|
|
505
|
+
var(--lv-highlight);
|
|
506
|
+
--lv-elev-4: 0 0 0 1px oklch(1 0 0 / 0.1), 0 22px 48px -12px oklch(0 0 0 / 0.7),
|
|
507
|
+
var(--lv-highlight);
|
|
508
|
+
--lv-elev-inset: inset 0 1px 3px 0 oklch(0 0 0 / 0.45),
|
|
509
|
+
inset 0 0 0 1px oklch(1 0 0 / 0.04);
|
|
510
|
+
|
|
511
|
+
--lv-skeleton-base: oklch(1 0 0 / 0.07);
|
|
512
|
+
--lv-skeleton-sheen: oklch(1 0 0 / 0.04);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/* Forced light under a dark OS. The §2 block already holds every light
|
|
516
|
+
value, so this only needs to re-assert `color-scheme`; the cascade
|
|
517
|
+
does the rest because the dark map lives behind
|
|
518
|
+
`:not([data-theme='light'])`. */
|
|
519
|
+
:root[data-theme='light'],
|
|
520
|
+
.lv-root[data-theme='light'] {
|
|
521
|
+
color-scheme: light;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
525
|
+
4. ROOT
|
|
526
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
527
|
+
|
|
528
|
+
.lv-root {
|
|
529
|
+
font-family: var(--lv-font-sans);
|
|
530
|
+
font-size: var(--lv-text-base);
|
|
531
|
+
line-height: var(--lv-lh-base);
|
|
532
|
+
color: var(--lv-text-primary);
|
|
533
|
+
background: var(--lv-surface-page);
|
|
534
|
+
/* macOS renders text heavier than intended; correct once, at the root. */
|
|
535
|
+
-webkit-font-smoothing: antialiased;
|
|
536
|
+
-moz-osx-font-smoothing: grayscale;
|
|
537
|
+
text-rendering: optimizeLegibility;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/* One focus treatment, on everything, in one place. `outline-offset`
|
|
541
|
+
does the ring gap without needing to know the surface behind it. */
|
|
542
|
+
.lv-root :is(a, button, input, select, textarea, summary, [tabindex]):focus-visible,
|
|
543
|
+
.lv-root:focus-visible {
|
|
544
|
+
outline: 2px solid var(--lv-focus);
|
|
545
|
+
outline-offset: 2px;
|
|
546
|
+
border-radius: inherit;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.lv-root ::selection {
|
|
550
|
+
background: var(--lv-accent-soft);
|
|
551
|
+
color: var(--lv-text-primary);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/* Any comparable number. Not opt-in. */
|
|
555
|
+
.lv-num {
|
|
556
|
+
font-variant-numeric: tabular-nums;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.lv-mono {
|
|
560
|
+
font-family: var(--lv-font-mono);
|
|
561
|
+
font-variant-numeric: tabular-nums;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.lv-truncate {
|
|
565
|
+
overflow: hidden;
|
|
566
|
+
text-overflow: ellipsis;
|
|
567
|
+
white-space: nowrap;
|
|
568
|
+
min-width: 0;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.lv-sr-only {
|
|
572
|
+
position: absolute;
|
|
573
|
+
width: 1px;
|
|
574
|
+
height: 1px;
|
|
575
|
+
padding: 0;
|
|
576
|
+
margin: -1px;
|
|
577
|
+
overflow: hidden;
|
|
578
|
+
clip-path: inset(50%);
|
|
579
|
+
white-space: nowrap;
|
|
580
|
+
border-width: 0;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/* Reduced motion — jump to the final state. Collapsing the duration
|
|
584
|
+
tokens covers every transition authored against the scale; the two
|
|
585
|
+
`!important` rules catch keyframes and anything a consumer nests. */
|
|
586
|
+
@media (prefers-reduced-motion: reduce) {
|
|
587
|
+
.lv-root,
|
|
588
|
+
.lv-root * {
|
|
589
|
+
--lv-dur-instant: 1ms;
|
|
590
|
+
--lv-dur-fast: 1ms;
|
|
591
|
+
--lv-dur-base: 1ms;
|
|
592
|
+
--lv-dur-slow: 1ms;
|
|
593
|
+
animation-duration: 1ms !important;
|
|
594
|
+
animation-iteration-count: 1 !important;
|
|
595
|
+
transition-duration: 1ms !important;
|
|
596
|
+
scroll-behavior: auto !important;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
601
|
+
5. LAYOUT PRIMITIVES
|
|
602
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
603
|
+
|
|
604
|
+
.lv-stack {
|
|
605
|
+
display: flex;
|
|
606
|
+
flex-direction: column;
|
|
607
|
+
min-width: 0;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.lv-inline {
|
|
611
|
+
display: flex;
|
|
612
|
+
flex-direction: row;
|
|
613
|
+
min-width: 0;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.lv-grid {
|
|
617
|
+
display: grid;
|
|
618
|
+
min-width: 0;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/* Section — a titled region sitting one tier above its shell. */
|
|
622
|
+
.lv-section {
|
|
623
|
+
background: var(--lv-surface-section);
|
|
624
|
+
border-radius: var(--lv-radius-lg);
|
|
625
|
+
box-shadow: var(--lv-elev-1);
|
|
626
|
+
padding: var(--lv-space-5);
|
|
627
|
+
min-width: 0;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.lv-section[data-flush='true'] {
|
|
631
|
+
background: transparent;
|
|
632
|
+
box-shadow: none;
|
|
633
|
+
padding: 0;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.lv-section-head {
|
|
637
|
+
display: flex;
|
|
638
|
+
align-items: flex-start;
|
|
639
|
+
justify-content: space-between;
|
|
640
|
+
gap: var(--lv-space-4);
|
|
641
|
+
/* Reserved so a section without actions is the same height as one
|
|
642
|
+
with them — the actions slot never resizes the header. */
|
|
643
|
+
min-height: 28px;
|
|
644
|
+
margin-bottom: var(--lv-space-4);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.lv-section-eyebrow {
|
|
648
|
+
font-size: var(--lv-text-2xs);
|
|
649
|
+
line-height: var(--lv-lh-2xs);
|
|
650
|
+
letter-spacing: var(--lv-tr-2xs);
|
|
651
|
+
text-transform: uppercase;
|
|
652
|
+
font-weight: var(--lv-weight-semibold);
|
|
653
|
+
color: var(--lv-text-tertiary);
|
|
654
|
+
margin-bottom: var(--lv-space-1);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.lv-section-title {
|
|
658
|
+
font-size: var(--lv-text-lg);
|
|
659
|
+
line-height: var(--lv-lh-lg);
|
|
660
|
+
letter-spacing: var(--lv-tr-lg);
|
|
661
|
+
font-weight: var(--lv-weight-semibold);
|
|
662
|
+
color: var(--lv-text-primary);
|
|
663
|
+
text-wrap: balance;
|
|
664
|
+
margin: 0;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.lv-section-desc {
|
|
668
|
+
font-size: var(--lv-text-sm);
|
|
669
|
+
line-height: var(--lv-lh-sm);
|
|
670
|
+
color: var(--lv-text-secondary);
|
|
671
|
+
text-wrap: pretty;
|
|
672
|
+
max-width: var(--lv-measure);
|
|
673
|
+
margin-top: var(--lv-space-1);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/* Panel — THE recessed inset. A hole cut into whatever surface holds
|
|
677
|
+
it. Never gets an outer shadow; never gets a hard border. */
|
|
678
|
+
.lv-panel {
|
|
679
|
+
background: var(--lv-surface-inset);
|
|
680
|
+
border-radius: var(--lv-radius-sm);
|
|
681
|
+
box-shadow: var(--lv-elev-inset);
|
|
682
|
+
padding: var(--lv-space-4);
|
|
683
|
+
min-width: 0;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.lv-panel[data-tone='quiet'] {
|
|
687
|
+
box-shadow: inset 0 0 0 1px var(--lv-hairline);
|
|
688
|
+
background: transparent;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.lv-panel[data-tone='raised'] {
|
|
692
|
+
background: var(--lv-surface-raised);
|
|
693
|
+
box-shadow: var(--lv-elev-2);
|
|
694
|
+
border-radius: var(--lv-radius-md);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/* Toolbar — controls float above content; the hairline is structural. */
|
|
698
|
+
.lv-toolbar {
|
|
699
|
+
display: flex;
|
|
700
|
+
align-items: center;
|
|
701
|
+
gap: var(--lv-space-2);
|
|
702
|
+
/* Reserved so an empty toolbar does not collapse the layout. */
|
|
703
|
+
min-height: 40px;
|
|
704
|
+
padding-inline: var(--lv-space-1);
|
|
705
|
+
min-width: 0;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.lv-toolbar[data-sticky='true'] {
|
|
709
|
+
position: sticky;
|
|
710
|
+
top: 0;
|
|
711
|
+
z-index: 5;
|
|
712
|
+
background: var(--lv-surface-shell);
|
|
713
|
+
padding-block: var(--lv-space-2);
|
|
714
|
+
box-shadow: inset 0 -1px 0 0 var(--lv-hairline);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.lv-toolbar-spacer {
|
|
718
|
+
flex: 1 1 auto;
|
|
719
|
+
min-width: var(--lv-space-2);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.lv-toolbar-divider {
|
|
723
|
+
flex: 0 0 auto;
|
|
724
|
+
align-self: stretch;
|
|
725
|
+
width: 1px;
|
|
726
|
+
margin-inline: var(--lv-space-1);
|
|
727
|
+
margin-block: var(--lv-space-2);
|
|
728
|
+
background: var(--lv-hairline);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
732
|
+
6. ACTION (button)
|
|
733
|
+
Exactly one `primary` per view. Everything else is secondary or ghost.
|
|
734
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
735
|
+
|
|
736
|
+
.lv-action {
|
|
737
|
+
position: relative;
|
|
738
|
+
display: inline-flex;
|
|
739
|
+
align-items: center;
|
|
740
|
+
justify-content: center;
|
|
741
|
+
gap: var(--lv-space-2);
|
|
742
|
+
flex: 0 0 auto;
|
|
743
|
+
height: 32px;
|
|
744
|
+
padding-inline: var(--lv-space-3);
|
|
745
|
+
border: 0;
|
|
746
|
+
border-radius: var(--lv-radius-md);
|
|
747
|
+
font-family: inherit;
|
|
748
|
+
font-size: var(--lv-text-sm);
|
|
749
|
+
font-weight: var(--lv-weight-medium);
|
|
750
|
+
line-height: 1;
|
|
751
|
+
white-space: nowrap;
|
|
752
|
+
cursor: pointer;
|
|
753
|
+
user-select: none;
|
|
754
|
+
/* Never `all`. */
|
|
755
|
+
transition-property: background-color, color, box-shadow, scale, opacity;
|
|
756
|
+
transition-duration: var(--lv-dur-fast);
|
|
757
|
+
transition-timing-function: var(--lv-ease-out);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.lv-action[data-size='sm'] {
|
|
761
|
+
height: 26px;
|
|
762
|
+
padding-inline: var(--lv-space-2);
|
|
763
|
+
font-size: var(--lv-text-xs);
|
|
764
|
+
border-radius: var(--lv-radius-sm);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
.lv-action[data-size='lg'] {
|
|
768
|
+
height: 40px;
|
|
769
|
+
padding-inline: var(--lv-space-4);
|
|
770
|
+
font-size: var(--lv-text-base);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.lv-action[data-icon-only='true'] {
|
|
774
|
+
width: 32px;
|
|
775
|
+
padding-inline: 0;
|
|
776
|
+
}
|
|
777
|
+
.lv-action[data-icon-only='true'][data-size='sm'] {
|
|
778
|
+
width: 26px;
|
|
779
|
+
}
|
|
780
|
+
.lv-action[data-icon-only='true'][data-size='lg'] {
|
|
781
|
+
width: 40px;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/* Tactile press. 0.96 exactly — below 0.95 reads as exaggerated. */
|
|
785
|
+
.lv-action:not(:disabled):active {
|
|
786
|
+
scale: 0.96;
|
|
787
|
+
transition-duration: var(--lv-dur-instant);
|
|
788
|
+
transition-timing-function: var(--lv-ease-spring);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.lv-action:disabled,
|
|
792
|
+
.lv-action[aria-disabled='true'] {
|
|
793
|
+
cursor: not-allowed;
|
|
794
|
+
/* Size is unchanged; only the paint changes. */
|
|
795
|
+
opacity: 0.5;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.lv-action[data-variant='primary'] {
|
|
799
|
+
background: var(--lv-accent);
|
|
800
|
+
color: var(--lv-text-on-accent);
|
|
801
|
+
box-shadow: var(--lv-elev-1);
|
|
802
|
+
}
|
|
803
|
+
.lv-action[data-variant='primary']:not(:disabled):hover {
|
|
804
|
+
background: var(--lv-accent-hover);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
.lv-action[data-variant='secondary'] {
|
|
808
|
+
background: var(--lv-surface-card);
|
|
809
|
+
color: var(--lv-text-primary);
|
|
810
|
+
box-shadow: var(--lv-elev-1);
|
|
811
|
+
}
|
|
812
|
+
.lv-action[data-variant='secondary']:not(:disabled):hover {
|
|
813
|
+
background: var(--lv-surface-hover);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.lv-action[data-variant='ghost'] {
|
|
817
|
+
background: transparent;
|
|
818
|
+
color: var(--lv-text-secondary);
|
|
819
|
+
}
|
|
820
|
+
.lv-action[data-variant='ghost']:not(:disabled):hover {
|
|
821
|
+
background: var(--lv-surface-hover);
|
|
822
|
+
color: var(--lv-text-primary);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
.lv-action[data-variant='danger'] {
|
|
826
|
+
background: transparent;
|
|
827
|
+
color: var(--lv-danger-text);
|
|
828
|
+
box-shadow: inset 0 0 0 1px var(--lv-danger-rule);
|
|
829
|
+
}
|
|
830
|
+
.lv-action[data-variant='danger']:not(:disabled):hover {
|
|
831
|
+
background: var(--lv-danger-soft);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/* Loading: the label stays in the DOM (holding the width) and goes
|
|
835
|
+
invisible; the spinner sits on top. The box never resizes. */
|
|
836
|
+
.lv-action[data-loading='true'] {
|
|
837
|
+
cursor: progress;
|
|
838
|
+
}
|
|
839
|
+
.lv-action[data-loading='true'] .lv-action-label {
|
|
840
|
+
visibility: hidden;
|
|
841
|
+
}
|
|
842
|
+
.lv-action-spinner {
|
|
843
|
+
position: absolute;
|
|
844
|
+
inset: 0;
|
|
845
|
+
display: grid;
|
|
846
|
+
place-items: center;
|
|
847
|
+
}
|
|
848
|
+
.lv-spin {
|
|
849
|
+
width: 14px;
|
|
850
|
+
height: 14px;
|
|
851
|
+
border-radius: var(--lv-radius-full);
|
|
852
|
+
border: 1.5px solid currentColor;
|
|
853
|
+
border-top-color: transparent;
|
|
854
|
+
opacity: 0.85;
|
|
855
|
+
animation: lv-spin 720ms linear infinite;
|
|
856
|
+
}
|
|
857
|
+
@keyframes lv-spin {
|
|
858
|
+
to {
|
|
859
|
+
transform: rotate(360deg);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
864
|
+
7. DOCUMENT SHELL
|
|
865
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
866
|
+
|
|
867
|
+
.lv-doc {
|
|
868
|
+
display: grid;
|
|
869
|
+
grid-template-columns: 1fr;
|
|
870
|
+
gap: var(--lv-space-6);
|
|
871
|
+
align-items: start;
|
|
872
|
+
background: var(--lv-surface-shell);
|
|
873
|
+
min-width: 0;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/* Hold the two-column structure until the content genuinely stops
|
|
877
|
+
fitting — not at a device preset. 900px is where a 224px rail plus a
|
|
878
|
+
readable measure runs out of room. */
|
|
879
|
+
@media (min-width: 900px) {
|
|
880
|
+
.lv-doc {
|
|
881
|
+
grid-template-columns: var(--lv-doc-nav-w) minmax(0, 1fr);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.lv-doc-header {
|
|
886
|
+
position: sticky;
|
|
887
|
+
top: 0;
|
|
888
|
+
z-index: 10;
|
|
889
|
+
grid-column: 1 / -1;
|
|
890
|
+
display: flex;
|
|
891
|
+
align-items: center;
|
|
892
|
+
gap: var(--lv-space-4);
|
|
893
|
+
/* Reserved: the header is this tall whether or not it has actions,
|
|
894
|
+
a status pill, or a two-line title. */
|
|
895
|
+
min-height: var(--lv-doc-header-h);
|
|
896
|
+
padding: var(--lv-space-3) var(--lv-space-5);
|
|
897
|
+
background: var(--lv-surface-shell);
|
|
898
|
+
box-shadow: inset 0 -1px 0 0 var(--lv-hairline);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.lv-doc-headline {
|
|
902
|
+
display: flex;
|
|
903
|
+
flex-direction: column;
|
|
904
|
+
gap: 2px;
|
|
905
|
+
min-width: 0;
|
|
906
|
+
flex: 1 1 auto;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
.lv-doc-eyebrow {
|
|
910
|
+
font-size: var(--lv-text-2xs);
|
|
911
|
+
line-height: var(--lv-lh-2xs);
|
|
912
|
+
letter-spacing: var(--lv-tr-2xs);
|
|
913
|
+
text-transform: uppercase;
|
|
914
|
+
font-weight: var(--lv-weight-semibold);
|
|
915
|
+
color: var(--lv-text-tertiary);
|
|
916
|
+
/* Reserved so a document with no eyebrow is the same height. */
|
|
917
|
+
min-height: calc(var(--lv-text-2xs) * var(--lv-lh-2xs));
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
.lv-doc-title {
|
|
921
|
+
font-size: var(--lv-text-xl);
|
|
922
|
+
line-height: var(--lv-lh-xl);
|
|
923
|
+
letter-spacing: var(--lv-tr-xl);
|
|
924
|
+
font-weight: var(--lv-weight-semibold);
|
|
925
|
+
color: var(--lv-text-primary);
|
|
926
|
+
margin: 0;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
.lv-doc-header-actions {
|
|
930
|
+
display: flex;
|
|
931
|
+
align-items: center;
|
|
932
|
+
gap: var(--lv-space-2);
|
|
933
|
+
flex: 0 0 auto;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/* Nav rail. On narrow viewports it becomes a horizontal scroller with
|
|
937
|
+
the next item peeking past the edge, so hidden items announce
|
|
938
|
+
themselves instead of silently disappearing. */
|
|
939
|
+
.lv-doc-nav {
|
|
940
|
+
position: sticky;
|
|
941
|
+
top: var(--lv-doc-header-h);
|
|
942
|
+
align-self: start;
|
|
943
|
+
padding: var(--lv-space-4) var(--lv-space-2) var(--lv-space-4)
|
|
944
|
+
var(--lv-space-5);
|
|
945
|
+
min-width: 0;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
@media (max-width: 899px) {
|
|
949
|
+
.lv-doc-nav {
|
|
950
|
+
position: sticky;
|
|
951
|
+
top: var(--lv-doc-header-h);
|
|
952
|
+
z-index: 9;
|
|
953
|
+
display: flex;
|
|
954
|
+
gap: var(--lv-space-1);
|
|
955
|
+
overflow-x: auto;
|
|
956
|
+
scrollbar-width: none;
|
|
957
|
+
padding: var(--lv-space-2) var(--lv-space-5);
|
|
958
|
+
background: var(--lv-surface-shell);
|
|
959
|
+
box-shadow: inset 0 -1px 0 0 var(--lv-hairline);
|
|
960
|
+
/* Let the next item peek so the scroller never looks complete. */
|
|
961
|
+
scroll-padding-inline: var(--lv-space-6);
|
|
962
|
+
}
|
|
963
|
+
.lv-doc-nav::-webkit-scrollbar {
|
|
964
|
+
display: none;
|
|
965
|
+
}
|
|
966
|
+
.lv-doc-nav-list {
|
|
967
|
+
display: contents;
|
|
968
|
+
}
|
|
969
|
+
.lv-doc-nav-item {
|
|
970
|
+
flex: 0 0 auto;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
.lv-doc-nav-label {
|
|
975
|
+
font-size: var(--lv-text-2xs);
|
|
976
|
+
line-height: var(--lv-lh-2xs);
|
|
977
|
+
letter-spacing: var(--lv-tr-2xs);
|
|
978
|
+
text-transform: uppercase;
|
|
979
|
+
font-weight: var(--lv-weight-semibold);
|
|
980
|
+
color: var(--lv-text-faint);
|
|
981
|
+
padding-inline-start: var(--lv-space-3);
|
|
982
|
+
margin-bottom: var(--lv-space-2);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
@media (max-width: 899px) {
|
|
986
|
+
.lv-doc-nav-label {
|
|
987
|
+
display: none;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.lv-doc-nav-list {
|
|
992
|
+
display: flex;
|
|
993
|
+
flex-direction: column;
|
|
994
|
+
gap: 1px;
|
|
995
|
+
list-style: none;
|
|
996
|
+
margin: 0;
|
|
997
|
+
padding: 0;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.lv-doc-nav-item {
|
|
1001
|
+
display: flex;
|
|
1002
|
+
align-items: center;
|
|
1003
|
+
gap: var(--lv-space-2);
|
|
1004
|
+
width: 100%;
|
|
1005
|
+
min-height: 30px;
|
|
1006
|
+
padding: var(--lv-space-1) var(--lv-space-3);
|
|
1007
|
+
border: 0;
|
|
1008
|
+
background: transparent;
|
|
1009
|
+
/* The active indicator is ALWAYS present, just transparent. Adding a
|
|
1010
|
+
rule on activation would shift every label by 2px. */
|
|
1011
|
+
border-inline-start: 2px solid transparent;
|
|
1012
|
+
border-radius: 0 var(--lv-radius-sm) var(--lv-radius-sm) 0;
|
|
1013
|
+
font-family: inherit;
|
|
1014
|
+
font-size: var(--lv-text-sm);
|
|
1015
|
+
line-height: var(--lv-lh-sm);
|
|
1016
|
+
font-weight: var(--lv-weight-medium);
|
|
1017
|
+
color: var(--lv-text-secondary);
|
|
1018
|
+
text-align: start;
|
|
1019
|
+
cursor: pointer;
|
|
1020
|
+
transition-property: background-color, color, border-color;
|
|
1021
|
+
transition-duration: var(--lv-dur-fast);
|
|
1022
|
+
transition-timing-function: var(--lv-ease-out);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
.lv-doc-nav-item:hover {
|
|
1026
|
+
background: var(--lv-surface-hover);
|
|
1027
|
+
color: var(--lv-text-primary);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
.lv-doc-nav-item[aria-current='true'] {
|
|
1031
|
+
color: var(--lv-text-primary);
|
|
1032
|
+
border-inline-start-color: var(--lv-accent-mark);
|
|
1033
|
+
background: var(--lv-surface-selected);
|
|
1034
|
+
font-weight: var(--lv-weight-semibold);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
@media (max-width: 899px) {
|
|
1038
|
+
.lv-doc-nav-item {
|
|
1039
|
+
border-inline-start: 0;
|
|
1040
|
+
border-block-end: 2px solid transparent;
|
|
1041
|
+
border-radius: var(--lv-radius-sm);
|
|
1042
|
+
white-space: nowrap;
|
|
1043
|
+
}
|
|
1044
|
+
.lv-doc-nav-item[aria-current='true'] {
|
|
1045
|
+
border-block-end-color: var(--lv-accent-mark);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
.lv-doc-nav-count {
|
|
1050
|
+
margin-inline-start: auto;
|
|
1051
|
+
font-size: var(--lv-text-2xs);
|
|
1052
|
+
font-variant-numeric: tabular-nums;
|
|
1053
|
+
color: var(--lv-text-faint);
|
|
1054
|
+
/* Reserved slot: a count appearing must not shove the label. */
|
|
1055
|
+
min-width: 2ch;
|
|
1056
|
+
text-align: end;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
.lv-doc-body {
|
|
1060
|
+
display: flex;
|
|
1061
|
+
flex-direction: column;
|
|
1062
|
+
gap: var(--lv-space-6);
|
|
1063
|
+
padding: var(--lv-space-4) var(--lv-space-5) var(--lv-space-20);
|
|
1064
|
+
min-width: 0;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/* ── Document section ─────────────────────────────────────────────── */
|
|
1068
|
+
|
|
1069
|
+
.lv-docsection {
|
|
1070
|
+
scroll-margin-top: var(--lv-scroll-offset);
|
|
1071
|
+
min-width: 0;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
.lv-docsection-head {
|
|
1075
|
+
display: flex;
|
|
1076
|
+
align-items: flex-start;
|
|
1077
|
+
justify-content: space-between;
|
|
1078
|
+
gap: var(--lv-space-4);
|
|
1079
|
+
min-height: 28px;
|
|
1080
|
+
margin-bottom: var(--lv-space-3);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.lv-docsection-title {
|
|
1084
|
+
font-size: var(--lv-text-lg);
|
|
1085
|
+
line-height: var(--lv-lh-lg);
|
|
1086
|
+
letter-spacing: var(--lv-tr-lg);
|
|
1087
|
+
font-weight: var(--lv-weight-semibold);
|
|
1088
|
+
color: var(--lv-text-primary);
|
|
1089
|
+
text-wrap: balance;
|
|
1090
|
+
margin: 0;
|
|
1091
|
+
scroll-margin-top: var(--lv-scroll-offset);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
.lv-docsection-desc {
|
|
1095
|
+
font-size: var(--lv-text-sm);
|
|
1096
|
+
line-height: var(--lv-lh-sm);
|
|
1097
|
+
color: var(--lv-text-secondary);
|
|
1098
|
+
text-wrap: pretty;
|
|
1099
|
+
max-width: var(--lv-measure);
|
|
1100
|
+
margin-top: var(--lv-space-1);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
.lv-docsection-body {
|
|
1104
|
+
min-width: 0;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/* Long-form prose inside a document section. */
|
|
1108
|
+
.lv-prose {
|
|
1109
|
+
font-size: var(--lv-prose-size);
|
|
1110
|
+
line-height: var(--lv-prose-lh);
|
|
1111
|
+
color: var(--lv-text-secondary);
|
|
1112
|
+
max-width: var(--lv-measure);
|
|
1113
|
+
text-wrap: pretty;
|
|
1114
|
+
}
|
|
1115
|
+
.lv-prose > * + * {
|
|
1116
|
+
margin-top: var(--lv-space-4);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
1120
|
+
8. FIELD ROW / DEFINITION LIST
|
|
1121
|
+
Every row shares one label column, so labels and values across
|
|
1122
|
+
unrelated rows sit on the same two edges.
|
|
1123
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
1124
|
+
|
|
1125
|
+
.lv-dl {
|
|
1126
|
+
display: flex;
|
|
1127
|
+
flex-direction: column;
|
|
1128
|
+
gap: var(--lv-space-1);
|
|
1129
|
+
margin: 0;
|
|
1130
|
+
min-width: 0;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
.lv-dl[data-dividers='true'] .lv-field-row + .lv-field-row {
|
|
1134
|
+
border-block-start: 1px solid var(--lv-hairline);
|
|
1135
|
+
padding-block-start: var(--lv-space-2);
|
|
1136
|
+
margin-block-start: var(--lv-space-1);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
.lv-field-row {
|
|
1140
|
+
display: grid;
|
|
1141
|
+
grid-template-columns: var(--lv-dl-label-w) minmax(0, 1fr);
|
|
1142
|
+
gap: var(--lv-space-4);
|
|
1143
|
+
align-items: baseline;
|
|
1144
|
+
min-width: 0;
|
|
1145
|
+
padding-block: var(--lv-space-1);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/* Below this the label column eats the value; stack instead of
|
|
1149
|
+
truncating something the reader came for. */
|
|
1150
|
+
@media (max-width: 639px) {
|
|
1151
|
+
.lv-field-row,
|
|
1152
|
+
.lv-field-row[data-layout='inline'] {
|
|
1153
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1154
|
+
gap: 2px;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
.lv-field-row[data-layout='stacked'] {
|
|
1159
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1160
|
+
gap: 2px;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
.lv-field-label {
|
|
1164
|
+
font-size: var(--lv-text-sm);
|
|
1165
|
+
line-height: var(--lv-lh-sm);
|
|
1166
|
+
font-weight: var(--lv-weight-medium);
|
|
1167
|
+
color: var(--lv-text-tertiary);
|
|
1168
|
+
margin: 0;
|
|
1169
|
+
min-width: 0;
|
|
1170
|
+
overflow-wrap: break-word;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
.lv-field-value {
|
|
1174
|
+
font-size: var(--lv-text-sm);
|
|
1175
|
+
line-height: var(--lv-lh-sm);
|
|
1176
|
+
color: var(--lv-text-primary);
|
|
1177
|
+
margin: 0;
|
|
1178
|
+
min-width: 0;
|
|
1179
|
+
overflow-wrap: break-word;
|
|
1180
|
+
/* Reserved: an empty value keeps the row at one line's height. */
|
|
1181
|
+
min-height: calc(var(--lv-text-sm) * var(--lv-lh-sm));
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.lv-field-value[data-empty='true'] {
|
|
1185
|
+
color: var(--lv-text-faint);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
.lv-field-hint {
|
|
1189
|
+
display: block;
|
|
1190
|
+
font-size: var(--lv-text-xs);
|
|
1191
|
+
line-height: var(--lv-lh-xs);
|
|
1192
|
+
color: var(--lv-text-faint);
|
|
1193
|
+
margin-top: 2px;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
1197
|
+
9. STAT TILE
|
|
1198
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
1199
|
+
|
|
1200
|
+
.lv-stat {
|
|
1201
|
+
display: flex;
|
|
1202
|
+
flex-direction: column;
|
|
1203
|
+
gap: var(--lv-space-1);
|
|
1204
|
+
padding: var(--lv-space-3) var(--lv-space-4);
|
|
1205
|
+
background: var(--lv-surface-card);
|
|
1206
|
+
border-radius: var(--lv-radius-md);
|
|
1207
|
+
box-shadow: var(--lv-elev-1);
|
|
1208
|
+
min-width: 0;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
.lv-stat[data-inset='true'] {
|
|
1212
|
+
background: var(--lv-surface-inset);
|
|
1213
|
+
box-shadow: var(--lv-elev-inset);
|
|
1214
|
+
border-radius: var(--lv-radius-sm);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.lv-stat-label {
|
|
1218
|
+
font-size: var(--lv-text-2xs);
|
|
1219
|
+
line-height: var(--lv-lh-2xs);
|
|
1220
|
+
letter-spacing: var(--lv-tr-2xs);
|
|
1221
|
+
text-transform: uppercase;
|
|
1222
|
+
font-weight: var(--lv-weight-semibold);
|
|
1223
|
+
color: var(--lv-text-tertiary);
|
|
1224
|
+
white-space: nowrap;
|
|
1225
|
+
overflow: hidden;
|
|
1226
|
+
text-overflow: ellipsis;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
.lv-stat-valuerow {
|
|
1230
|
+
display: flex;
|
|
1231
|
+
align-items: baseline;
|
|
1232
|
+
gap: var(--lv-space-1);
|
|
1233
|
+
/* Reserved so the tile's height never depends on whether a value has
|
|
1234
|
+
arrived yet. */
|
|
1235
|
+
min-height: calc(var(--lv-text-stat) * var(--lv-lh-stat));
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
.lv-stat-value {
|
|
1239
|
+
font-size: var(--lv-text-stat);
|
|
1240
|
+
line-height: var(--lv-lh-stat);
|
|
1241
|
+
letter-spacing: var(--lv-tr-stat);
|
|
1242
|
+
font-weight: var(--lv-weight-semibold);
|
|
1243
|
+
font-variant-numeric: tabular-nums;
|
|
1244
|
+
color: var(--lv-text-primary);
|
|
1245
|
+
/* The slot. Sized in `ch` for the largest plausible value, so a
|
|
1246
|
+
counter ticking 9 → 10 → 100 never reflows its neighbours. */
|
|
1247
|
+
min-width: calc(var(--lv-stat-slots, 6) * 1ch);
|
|
1248
|
+
display: inline-block;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
.lv-stat-unit {
|
|
1252
|
+
font-size: var(--lv-text-sm);
|
|
1253
|
+
font-weight: var(--lv-weight-medium);
|
|
1254
|
+
color: var(--lv-text-tertiary);
|
|
1255
|
+
white-space: nowrap;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
/* The delta row is ALWAYS in the box. When there is no delta it is
|
|
1259
|
+
hidden, not removed — `display: none` would resize the tile. */
|
|
1260
|
+
.lv-stat-footrow {
|
|
1261
|
+
display: flex;
|
|
1262
|
+
align-items: center;
|
|
1263
|
+
gap: var(--lv-space-1);
|
|
1264
|
+
font-size: var(--lv-text-xs);
|
|
1265
|
+
line-height: var(--lv-lh-xs);
|
|
1266
|
+
font-variant-numeric: tabular-nums;
|
|
1267
|
+
color: var(--lv-text-tertiary);
|
|
1268
|
+
min-height: calc(var(--lv-text-xs) * var(--lv-lh-xs));
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
.lv-stat-footrow[data-hidden='true'] {
|
|
1272
|
+
visibility: hidden;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
.lv-stat-delta[data-tone='up'] {
|
|
1276
|
+
color: var(--lv-success-text);
|
|
1277
|
+
}
|
|
1278
|
+
.lv-stat-delta[data-tone='down'] {
|
|
1279
|
+
color: var(--lv-danger-text);
|
|
1280
|
+
}
|
|
1281
|
+
.lv-stat-delta[data-tone='flat'] {
|
|
1282
|
+
color: var(--lv-text-tertiary);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
.lv-stat-hint {
|
|
1286
|
+
color: var(--lv-text-faint);
|
|
1287
|
+
overflow: hidden;
|
|
1288
|
+
text-overflow: ellipsis;
|
|
1289
|
+
white-space: nowrap;
|
|
1290
|
+
min-width: 0;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
1294
|
+
10. CALLOUT
|
|
1295
|
+
Tone is carried by an icon, a word, and a leading rule — never by
|
|
1296
|
+
colour alone.
|
|
1297
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
1298
|
+
|
|
1299
|
+
.lv-callout {
|
|
1300
|
+
display: grid;
|
|
1301
|
+
grid-template-columns: 18px minmax(0, 1fr);
|
|
1302
|
+
gap: var(--lv-space-3);
|
|
1303
|
+
align-items: start;
|
|
1304
|
+
padding: var(--lv-space-3) var(--lv-space-4);
|
|
1305
|
+
border-radius: var(--lv-radius-md);
|
|
1306
|
+
/* Leading rule, not a full border. A boxed callout reads as a form
|
|
1307
|
+
field; a ruled one reads as an aside. */
|
|
1308
|
+
border-inline-start: 2px solid var(--lv-info-rule);
|
|
1309
|
+
background: var(--lv-info-soft);
|
|
1310
|
+
min-width: 0;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
.lv-callout[data-tone='info'] {
|
|
1314
|
+
border-inline-start-color: var(--lv-info-rule);
|
|
1315
|
+
background: var(--lv-info-soft);
|
|
1316
|
+
}
|
|
1317
|
+
.lv-callout[data-tone='success'] {
|
|
1318
|
+
border-inline-start-color: var(--lv-success-rule);
|
|
1319
|
+
background: var(--lv-success-soft);
|
|
1320
|
+
}
|
|
1321
|
+
.lv-callout[data-tone='warn'] {
|
|
1322
|
+
border-inline-start-color: var(--lv-warning-rule);
|
|
1323
|
+
background: var(--lv-warning-soft);
|
|
1324
|
+
}
|
|
1325
|
+
.lv-callout[data-tone='danger'] {
|
|
1326
|
+
border-inline-start-color: var(--lv-danger-rule);
|
|
1327
|
+
background: var(--lv-danger-soft);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
.lv-callout-icon {
|
|
1331
|
+
display: grid;
|
|
1332
|
+
place-items: center;
|
|
1333
|
+
width: 18px;
|
|
1334
|
+
height: 18px;
|
|
1335
|
+
/* Optical: nudge onto the title's cap-height rather than its box. */
|
|
1336
|
+
margin-top: 1px;
|
|
1337
|
+
flex: 0 0 auto;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
.lv-callout[data-tone='info'] .lv-callout-icon {
|
|
1341
|
+
color: var(--lv-info-text);
|
|
1342
|
+
}
|
|
1343
|
+
.lv-callout[data-tone='success'] .lv-callout-icon {
|
|
1344
|
+
color: var(--lv-success-text);
|
|
1345
|
+
}
|
|
1346
|
+
.lv-callout[data-tone='warn'] .lv-callout-icon {
|
|
1347
|
+
color: var(--lv-warning-text);
|
|
1348
|
+
}
|
|
1349
|
+
.lv-callout[data-tone='danger'] .lv-callout-icon {
|
|
1350
|
+
color: var(--lv-danger-text);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
.lv-callout-title {
|
|
1354
|
+
font-size: var(--lv-text-sm);
|
|
1355
|
+
line-height: var(--lv-lh-sm);
|
|
1356
|
+
font-weight: var(--lv-weight-semibold);
|
|
1357
|
+
color: var(--lv-text-primary);
|
|
1358
|
+
margin: 0;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
.lv-callout-body {
|
|
1362
|
+
font-size: var(--lv-text-sm);
|
|
1363
|
+
line-height: var(--lv-lh-sm);
|
|
1364
|
+
color: var(--lv-text-secondary);
|
|
1365
|
+
text-wrap: pretty;
|
|
1366
|
+
max-width: var(--lv-measure);
|
|
1367
|
+
margin-top: 2px;
|
|
1368
|
+
overflow-wrap: break-word;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
.lv-callout-actions {
|
|
1372
|
+
display: flex;
|
|
1373
|
+
gap: var(--lv-space-2);
|
|
1374
|
+
margin-top: var(--lv-space-3);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
1378
|
+
11. STATUS PILL
|
|
1379
|
+
The marker SHAPE differs per tone as well as the colour, so the pill
|
|
1380
|
+
still reads correctly in greyscale.
|
|
1381
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
1382
|
+
|
|
1383
|
+
.lv-pill {
|
|
1384
|
+
display: inline-flex;
|
|
1385
|
+
align-items: center;
|
|
1386
|
+
gap: var(--lv-space-1);
|
|
1387
|
+
/* Fixed height. A pill changing tone must not change size. */
|
|
1388
|
+
height: 20px;
|
|
1389
|
+
padding-inline: var(--lv-space-2);
|
|
1390
|
+
border-radius: var(--lv-radius-full);
|
|
1391
|
+
font-size: var(--lv-text-2xs);
|
|
1392
|
+
line-height: 1;
|
|
1393
|
+
letter-spacing: 0.01em;
|
|
1394
|
+
font-weight: var(--lv-weight-semibold);
|
|
1395
|
+
white-space: nowrap;
|
|
1396
|
+
font-variant-numeric: tabular-nums;
|
|
1397
|
+
max-width: 100%;
|
|
1398
|
+
min-width: 0;
|
|
1399
|
+
background: var(--lv-surface-hover);
|
|
1400
|
+
color: var(--lv-text-secondary);
|
|
1401
|
+
box-shadow: inset 0 0 0 1px var(--lv-hairline);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
.lv-pill-text {
|
|
1405
|
+
overflow: hidden;
|
|
1406
|
+
text-overflow: ellipsis;
|
|
1407
|
+
min-width: 0;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
.lv-pill-mark {
|
|
1411
|
+
flex: 0 0 auto;
|
|
1412
|
+
width: 7px;
|
|
1413
|
+
height: 7px;
|
|
1414
|
+
background: currentColor;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/* Shape per tone — filled dot, ring, square, triangle. */
|
|
1418
|
+
.lv-pill-mark[data-shape='dot'] {
|
|
1419
|
+
border-radius: var(--lv-radius-full);
|
|
1420
|
+
}
|
|
1421
|
+
.lv-pill-mark[data-shape='ring'] {
|
|
1422
|
+
border-radius: var(--lv-radius-full);
|
|
1423
|
+
background: transparent;
|
|
1424
|
+
box-shadow: inset 0 0 0 1.5px currentColor;
|
|
1425
|
+
}
|
|
1426
|
+
.lv-pill-mark[data-shape='square'] {
|
|
1427
|
+
border-radius: 1px;
|
|
1428
|
+
}
|
|
1429
|
+
.lv-pill-mark[data-shape='triangle'] {
|
|
1430
|
+
background: transparent;
|
|
1431
|
+
width: 0;
|
|
1432
|
+
height: 0;
|
|
1433
|
+
border-inline: 4px solid transparent;
|
|
1434
|
+
border-block-end: 7px solid currentColor;
|
|
1435
|
+
}
|
|
1436
|
+
.lv-pill-mark[data-shape='bar'] {
|
|
1437
|
+
width: 3px;
|
|
1438
|
+
height: 9px;
|
|
1439
|
+
border-radius: 1px;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
.lv-pill[data-tone='accent'] {
|
|
1443
|
+
background: var(--lv-accent-soft);
|
|
1444
|
+
color: var(--lv-accent-text);
|
|
1445
|
+
box-shadow: inset 0 0 0 1px var(--lv-accent-rule);
|
|
1446
|
+
}
|
|
1447
|
+
.lv-pill[data-tone='success'] {
|
|
1448
|
+
background: var(--lv-success-soft);
|
|
1449
|
+
color: var(--lv-success-text);
|
|
1450
|
+
box-shadow: inset 0 0 0 1px var(--lv-success-rule);
|
|
1451
|
+
}
|
|
1452
|
+
.lv-pill[data-tone='warning'] {
|
|
1453
|
+
background: var(--lv-warning-soft);
|
|
1454
|
+
color: var(--lv-warning-text);
|
|
1455
|
+
box-shadow: inset 0 0 0 1px var(--lv-warning-rule);
|
|
1456
|
+
}
|
|
1457
|
+
.lv-pill[data-tone='danger'] {
|
|
1458
|
+
background: var(--lv-danger-soft);
|
|
1459
|
+
color: var(--lv-danger-text);
|
|
1460
|
+
box-shadow: inset 0 0 0 1px var(--lv-danger-rule);
|
|
1461
|
+
}
|
|
1462
|
+
.lv-pill[data-tone='info'] {
|
|
1463
|
+
background: var(--lv-info-soft);
|
|
1464
|
+
color: var(--lv-info-text);
|
|
1465
|
+
box-shadow: inset 0 0 0 1px var(--lv-info-rule);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
1469
|
+
12. PROGRESS TRACK
|
|
1470
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
1471
|
+
|
|
1472
|
+
.lv-progress {
|
|
1473
|
+
display: flex;
|
|
1474
|
+
flex-direction: column;
|
|
1475
|
+
gap: var(--lv-space-2);
|
|
1476
|
+
min-width: 0;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
.lv-progress-head {
|
|
1480
|
+
display: flex;
|
|
1481
|
+
align-items: baseline;
|
|
1482
|
+
justify-content: space-between;
|
|
1483
|
+
gap: var(--lv-space-3);
|
|
1484
|
+
/* Reserved: label row exists whether or not a label was passed. */
|
|
1485
|
+
min-height: calc(var(--lv-text-xs) * var(--lv-lh-xs));
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
.lv-progress-label {
|
|
1489
|
+
font-size: var(--lv-text-xs);
|
|
1490
|
+
line-height: var(--lv-lh-xs);
|
|
1491
|
+
font-weight: var(--lv-weight-medium);
|
|
1492
|
+
color: var(--lv-text-secondary);
|
|
1493
|
+
overflow: hidden;
|
|
1494
|
+
text-overflow: ellipsis;
|
|
1495
|
+
white-space: nowrap;
|
|
1496
|
+
min-width: 0;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
.lv-progress-value {
|
|
1500
|
+
font-size: var(--lv-text-xs);
|
|
1501
|
+
line-height: var(--lv-lh-xs);
|
|
1502
|
+
font-variant-numeric: tabular-nums;
|
|
1503
|
+
font-weight: var(--lv-weight-semibold);
|
|
1504
|
+
color: var(--lv-text-tertiary);
|
|
1505
|
+
/* Slot for "100%" so 9% → 100% never nudges the label. */
|
|
1506
|
+
min-width: 4ch;
|
|
1507
|
+
text-align: end;
|
|
1508
|
+
flex: 0 0 auto;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/* The track is a recessed groove — the inset treatment, in miniature. */
|
|
1512
|
+
.lv-progress-track {
|
|
1513
|
+
position: relative;
|
|
1514
|
+
height: 6px;
|
|
1515
|
+
border-radius: var(--lv-radius-full);
|
|
1516
|
+
background: var(--lv-surface-inset);
|
|
1517
|
+
box-shadow: var(--lv-elev-inset);
|
|
1518
|
+
overflow: hidden;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
.lv-progress-fill {
|
|
1522
|
+
height: 100%;
|
|
1523
|
+
border-radius: inherit;
|
|
1524
|
+
background: var(--lv-accent-mark);
|
|
1525
|
+
/* Ease, never spring: overshoot on a measured value reads as wrong. */
|
|
1526
|
+
transition-property: width;
|
|
1527
|
+
transition-duration: var(--lv-dur-slow);
|
|
1528
|
+
transition-timing-function: var(--lv-ease-out);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
.lv-progress[data-tone='success'] .lv-progress-fill {
|
|
1532
|
+
background: var(--lv-success-mark);
|
|
1533
|
+
}
|
|
1534
|
+
.lv-progress[data-tone='warning'] .lv-progress-fill {
|
|
1535
|
+
background: var(--lv-warning-mark);
|
|
1536
|
+
}
|
|
1537
|
+
.lv-progress[data-tone='danger'] .lv-progress-fill {
|
|
1538
|
+
background: var(--lv-danger-mark);
|
|
1539
|
+
}
|
|
1540
|
+
.lv-progress[data-tone='neutral'] .lv-progress-fill {
|
|
1541
|
+
background: var(--lv-text-faint);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
.lv-progress-indeterminate {
|
|
1545
|
+
position: absolute;
|
|
1546
|
+
inset-block: 0;
|
|
1547
|
+
inline-size: 40%;
|
|
1548
|
+
border-radius: inherit;
|
|
1549
|
+
background: var(--lv-accent-mark);
|
|
1550
|
+
animation: lv-indeterminate 1.4s var(--lv-ease-in-out) infinite;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
@keyframes lv-indeterminate {
|
|
1554
|
+
0% {
|
|
1555
|
+
transform: translateX(-110%);
|
|
1556
|
+
}
|
|
1557
|
+
100% {
|
|
1558
|
+
transform: translateX(320%);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
/* Reduced motion: no travelling bar. Show a static, obviously-partial
|
|
1563
|
+
fill instead — the final state, not a frozen animation. */
|
|
1564
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1565
|
+
.lv-progress-indeterminate {
|
|
1566
|
+
animation: none;
|
|
1567
|
+
inline-size: 100%;
|
|
1568
|
+
opacity: 0.4;
|
|
1569
|
+
transform: none;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
1574
|
+
13. TIMELINE
|
|
1575
|
+
The rail is a structural line, which is exactly what a border is for.
|
|
1576
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
1577
|
+
|
|
1578
|
+
.lv-timeline {
|
|
1579
|
+
display: flex;
|
|
1580
|
+
flex-direction: column;
|
|
1581
|
+
list-style: none;
|
|
1582
|
+
margin: 0;
|
|
1583
|
+
padding: 0;
|
|
1584
|
+
min-width: 0;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
.lv-timeline-item {
|
|
1588
|
+
position: relative;
|
|
1589
|
+
display: grid;
|
|
1590
|
+
grid-template-columns: 20px minmax(0, 1fr);
|
|
1591
|
+
gap: var(--lv-space-3);
|
|
1592
|
+
padding-block-end: var(--lv-space-5);
|
|
1593
|
+
min-width: 0;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
.lv-timeline-item:last-child {
|
|
1597
|
+
padding-block-end: 0;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
/* The rail. Drawn on the marker column, stopped on the last item. */
|
|
1601
|
+
.lv-timeline-item::before {
|
|
1602
|
+
content: '';
|
|
1603
|
+
position: absolute;
|
|
1604
|
+
inset-block-start: 18px;
|
|
1605
|
+
inset-block-end: 0;
|
|
1606
|
+
inset-inline-start: 9px;
|
|
1607
|
+
width: 2px;
|
|
1608
|
+
background: var(--lv-hairline-strong);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
.lv-timeline-item:last-child::before {
|
|
1612
|
+
display: none;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
.lv-timeline-marker {
|
|
1616
|
+
position: relative;
|
|
1617
|
+
z-index: 1;
|
|
1618
|
+
display: grid;
|
|
1619
|
+
place-items: center;
|
|
1620
|
+
width: 20px;
|
|
1621
|
+
height: 20px;
|
|
1622
|
+
border-radius: var(--lv-radius-full);
|
|
1623
|
+
background: var(--lv-surface-card);
|
|
1624
|
+
box-shadow: inset 0 0 0 2px var(--lv-hairline-strong);
|
|
1625
|
+
color: var(--lv-text-tertiary);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
.lv-timeline-item[data-state='done'] .lv-timeline-marker {
|
|
1629
|
+
background: var(--lv-success-soft);
|
|
1630
|
+
box-shadow: inset 0 0 0 2px var(--lv-success-rule);
|
|
1631
|
+
color: var(--lv-success-text);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
.lv-timeline-item[data-state='current'] .lv-timeline-marker {
|
|
1635
|
+
background: var(--lv-accent-soft);
|
|
1636
|
+
box-shadow: inset 0 0 0 2px var(--lv-accent-rule);
|
|
1637
|
+
color: var(--lv-accent-text);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
.lv-timeline-item[data-state='blocked'] .lv-timeline-marker {
|
|
1641
|
+
background: var(--lv-danger-soft);
|
|
1642
|
+
box-shadow: inset 0 0 0 2px var(--lv-danger-rule);
|
|
1643
|
+
color: var(--lv-danger-text);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
.lv-timeline-content {
|
|
1647
|
+
min-width: 0;
|
|
1648
|
+
padding-block-start: 1px;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
.lv-timeline-titlerow {
|
|
1652
|
+
display: flex;
|
|
1653
|
+
align-items: baseline;
|
|
1654
|
+
gap: var(--lv-space-2);
|
|
1655
|
+
justify-content: space-between;
|
|
1656
|
+
min-width: 0;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
.lv-timeline-title {
|
|
1660
|
+
font-size: var(--lv-text-sm);
|
|
1661
|
+
line-height: var(--lv-lh-sm);
|
|
1662
|
+
font-weight: var(--lv-weight-semibold);
|
|
1663
|
+
color: var(--lv-text-primary);
|
|
1664
|
+
min-width: 0;
|
|
1665
|
+
overflow-wrap: break-word;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
.lv-timeline-meta {
|
|
1669
|
+
font-size: var(--lv-text-xs);
|
|
1670
|
+
line-height: var(--lv-lh-xs);
|
|
1671
|
+
font-variant-numeric: tabular-nums;
|
|
1672
|
+
color: var(--lv-text-faint);
|
|
1673
|
+
white-space: nowrap;
|
|
1674
|
+
flex: 0 0 auto;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
.lv-timeline-desc {
|
|
1678
|
+
font-size: var(--lv-text-sm);
|
|
1679
|
+
line-height: var(--lv-lh-sm);
|
|
1680
|
+
color: var(--lv-text-secondary);
|
|
1681
|
+
text-wrap: pretty;
|
|
1682
|
+
max-width: var(--lv-measure);
|
|
1683
|
+
margin-top: 2px;
|
|
1684
|
+
overflow-wrap: break-word;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
1688
|
+
14. STATES — empty / loading / error
|
|
1689
|
+
All three share ONE frame with the same reserved min-height, so the
|
|
1690
|
+
swap between them (and to the loaded content) shifts nothing.
|
|
1691
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
1692
|
+
|
|
1693
|
+
.lv-state {
|
|
1694
|
+
display: flex;
|
|
1695
|
+
flex-direction: column;
|
|
1696
|
+
align-items: center;
|
|
1697
|
+
justify-content: center;
|
|
1698
|
+
gap: var(--lv-space-3);
|
|
1699
|
+
text-align: center;
|
|
1700
|
+
padding: var(--lv-space-8) var(--lv-space-6);
|
|
1701
|
+
min-height: var(--lv-state-min-h);
|
|
1702
|
+
min-width: 0;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
.lv-state[data-surface='inset'] {
|
|
1706
|
+
background: var(--lv-surface-inset);
|
|
1707
|
+
border-radius: var(--lv-radius-sm);
|
|
1708
|
+
box-shadow: var(--lv-elev-inset);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
.lv-state[data-surface='card'] {
|
|
1712
|
+
background: var(--lv-surface-card);
|
|
1713
|
+
border-radius: var(--lv-radius-md);
|
|
1714
|
+
box-shadow: var(--lv-elev-1);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
.lv-state-icon {
|
|
1718
|
+
display: grid;
|
|
1719
|
+
place-items: center;
|
|
1720
|
+
width: 36px;
|
|
1721
|
+
height: 36px;
|
|
1722
|
+
border-radius: var(--lv-radius-full);
|
|
1723
|
+
background: var(--lv-surface-hover);
|
|
1724
|
+
color: var(--lv-text-tertiary);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
.lv-state[data-tone='error'] .lv-state-icon {
|
|
1728
|
+
background: var(--lv-danger-soft);
|
|
1729
|
+
color: var(--lv-danger-text);
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
.lv-state-title {
|
|
1733
|
+
font-size: var(--lv-text-md);
|
|
1734
|
+
line-height: var(--lv-lh-md);
|
|
1735
|
+
font-weight: var(--lv-weight-semibold);
|
|
1736
|
+
color: var(--lv-text-primary);
|
|
1737
|
+
text-wrap: balance;
|
|
1738
|
+
margin: 0;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
.lv-state-body {
|
|
1742
|
+
font-size: var(--lv-text-sm);
|
|
1743
|
+
line-height: var(--lv-lh-sm);
|
|
1744
|
+
color: var(--lv-text-secondary);
|
|
1745
|
+
text-wrap: pretty;
|
|
1746
|
+
max-width: 46ch;
|
|
1747
|
+
margin: 0;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
.lv-state-detail {
|
|
1751
|
+
width: 100%;
|
|
1752
|
+
max-width: 56ch;
|
|
1753
|
+
margin-top: var(--lv-space-1);
|
|
1754
|
+
text-align: start;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
.lv-state-detail summary {
|
|
1758
|
+
font-size: var(--lv-text-xs);
|
|
1759
|
+
color: var(--lv-text-tertiary);
|
|
1760
|
+
cursor: pointer;
|
|
1761
|
+
list-style: none;
|
|
1762
|
+
text-align: center;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
.lv-state-detail summary::-webkit-details-marker {
|
|
1766
|
+
display: none;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
.lv-state-detail pre {
|
|
1770
|
+
margin-top: var(--lv-space-2);
|
|
1771
|
+
padding: var(--lv-space-3);
|
|
1772
|
+
background: var(--lv-surface-inset);
|
|
1773
|
+
box-shadow: var(--lv-elev-inset);
|
|
1774
|
+
border-radius: var(--lv-radius-sm);
|
|
1775
|
+
font-family: var(--lv-font-mono);
|
|
1776
|
+
font-size: var(--lv-text-xs);
|
|
1777
|
+
line-height: 1.5;
|
|
1778
|
+
color: var(--lv-text-secondary);
|
|
1779
|
+
overflow-x: auto;
|
|
1780
|
+
white-space: pre-wrap;
|
|
1781
|
+
overflow-wrap: break-word;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
.lv-state-actions {
|
|
1785
|
+
display: flex;
|
|
1786
|
+
gap: var(--lv-space-2);
|
|
1787
|
+
margin-top: var(--lv-space-1);
|
|
1788
|
+
/* Reserved so an action-less state is the same height. */
|
|
1789
|
+
min-height: 32px;
|
|
1790
|
+
align-items: center;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
/* ── Skeleton ─────────────────────────────────────────────────────── */
|
|
1794
|
+
|
|
1795
|
+
.lv-skeleton {
|
|
1796
|
+
display: block;
|
|
1797
|
+
background: var(--lv-skeleton-base);
|
|
1798
|
+
border-radius: var(--lv-radius-sm);
|
|
1799
|
+
position: relative;
|
|
1800
|
+
overflow: hidden;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
.lv-skeleton::after {
|
|
1804
|
+
content: '';
|
|
1805
|
+
position: absolute;
|
|
1806
|
+
inset: 0;
|
|
1807
|
+
background: linear-gradient(
|
|
1808
|
+
90deg,
|
|
1809
|
+
transparent,
|
|
1810
|
+
var(--lv-skeleton-sheen),
|
|
1811
|
+
transparent
|
|
1812
|
+
);
|
|
1813
|
+
transform: translateX(-100%);
|
|
1814
|
+
animation: lv-sheen 1.6s var(--lv-ease-in-out) infinite;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
@keyframes lv-sheen {
|
|
1818
|
+
100% {
|
|
1819
|
+
transform: translateX(100%);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
/* Reduced motion: no sheen at all. The static bar IS the final state. */
|
|
1824
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1825
|
+
.lv-skeleton::after {
|
|
1826
|
+
display: none;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
.lv-skeleton-group {
|
|
1831
|
+
display: flex;
|
|
1832
|
+
flex-direction: column;
|
|
1833
|
+
gap: var(--lv-space-2);
|
|
1834
|
+
min-width: 0;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
/* Same surfaces as .lv-state, so a skeleton and the empty/error state it
|
|
1838
|
+
swaps with sit on the identical box. */
|
|
1839
|
+
.lv-skeleton-group[data-surface='inset'] {
|
|
1840
|
+
background: var(--lv-surface-inset);
|
|
1841
|
+
border-radius: var(--lv-radius-sm);
|
|
1842
|
+
box-shadow: var(--lv-elev-inset);
|
|
1843
|
+
padding: var(--lv-space-4);
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
.lv-skeleton-group[data-surface='card'] {
|
|
1847
|
+
background: var(--lv-surface-card);
|
|
1848
|
+
border-radius: var(--lv-radius-md);
|
|
1849
|
+
box-shadow: var(--lv-elev-1);
|
|
1850
|
+
padding: var(--lv-space-4);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
1854
|
+
15. SHOWCASE-ONLY helpers (the demo page uses these; apps do not).
|
|
1855
|
+
══════════════════════════════════════════════════════════════════════ */
|
|
1856
|
+
|
|
1857
|
+
.lv-swatch {
|
|
1858
|
+
height: 44px;
|
|
1859
|
+
border-radius: var(--lv-radius-sm);
|
|
1860
|
+
box-shadow: inset 0 0 0 1px oklch(0 0 0 / 0.08);
|
|
1861
|
+
}
|