@fluid-theme/core 0.4.3
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/AGENTS.md +32 -0
- package/CHANGELOG.md +159 -0
- package/CLAUDE.md +104 -0
- package/GEMINI.md +48 -0
- package/LICENSE +21 -0
- package/README.md +253 -0
- package/dist/fluid.min.css +2 -0
- package/fluid-agent-kit/README.md +57 -0
- package/fluid-agent-kit/skills/accessibility.md +41 -0
- package/fluid-agent-kit/skills/css-conventions.md +51 -0
- package/fluid-agent-kit/skills/image-optimization.md +115 -0
- package/fluid-agent-kit/skills/ux-rules.md +43 -0
- package/package.json +68 -0
- package/src/animations/scroll-reveal.css +224 -0
- package/src/components/accordion.css +250 -0
- package/src/components/alert.css +453 -0
- package/src/components/avatar.css +192 -0
- package/src/components/badge.css +182 -0
- package/src/components/button.css +387 -0
- package/src/components/card.css +650 -0
- package/src/components/chip.css +143 -0
- package/src/components/dropdown.css +335 -0
- package/src/components/empty-state.css +105 -0
- package/src/components/form.css +508 -0
- package/src/components/hero.css +293 -0
- package/src/components/indicator.css +455 -0
- package/src/components/loading.css +274 -0
- package/src/components/modal.css +364 -0
- package/src/components/nav.css +726 -0
- package/src/components/scroll-progress.css +104 -0
- package/src/components/search-input.css +125 -0
- package/src/components/settings-panel.css +603 -0
- package/src/components/step-indicator.css +188 -0
- package/src/components/table.css +350 -0
- package/src/components/themed-images.css +229 -0
- package/src/components/tooltip.css +247 -0
- package/src/components/version-badge.css +70 -0
- package/src/core/base.css +884 -0
- package/src/core/reset.css +159 -0
- package/src/core/tokens.css +2416 -0
- package/src/fluid.css +242 -0
- package/src/layouts/positioning.css +626 -0
- package/src/sections/cta.css +210 -0
- package/src/sections/features.css +356 -0
- package/src/sections/footer.css +283 -0
- package/src/sections/stats.css +234 -0
- package/src/sections/team.css +181 -0
- package/src/sections/testimonials.css +251 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fluid Framework - Status Indicator System
|
|
3
|
+
* @layer components
|
|
4
|
+
*
|
|
5
|
+
* A semantic status system for scores, progress, and feedback.
|
|
6
|
+
* Uses data-status to drive ALL visual styling - prevents color drift.
|
|
7
|
+
*
|
|
8
|
+
* Philosophy:
|
|
9
|
+
* - Status is the single source of truth
|
|
10
|
+
* - Score → Status → Theme Token → Visuals
|
|
11
|
+
* - No component may mix status color families
|
|
12
|
+
*
|
|
13
|
+
* Status Tiers (default thresholds):
|
|
14
|
+
* - good: ≥80% (success family)
|
|
15
|
+
* - moderate: 60-79% (warning family)
|
|
16
|
+
* - severe: <60% (error family)
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* <div data-status="good">88% - Meeting goals</div>
|
|
20
|
+
* <progress data-status="moderate" value="65" max="100"></progress>
|
|
21
|
+
* <span class="badge" data-status="severe">Needs attention</span>
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
@layer components {
|
|
25
|
+
|
|
26
|
+
/* ========================================
|
|
27
|
+
STATUS COLOR SYSTEM
|
|
28
|
+
All colors derive from status token
|
|
29
|
+
======================================== */
|
|
30
|
+
|
|
31
|
+
/* Good (Success family) - ≥80% */
|
|
32
|
+
[data-status="good"] {
|
|
33
|
+
--status-color: var(--color-success);
|
|
34
|
+
--status-bg: var(--color-success-subtle);
|
|
35
|
+
--status-text: var(--color-success);
|
|
36
|
+
--status-border: var(--color-success);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Moderate (Warning family) - 60-79% */
|
|
40
|
+
[data-status="moderate"] {
|
|
41
|
+
--status-color: var(--color-warning);
|
|
42
|
+
--status-bg: var(--color-warning-subtle);
|
|
43
|
+
--status-text: var(--color-warning);
|
|
44
|
+
--status-text: oklch(from var(--color-warning) calc(l - 0.2) c h);
|
|
45
|
+
--status-border: var(--color-warning);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Severe (Error family) - <60% */
|
|
49
|
+
[data-status="severe"] {
|
|
50
|
+
--status-color: var(--color-error);
|
|
51
|
+
--status-bg: var(--color-error-subtle);
|
|
52
|
+
--status-text: var(--color-error);
|
|
53
|
+
--status-border: var(--color-error);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Neutral (no score/status) */
|
|
57
|
+
[data-status="neutral"] {
|
|
58
|
+
--status-color: var(--color-text-muted);
|
|
59
|
+
--status-bg: var(--color-surface);
|
|
60
|
+
--status-text: var(--color-text-muted);
|
|
61
|
+
--status-border: var(--color-border);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/* ========================================
|
|
66
|
+
ENHANCED PROGRESS BAR
|
|
67
|
+
Animated, labeled, status-aware
|
|
68
|
+
======================================== */
|
|
69
|
+
|
|
70
|
+
.progress {
|
|
71
|
+
--_progress-height: var(--space-3);
|
|
72
|
+
--_progress-radius: var(--radius-full);
|
|
73
|
+
--_progress-track: var(--color-border);
|
|
74
|
+
--_progress-fill: var(--status-color, var(--accent));
|
|
75
|
+
|
|
76
|
+
position: relative;
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
gap: var(--space-2);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Progress label row */
|
|
83
|
+
.progress__label {
|
|
84
|
+
display: flex;
|
|
85
|
+
justify-content: space-between;
|
|
86
|
+
align-items: baseline;
|
|
87
|
+
font-size: var(--font-size-sm);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.progress__title {
|
|
91
|
+
font-weight: var(--weight-medium);
|
|
92
|
+
color: var(--color-text);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.progress__value {
|
|
96
|
+
font-weight: var(--weight-semibold);
|
|
97
|
+
font-variant-numeric: tabular-nums;
|
|
98
|
+
color: var(--status-text, var(--color-text));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Progress track (background) - shows the "empty" portion */
|
|
102
|
+
.progress__track {
|
|
103
|
+
position: relative;
|
|
104
|
+
block-size: var(--_progress-height);
|
|
105
|
+
background-color: var(--_progress-track);
|
|
106
|
+
border-radius: var(--_progress-radius);
|
|
107
|
+
overflow: hidden;
|
|
108
|
+
/* Subtle inner shadow to define the track boundary */
|
|
109
|
+
box-shadow: inset 0 1px 2px oklch(0% 0 0 / 0.1);
|
|
110
|
+
/* Light mode: use status-aware tinted track */
|
|
111
|
+
--_progress-track: color-mix(in oklch, var(--status-color, var(--accent)) 15%, var(--color-surface-raised));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Dark mode: make track more visible with stronger tint */
|
|
115
|
+
@media (prefers-color-scheme: dark) {
|
|
116
|
+
.progress__track {
|
|
117
|
+
--_progress-track: color-mix(in oklch, var(--status-color, var(--accent)) 20%, var(--color-surface-raised));
|
|
118
|
+
box-shadow: inset 0 1px 3px oklch(0% 0 0 / 0.3);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
[data-theme="dark"] .progress__track {
|
|
123
|
+
--_progress-track: color-mix(in oklch, var(--status-color, var(--accent)) 20%, var(--color-surface-raised));
|
|
124
|
+
box-shadow: inset 0 1px 3px oklch(0% 0 0 / 0.3);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Progress fill bar */
|
|
128
|
+
.progress__fill {
|
|
129
|
+
position: absolute;
|
|
130
|
+
inset-block: 0;
|
|
131
|
+
inset-inline-start: 0;
|
|
132
|
+
inline-size: var(--progress-percent, 0%);
|
|
133
|
+
background-color: var(--_progress-fill);
|
|
134
|
+
border-radius: var(--_progress-radius);
|
|
135
|
+
transition: inline-size var(--duration-slow) var(--ease-fluid);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Animated progress - starts at 0, fills when visible */
|
|
139
|
+
.progress[data-animate] .progress__fill {
|
|
140
|
+
inline-size: 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.progress[data-animate][data-visible] .progress__fill {
|
|
144
|
+
inline-size: var(--progress-percent, 0%);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
/* ========================================
|
|
149
|
+
PROGRESS SIZE VARIANTS
|
|
150
|
+
======================================== */
|
|
151
|
+
|
|
152
|
+
.progress[data-size="sm"] {
|
|
153
|
+
--_progress-height: var(--space-2);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.progress[data-size="lg"] {
|
|
157
|
+
--_progress-height: var(--space-4);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.progress[data-size="xl"] {
|
|
161
|
+
--_progress-height: var(--space-6);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
/* ========================================
|
|
166
|
+
PROGRESS WITH THRESHOLD MARKERS
|
|
167
|
+
Visual indicators for score bands
|
|
168
|
+
======================================== */
|
|
169
|
+
|
|
170
|
+
.progress__markers {
|
|
171
|
+
position: absolute;
|
|
172
|
+
inset: 0;
|
|
173
|
+
pointer-events: none;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.progress__marker {
|
|
177
|
+
position: absolute;
|
|
178
|
+
inset-block: calc(var(--_progress-height) * -0.25);
|
|
179
|
+
inline-size: 2px;
|
|
180
|
+
background-color: var(--color-text-subtle);
|
|
181
|
+
opacity: 0.5;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* Position markers at thresholds */
|
|
185
|
+
.progress__marker[data-at="60"] {
|
|
186
|
+
inset-inline-start: 60%;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.progress__marker[data-at="80"] {
|
|
190
|
+
inset-inline-start: 80%;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
/* ========================================
|
|
195
|
+
STATUS BADGE / PILL
|
|
196
|
+
Compact status indicator
|
|
197
|
+
======================================== */
|
|
198
|
+
|
|
199
|
+
.badge {
|
|
200
|
+
display: inline-flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
gap: var(--space-1);
|
|
203
|
+
padding-block: var(--space-1);
|
|
204
|
+
padding-inline: var(--space-2);
|
|
205
|
+
font-size: var(--font-size-xs);
|
|
206
|
+
font-weight: var(--weight-medium);
|
|
207
|
+
line-height: var(--leading-none);
|
|
208
|
+
color: var(--status-text, var(--color-text));
|
|
209
|
+
background-color: var(--status-bg, var(--color-surface));
|
|
210
|
+
border-radius: var(--radius-full);
|
|
211
|
+
white-space: nowrap;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* Filled variant - stronger emphasis */
|
|
215
|
+
.badge[data-variant="filled"] {
|
|
216
|
+
color: var(--white);
|
|
217
|
+
background-color: var(--status-color, var(--accent));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/* Outline variant */
|
|
221
|
+
.badge[data-variant="outline"] {
|
|
222
|
+
background-color: transparent;
|
|
223
|
+
border: 1px solid var(--status-border, var(--color-border));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Badge sizes */
|
|
227
|
+
.badge[data-size="sm"] {
|
|
228
|
+
padding-block: var(--space-0-5);
|
|
229
|
+
padding-inline: var(--space-1-5);
|
|
230
|
+
font-size: var(--font-size-xs);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.badge[data-size="lg"] {
|
|
234
|
+
padding-block: var(--space-2);
|
|
235
|
+
padding-inline: var(--space-3);
|
|
236
|
+
font-size: var(--font-size-sm);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Badge with dot indicator */
|
|
240
|
+
.badge[data-dot]::before {
|
|
241
|
+
content: "";
|
|
242
|
+
inline-size: 0.5em;
|
|
243
|
+
block-size: 0.5em;
|
|
244
|
+
border-radius: var(--radius-full);
|
|
245
|
+
background-color: var(--status-color, var(--accent));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
/* ========================================
|
|
250
|
+
STATUS CONTAINER / CARD
|
|
251
|
+
Wrap content in status context
|
|
252
|
+
======================================== */
|
|
253
|
+
|
|
254
|
+
.status-card {
|
|
255
|
+
padding: var(--space-4);
|
|
256
|
+
background-color: var(--status-bg, var(--color-surface));
|
|
257
|
+
border: 1px solid var(--status-border, var(--color-border-subtle));
|
|
258
|
+
border-radius: var(--radius-lg);
|
|
259
|
+
border-inline-start-width: 4px;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.status-card__header {
|
|
263
|
+
display: flex;
|
|
264
|
+
justify-content: space-between;
|
|
265
|
+
align-items: center;
|
|
266
|
+
margin-block-end: var(--space-3);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.status-card__title {
|
|
270
|
+
font-weight: var(--weight-semibold);
|
|
271
|
+
color: var(--color-text);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.status-card__score {
|
|
275
|
+
font-size: var(--font-size-2xl);
|
|
276
|
+
font-weight: var(--weight-bold);
|
|
277
|
+
font-variant-numeric: tabular-nums;
|
|
278
|
+
color: var(--status-text, var(--color-text));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
/* ========================================
|
|
283
|
+
SCORE LEGEND / KEY
|
|
284
|
+
Display threshold explanations
|
|
285
|
+
======================================== */
|
|
286
|
+
|
|
287
|
+
.score-legend {
|
|
288
|
+
display: flex;
|
|
289
|
+
flex-wrap: wrap;
|
|
290
|
+
gap: var(--space-4);
|
|
291
|
+
padding: var(--space-4);
|
|
292
|
+
background-color: var(--color-surface);
|
|
293
|
+
border-radius: var(--radius-lg);
|
|
294
|
+
font-size: var(--font-size-sm);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.score-legend__item {
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: center;
|
|
300
|
+
gap: var(--space-2);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.score-legend__color {
|
|
304
|
+
inline-size: var(--space-4);
|
|
305
|
+
block-size: var(--space-4);
|
|
306
|
+
border-radius: var(--radius-sm);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.score-legend__item[data-status="good"] .score-legend__color {
|
|
310
|
+
background-color: var(--color-success);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.score-legend__item[data-status="moderate"] .score-legend__color {
|
|
314
|
+
background-color: var(--color-warning);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.score-legend__item[data-status="severe"] .score-legend__color {
|
|
318
|
+
background-color: var(--color-error);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.score-legend__range {
|
|
322
|
+
color: var(--color-text-muted);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.score-legend__label {
|
|
326
|
+
font-weight: var(--weight-medium);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
/* ========================================
|
|
331
|
+
FRACTION DISPLAY
|
|
332
|
+
Show "23 / 26" style values
|
|
333
|
+
======================================== */
|
|
334
|
+
|
|
335
|
+
.fraction {
|
|
336
|
+
display: inline-flex;
|
|
337
|
+
align-items: baseline;
|
|
338
|
+
gap: var(--space-1);
|
|
339
|
+
font-variant-numeric: tabular-nums;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.fraction__numerator {
|
|
343
|
+
font-weight: var(--weight-bold);
|
|
344
|
+
color: var(--status-text, var(--color-text));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.fraction__separator {
|
|
348
|
+
color: var(--color-text-subtle);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.fraction__denominator {
|
|
352
|
+
color: var(--color-text-muted);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
/* ========================================
|
|
357
|
+
MOTION-AWARE ANIMATIONS
|
|
358
|
+
Each theme has its own progress animation style
|
|
359
|
+
======================================== */
|
|
360
|
+
|
|
361
|
+
/* Still: instant fill - no animation */
|
|
362
|
+
[data-motion="still"] .progress__fill {
|
|
363
|
+
transition: none;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* Serene: very slow, gentle fill */
|
|
367
|
+
[data-motion="serene"] .progress__fill {
|
|
368
|
+
transition: inline-size var(--duration-slowest) var(--ease-fluid);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* Trickling: subtle, quick fill */
|
|
372
|
+
[data-motion="trickling"] .progress__fill {
|
|
373
|
+
transition: inline-size var(--duration-slow) var(--ease-fluid);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* Stream: smooth horizontal flow */
|
|
377
|
+
[data-motion="stream"] .progress__fill {
|
|
378
|
+
transition: inline-size var(--duration-slow) var(--ease-fluid);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* Flowing: balanced, organic fill (default) */
|
|
382
|
+
[data-motion="flowing"] .progress__fill,
|
|
383
|
+
:root:not([data-motion]) .progress__fill {
|
|
384
|
+
transition: inline-size var(--duration-slow) var(--ease-fluid);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* Cascade: bouncy waterfall effect */
|
|
388
|
+
[data-motion="cascade"] .progress__fill {
|
|
389
|
+
transition: inline-size var(--duration-normal) var(--ease-settle);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/* Rapids: fast, energetic with overshoot */
|
|
393
|
+
[data-motion="rapids"] .progress__fill {
|
|
394
|
+
transition: inline-size var(--duration-normal) var(--ease-settle);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* Tsunami: wave animation - rise, crest, crash, bounce, settle */
|
|
398
|
+
@keyframes tsunami-progress-fill {
|
|
399
|
+
0% {
|
|
400
|
+
inline-size: 0;
|
|
401
|
+
transform: scaleY(1);
|
|
402
|
+
}
|
|
403
|
+
25% {
|
|
404
|
+
/* Rising - wave builds up, overshoots target */
|
|
405
|
+
inline-size: calc(var(--progress-percent, 0%) * 1.15);
|
|
406
|
+
transform: scaleY(1.3);
|
|
407
|
+
}
|
|
408
|
+
40% {
|
|
409
|
+
/* Cresting - peak of the wave */
|
|
410
|
+
inline-size: calc(var(--progress-percent, 0%) * 1.1);
|
|
411
|
+
transform: scaleY(1.4);
|
|
412
|
+
}
|
|
413
|
+
60% {
|
|
414
|
+
/* Crashing down - undershoots */
|
|
415
|
+
inline-size: calc(var(--progress-percent, 0%) * 0.92);
|
|
416
|
+
transform: scaleY(0.8);
|
|
417
|
+
}
|
|
418
|
+
75% {
|
|
419
|
+
/* Bounce back up */
|
|
420
|
+
inline-size: calc(var(--progress-percent, 0%) * 1.03);
|
|
421
|
+
transform: scaleY(1.1);
|
|
422
|
+
}
|
|
423
|
+
90% {
|
|
424
|
+
/* Settling */
|
|
425
|
+
inline-size: calc(var(--progress-percent, 0%) * 0.99);
|
|
426
|
+
transform: scaleY(0.95);
|
|
427
|
+
}
|
|
428
|
+
100% {
|
|
429
|
+
/* Final position */
|
|
430
|
+
inline-size: var(--progress-percent, 0%);
|
|
431
|
+
transform: scaleY(1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
[data-motion="tsunami"] .progress__fill {
|
|
436
|
+
transition: none;
|
|
437
|
+
transform-origin: left center;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
[data-motion="tsunami"] .progress[data-animate][data-visible] .progress__fill {
|
|
441
|
+
animation: tsunami-progress-fill var(--duration-slower) var(--ease-settle) forwards;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/* Non-animated tsunami progress - just use transition */
|
|
445
|
+
[data-motion="tsunami"] .progress:not([data-animate]) .progress__fill {
|
|
446
|
+
transition: inline-size var(--duration-slower) var(--ease-settle);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/* Reduced motion: respect preference */
|
|
450
|
+
@media (prefers-reduced-motion: reduce) {
|
|
451
|
+
.progress__fill {
|
|
452
|
+
transition: none;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fluid Framework - Loading & Skeleton Component
|
|
3
|
+
* @layer components
|
|
4
|
+
*
|
|
5
|
+
* Loading spinners and skeleton screens for loading states.
|
|
6
|
+
* Respects prefers-reduced-motion.
|
|
7
|
+
*
|
|
8
|
+
* Spinner Usage:
|
|
9
|
+
* <div class="spinner" aria-label="Loading"></div>
|
|
10
|
+
* <div class="spinner" data-size="sm"></div>
|
|
11
|
+
* <div class="spinner" data-size="lg"></div>
|
|
12
|
+
*
|
|
13
|
+
* Skeleton Usage:
|
|
14
|
+
* <div class="skeleton" style="--skeleton-h: 1rem; --skeleton-w: 60%;"></div>
|
|
15
|
+
* <div class="skeleton" data-variant="circle"></div>
|
|
16
|
+
* <div class="skeleton" data-variant="card"></div>
|
|
17
|
+
*
|
|
18
|
+
* Skeleton Card (preset layout):
|
|
19
|
+
* <div class="skeleton-card">
|
|
20
|
+
* <div class="skeleton" data-variant="media"></div>
|
|
21
|
+
* <div class="skeleton" data-variant="title"></div>
|
|
22
|
+
* <div class="skeleton" data-variant="text"></div>
|
|
23
|
+
* <div class="skeleton" data-variant="text" style="--skeleton-w: 75%;"></div>
|
|
24
|
+
* </div>
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
@layer components {
|
|
28
|
+
/* ========================================
|
|
29
|
+
SPINNER
|
|
30
|
+
======================================== */
|
|
31
|
+
|
|
32
|
+
.spinner {
|
|
33
|
+
--_spinner-size: 2rem;
|
|
34
|
+
--_spinner-width: 3px;
|
|
35
|
+
--_spinner-color: var(--accent);
|
|
36
|
+
|
|
37
|
+
display: inline-block;
|
|
38
|
+
inline-size: var(--_spinner-size);
|
|
39
|
+
block-size: var(--_spinner-size);
|
|
40
|
+
border: var(--_spinner-width) solid color-mix(in oklch, var(--_spinner-color) 20%, transparent);
|
|
41
|
+
border-block-start-color: var(--_spinner-color);
|
|
42
|
+
border-radius: var(--radius-full);
|
|
43
|
+
animation: spinner-rotate var(--duration-slow, 640ms) linear infinite;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@keyframes spinner-rotate {
|
|
47
|
+
to { transform: rotate(360deg); }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Sizes */
|
|
51
|
+
.spinner[data-size="xs"] {
|
|
52
|
+
--_spinner-size: 1rem;
|
|
53
|
+
--_spinner-width: 2px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.spinner[data-size="sm"] {
|
|
57
|
+
--_spinner-size: 1.5rem;
|
|
58
|
+
--_spinner-width: 2px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.spinner[data-size="lg"] {
|
|
62
|
+
--_spinner-size: 3rem;
|
|
63
|
+
--_spinner-width: 4px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.spinner[data-size="xl"] {
|
|
67
|
+
--_spinner-size: 4rem;
|
|
68
|
+
--_spinner-width: 4px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Reduced motion: pulse instead of spin */
|
|
72
|
+
@media (prefers-reduced-motion: reduce) {
|
|
73
|
+
.spinner {
|
|
74
|
+
animation: spinner-pulse 1.5s ease-in-out infinite;
|
|
75
|
+
border-color: color-mix(in oklch, var(--_spinner-color) 30%, transparent);
|
|
76
|
+
border-block-start-color: color-mix(in oklch, var(--_spinner-color) 30%, transparent);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@keyframes spinner-pulse {
|
|
80
|
+
0%, 100% { opacity: 0.4; }
|
|
81
|
+
50% { opacity: 1; }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
/* ========================================
|
|
87
|
+
DOTS SPINNER (alternative)
|
|
88
|
+
======================================== */
|
|
89
|
+
|
|
90
|
+
.spinner-dots {
|
|
91
|
+
display: inline-flex;
|
|
92
|
+
gap: var(--space-1-5);
|
|
93
|
+
align-items: center;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.spinner-dots::before,
|
|
97
|
+
.spinner-dots::after,
|
|
98
|
+
.spinner-dots span {
|
|
99
|
+
content: "";
|
|
100
|
+
display: block;
|
|
101
|
+
inline-size: 0.5rem;
|
|
102
|
+
block-size: 0.5rem;
|
|
103
|
+
border-radius: var(--radius-full);
|
|
104
|
+
background: var(--accent);
|
|
105
|
+
animation: spinner-dot-bounce var(--duration-slow, 640ms) ease-in-out infinite;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.spinner-dots::after {
|
|
109
|
+
animation-delay: calc(var(--duration-slow, 640ms) * 0.16);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.spinner-dots span {
|
|
113
|
+
animation-delay: calc(var(--duration-slow, 640ms) * 0.32);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@keyframes spinner-dot-bounce {
|
|
117
|
+
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
|
|
118
|
+
40% { transform: scale(1); opacity: 1; }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@media (prefers-reduced-motion: reduce) {
|
|
122
|
+
.spinner-dots::before,
|
|
123
|
+
.spinner-dots::after,
|
|
124
|
+
.spinner-dots span {
|
|
125
|
+
animation: none;
|
|
126
|
+
opacity: 0.6;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
/* ========================================
|
|
132
|
+
LOADING OVERLAY
|
|
133
|
+
Full-area loading state
|
|
134
|
+
======================================== */
|
|
135
|
+
|
|
136
|
+
.loading-overlay {
|
|
137
|
+
position: absolute;
|
|
138
|
+
inset: 0;
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
justify-content: center;
|
|
142
|
+
flex-direction: column;
|
|
143
|
+
gap: var(--space-3);
|
|
144
|
+
background: color-mix(in oklch, var(--color-background) 80%, transparent);
|
|
145
|
+
z-index: var(--z-overlay);
|
|
146
|
+
border-radius: inherit;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.loading-overlay__text {
|
|
150
|
+
font-size: var(--font-size-sm);
|
|
151
|
+
color: var(--color-text-muted);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
/* ========================================
|
|
156
|
+
SKELETON SCREENS
|
|
157
|
+
======================================== */
|
|
158
|
+
|
|
159
|
+
.skeleton {
|
|
160
|
+
--skeleton-h: 1rem;
|
|
161
|
+
--skeleton-w: 100%;
|
|
162
|
+
--_skeleton-radius: var(--radius-md);
|
|
163
|
+
|
|
164
|
+
display: block;
|
|
165
|
+
inline-size: var(--skeleton-w);
|
|
166
|
+
block-size: var(--skeleton-h);
|
|
167
|
+
border-radius: var(--_skeleton-radius);
|
|
168
|
+
background: linear-gradient(
|
|
169
|
+
90deg,
|
|
170
|
+
var(--color-surface) 0%,
|
|
171
|
+
var(--color-surface-raised) 40%,
|
|
172
|
+
var(--color-surface) 80%
|
|
173
|
+
);
|
|
174
|
+
background-size: 200% 100%;
|
|
175
|
+
animation: skeleton-shimmer 1.5s ease-in-out infinite;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@keyframes skeleton-shimmer {
|
|
179
|
+
0% { background-position: 200% 0; }
|
|
180
|
+
100% { background-position: -200% 0; }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@media (prefers-reduced-motion: reduce) {
|
|
184
|
+
.skeleton {
|
|
185
|
+
animation: none;
|
|
186
|
+
background: var(--color-surface);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
/* ========================================
|
|
192
|
+
SKELETON VARIANTS
|
|
193
|
+
======================================== */
|
|
194
|
+
|
|
195
|
+
/* Circle: avatar placeholder */
|
|
196
|
+
.skeleton[data-variant="circle"] {
|
|
197
|
+
--skeleton-w: 2.5rem;
|
|
198
|
+
--skeleton-h: 2.5rem;
|
|
199
|
+
--_skeleton-radius: var(--radius-full);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Title: heading placeholder */
|
|
203
|
+
.skeleton[data-variant="title"] {
|
|
204
|
+
--skeleton-h: 1.5rem;
|
|
205
|
+
--skeleton-w: 60%;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Text: body line placeholder */
|
|
209
|
+
.skeleton[data-variant="text"] {
|
|
210
|
+
--skeleton-h: 0.875rem;
|
|
211
|
+
--skeleton-w: var(--skeleton-w, 100%);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* Media: image/video placeholder */
|
|
215
|
+
.skeleton[data-variant="media"] {
|
|
216
|
+
--skeleton-h: 12rem;
|
|
217
|
+
--_skeleton-radius: var(--radius-lg);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/* Button placeholder */
|
|
221
|
+
.skeleton[data-variant="button"] {
|
|
222
|
+
--skeleton-h: 2.75rem;
|
|
223
|
+
--skeleton-w: 8rem;
|
|
224
|
+
--_skeleton-radius: var(--radius-button, var(--radius-md));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
/* ========================================
|
|
229
|
+
SKELETON CARD
|
|
230
|
+
Pre-built card loading state
|
|
231
|
+
======================================== */
|
|
232
|
+
|
|
233
|
+
.skeleton-card {
|
|
234
|
+
display: flex;
|
|
235
|
+
flex-direction: column;
|
|
236
|
+
gap: var(--space-3);
|
|
237
|
+
padding: var(--space-5);
|
|
238
|
+
background: var(--color-surface-raised);
|
|
239
|
+
border: var(--border-width) solid var(--color-border-subtle);
|
|
240
|
+
border-radius: var(--radius-xl);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.skeleton-card .skeleton[data-variant="media"] {
|
|
244
|
+
margin: calc(var(--space-5) * -1);
|
|
245
|
+
margin-block-end: var(--space-3);
|
|
246
|
+
inline-size: calc(100% + var(--space-5) * 2);
|
|
247
|
+
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
/* ========================================
|
|
252
|
+
THEME-SPECIFIC STYLES
|
|
253
|
+
======================================== */
|
|
254
|
+
|
|
255
|
+
/* Still: no animation */
|
|
256
|
+
[data-motion="still"] .spinner {
|
|
257
|
+
animation-duration: 800ms;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
[data-motion="still"] .skeleton {
|
|
261
|
+
animation: none;
|
|
262
|
+
background: var(--color-surface);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* Tsunami: asymmetric skeleton cards */
|
|
266
|
+
[data-motion="tsunami"] .skeleton-card {
|
|
267
|
+
border-radius: var(--radius-asymmetric, 0 24px 0 0);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* Rapids: faster spinner */
|
|
271
|
+
[data-motion="rapids"] .spinner {
|
|
272
|
+
animation-duration: 500ms;
|
|
273
|
+
}
|
|
274
|
+
}
|