@fluid-theme/core 0.4.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -29
- package/CHANGELOG.md +543 -2
- package/CLAUDE.md +193 -14
- package/GEMINI.md +6 -45
- package/README.md +17 -8
- package/assets/js/compare.js +77 -0
- package/assets/js/fit-text.js +103 -0
- package/assets/js/lightbox.js +153 -0
- package/assets/js/scroll-reveal.js +72 -0
- package/assets/js/toc-build.mjs +86 -0
- package/assets/js/toc.js +384 -0
- package/dist/fluid-1x.min.css +4 -0
- package/dist/fluid-fonts.min.css +2 -0
- package/dist/fluid-legacy-1x.css +3 -0
- package/dist/fluid-nav.min.css +3 -0
- package/dist/fluid-toc.min.css +3 -0
- package/dist/fluid-zero.min.css +3 -0
- package/dist/fluid.min.css +3 -2
- package/dist/fonts/OFL-newsreader.txt +93 -0
- package/dist/fonts/OFL-schibsted-grotesk.txt +93 -0
- package/dist/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
- package/dist/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/dist/fonts/schibsted-grotesk-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/schibsted-grotesk-latin-wght-normal.woff2 +0 -0
- package/fluid-agent-kit/README.md +1 -0
- package/fluid-agent-kit/skills/accessibility.md +3 -1
- package/fluid-agent-kit/skills/css-conventions.md +22 -5
- package/fluid-agent-kit/skills/image-optimization.md +2 -0
- package/fluid-agent-kit/skills/preset-composition.md +108 -0
- package/package.json +29 -3
- package/src/animations/scroll-reveal.css +75 -193
- package/src/components/accordion.css +6 -47
- package/src/components/alert.css +47 -88
- package/src/components/avatar.css +4 -20
- package/src/components/badge.css +25 -48
- package/src/components/button.css +69 -175
- package/src/components/card.css +146 -481
- package/src/components/chip.css +46 -12
- package/src/components/compare.css +234 -0
- package/src/components/dropdown.css +10 -67
- package/src/components/empty-state.css +4 -0
- package/src/components/form.css +113 -81
- package/src/components/gallery.css +252 -0
- package/src/components/hero.css +203 -88
- package/src/components/indicator.css +23 -117
- package/src/components/loading.css +7 -29
- package/src/components/modal.css +12 -65
- package/src/components/nav-modes.css +261 -0
- package/src/components/nav.css +324 -392
- package/src/components/search-input.css +4 -2
- package/src/components/step-indicator.css +26 -27
- package/src/components/table.css +64 -171
- package/src/components/toc.css +254 -0
- package/src/components/tooltip.css +5 -46
- package/src/core/base.css +393 -547
- package/src/core/reset.css +17 -5
- package/src/core/tokens.css +426 -2276
- package/src/fluid.css +258 -17
- package/src/fonts/OFL-newsreader.txt +93 -0
- package/src/fonts/OFL-schibsted-grotesk.txt +93 -0
- package/src/fonts/fluid-fonts.css +55 -0
- package/src/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
- package/src/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/src/fonts/schibsted-grotesk-latin-wght-italic.woff2 +0 -0
- package/src/fonts/schibsted-grotesk-latin-wght-normal.woff2 +0 -0
- package/src/layouts/positioning.css +6 -3
- package/src/legacy-1x.css +61 -0
- package/src/sections/cta.css +33 -101
- package/src/sections/features.css +42 -219
- package/src/sections/footer.css +53 -134
- package/src/sections/stats.css +40 -153
- package/src/sections/team.css +24 -113
- package/src/sections/testimonials.css +34 -148
- package/src/zero/base.css +166 -0
- package/src/zero/tokens.css +64 -0
- package/src/zero.css +20 -0
- package/src/components/scroll-progress.css +0 -104
- package/src/components/settings-panel.css +0 -603
- package/src/components/themed-images.css +0 -229
- package/src/components/version-badge.css +0 -70
package/src/components/hero.css
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fluid
|
|
2
|
+
* Fluid Theme - Hero Component
|
|
3
3
|
* @layer components
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - Backgrounds, overlays, and content are separate concerns
|
|
11
|
-
* - Serene presence, not aggressive attention-seeking
|
|
5
|
+
* Plain background, start-aligned, content-tall. Order: eyebrow → display
|
|
6
|
+
* headline → lede → actions; the default media is the plate, a full-bleed
|
|
7
|
+
* figure under the text at its natural ratio (data-aspect crops it).
|
|
8
|
+
* The headline is the base .display rule: it fills the hero's column, and
|
|
9
|
+
* hand breaks (<br>) hold as the screen grows (src/core/base.css).
|
|
12
10
|
*
|
|
13
11
|
* Usage:
|
|
14
|
-
* <section class="hero"
|
|
15
|
-
* <
|
|
16
|
-
* <
|
|
17
|
-
* <
|
|
12
|
+
* <section class="hero">
|
|
13
|
+
* <p class="eyebrow">Studio</p>
|
|
14
|
+
* <h1 class="hero__headline" data-fit>Make the<br>quiet thing<br>loud.</h1>
|
|
15
|
+
* <p class="lede">One or two sentences.</p>
|
|
16
|
+
* <div class="hero__actions"><a class="button" href="#">Start</a></div>
|
|
17
|
+
* <figure class="hero__plate"><img src="..." alt="..." width="1600" height="900"></figure>
|
|
18
18
|
* </section>
|
|
19
|
+
*
|
|
20
|
+
* Themes: data-theme="dark" (a dark band: the role scope, tokens.css),
|
|
21
|
+
* "accent" (solid accent, shared with data-bg="accent-strong"), "gradient",
|
|
22
|
+
* "subtle"; data-overlay over a .hero__background image.
|
|
19
23
|
*/
|
|
20
24
|
|
|
21
25
|
@layer components {
|
|
@@ -25,9 +29,11 @@
|
|
|
25
29
|
A full-width section ready for content
|
|
26
30
|
======================================== */
|
|
27
31
|
|
|
32
|
+
/* The inline padding puts the content on the .container edges while the
|
|
33
|
+
background runs full width. */
|
|
28
34
|
.hero {
|
|
29
|
-
--hero-min-height:
|
|
30
|
-
--hero-background: var(--color-
|
|
35
|
+
--hero-min-height: auto;
|
|
36
|
+
--hero-background: var(--color-background);
|
|
31
37
|
--hero-text-color: var(--color-text);
|
|
32
38
|
|
|
33
39
|
position: relative;
|
|
@@ -36,9 +42,19 @@
|
|
|
36
42
|
color: var(--hero-text-color);
|
|
37
43
|
overflow: hidden;
|
|
38
44
|
container-type: inline-size;
|
|
45
|
+
padding-inline: max(var(--page-edge), (100% - var(--container-content)) / 2);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* An inner .container owns the edge, as in every .section; padding here
|
|
49
|
+
too would inset the content twice. */
|
|
50
|
+
.hero:has(> .container) { padding-inline: 0; }
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
/* Generous space goes between sections, not above the first line: at
|
|
53
|
+
1440 × 900 the old 160px top pushed the actions onto the fold. It
|
|
54
|
+
follows --space-section, so a tight look keeps a tight hero. The
|
|
55
|
+
data-padding scale (layouts layer) wins whenever the author sets it. */
|
|
56
|
+
.hero:not([data-padding]) {
|
|
57
|
+
padding-block: min(var(--space-xl), var(--space-section));
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
/* Ensure content stays above backgrounds/overlays */
|
|
@@ -58,12 +74,10 @@
|
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
.hero[data-size="full"] {
|
|
61
|
-
--hero-min-height:
|
|
77
|
+
--hero-min-height: 100svh;
|
|
62
78
|
}
|
|
63
79
|
|
|
64
|
-
|
|
65
|
-
--hero-min-height: auto;
|
|
66
|
-
}
|
|
80
|
+
/* data-size="auto" is the default: no rule. */
|
|
67
81
|
|
|
68
82
|
|
|
69
83
|
/* ========================================
|
|
@@ -75,33 +89,77 @@
|
|
|
75
89
|
max-inline-size: var(--content-width, var(--container-md));
|
|
76
90
|
}
|
|
77
91
|
|
|
92
|
+
/* The headline's face, size, leading and balance are the base .display
|
|
93
|
+
rule; here only its overflow safety (it is not always a heading; a long
|
|
94
|
+
word breaks, never with an automatic hyphen) and the space to the lede.
|
|
95
|
+
The space grows with the headline (0.18em), so a display face with a
|
|
96
|
+
long descender (a Didone at 190px) never inks into the lede. */
|
|
78
97
|
.hero__headline {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
letter-spacing: -0.03em;
|
|
83
|
-
line-height: var(--leading-tight);
|
|
84
|
-
margin-block-end: var(--space-4);
|
|
85
|
-
text-wrap: balance; /* Prevent orphans in headlines */
|
|
98
|
+
margin-block: 0 max(var(--space-m), 0.18em);
|
|
99
|
+
overflow-wrap: break-word;
|
|
100
|
+
hyphens: manual;
|
|
86
101
|
}
|
|
87
102
|
|
|
88
|
-
.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
.hero .eyebrow { margin-block-end: var(--space-m); }
|
|
104
|
+
|
|
105
|
+
/* The label over a display headline: the trimmed cap height sat 24px
|
|
106
|
+
under it at 160px type, glued on. The gap grows with the headline
|
|
107
|
+
(about 48px at 160px, 20px on a phone); margins collapse with the
|
|
108
|
+
eyebrow's own. */
|
|
109
|
+
.eyebrow + :is(.hero__headline, .display) { margin-block-start: max(var(--space-m), 0.3em); }
|
|
110
|
+
|
|
111
|
+
/* In a gap layout (the stack's <div class="container stack"> inside the
|
|
112
|
+
hero) the gap is the rhythm; margins would add to it. */
|
|
113
|
+
:is(.stack, .cluster, .grid) > :is(.hero__headline, .hero__subheadline, .lede, .eyebrow) {
|
|
114
|
+
margin-block: 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
:is(.hero__subheadline, .hero .lede) {
|
|
118
|
+
font-family: var(--font-heading);
|
|
119
|
+
font-weight: calc(var(--weight-light) + var(--_dark-weight));
|
|
120
|
+
font-size: var(--step-1);
|
|
121
|
+
line-height: 1.4;
|
|
122
|
+
letter-spacing: -0.005em;
|
|
123
|
+
color: var(--hero-text-muted, var(--hero-text-color));
|
|
124
|
+
max-inline-size: 30em;
|
|
125
|
+
margin-block: 0 var(--space-l);
|
|
96
126
|
}
|
|
97
127
|
|
|
98
128
|
.hero__actions {
|
|
99
129
|
display: flex;
|
|
100
130
|
flex-wrap: wrap;
|
|
101
|
-
|
|
131
|
+
align-items: center;
|
|
132
|
+
gap: var(--space-s) var(--space-m);
|
|
102
133
|
justify-content: inherit;
|
|
103
134
|
}
|
|
104
135
|
|
|
136
|
+
/* The plate: a full-bleed figure under the text, natural ratio. Its
|
|
137
|
+
negative margin is the hero's inline padding measured on the viewport;
|
|
138
|
+
a scrollbar's few extra pixels are clipped by the hero. */
|
|
139
|
+
.hero__plate {
|
|
140
|
+
margin-block: var(--space-xl) calc(-1 * var(--space-xl));
|
|
141
|
+
margin-inline: calc(-1 * max(var(--page-edge), (100vi - var(--container-content)) / 2));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* An uncaptioned plate runs to the hero's bottom edge; a caption keeps
|
|
145
|
+
--space-m above that edge, so its text never sits flush on a dark or
|
|
146
|
+
tinted hero band (visual-scan SEAM, 2026-10-29). */
|
|
147
|
+
.hero__plate:has(> figcaption) {
|
|
148
|
+
margin-block-end: calc(var(--space-m) - var(--space-xl));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.hero__plate :is(img, video) {
|
|
152
|
+
inline-size: 100%;
|
|
153
|
+
border-radius: 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.hero__plate :is(img, video)[data-aspect] { max-block-size: 88svh; }
|
|
157
|
+
|
|
158
|
+
/* The caption returns to the content edge. */
|
|
159
|
+
.hero__plate figcaption {
|
|
160
|
+
margin-inline: max(var(--page-edge), (100vi - var(--container-content)) / 2);
|
|
161
|
+
}
|
|
162
|
+
|
|
105
163
|
|
|
106
164
|
/* ========================================
|
|
107
165
|
HERO BACKGROUNDS
|
|
@@ -113,6 +171,9 @@
|
|
|
113
171
|
inset: 0;
|
|
114
172
|
z-index: 0;
|
|
115
173
|
overflow: hidden;
|
|
174
|
+
/* data-layout="left" (and the other layouts) cap each child's width; the
|
|
175
|
+
background is not content and always fills the hero (#72). */
|
|
176
|
+
max-inline-size: none;
|
|
116
177
|
}
|
|
117
178
|
|
|
118
179
|
.hero__background img,
|
|
@@ -122,13 +183,7 @@
|
|
|
122
183
|
object-fit: cover;
|
|
123
184
|
}
|
|
124
185
|
|
|
125
|
-
/*
|
|
126
|
-
.hero__background[data-focus="top"] img { object-position: center top; }
|
|
127
|
-
.hero__background[data-focus="bottom"] img { object-position: center bottom; }
|
|
128
|
-
.hero__background[data-focus="left"] img { object-position: left center; }
|
|
129
|
-
.hero__background[data-focus="right"] img { object-position: right center; }
|
|
130
|
-
.hero__background[data-focus="center"] img { object-position: center center; }
|
|
131
|
-
|
|
186
|
+
/* data-focus on the wrapper sets --focus (base.css), which the img reads. */
|
|
132
187
|
/* Custom focal point via CSS custom properties */
|
|
133
188
|
.hero__background[style*="--focus"] img {
|
|
134
189
|
object-position: var(--focus-x, 50%) var(--focus-y, 50%);
|
|
@@ -149,7 +204,7 @@
|
|
|
149
204
|
pointer-events: none;
|
|
150
205
|
}
|
|
151
206
|
|
|
152
|
-
/* Overlay intensity
|
|
207
|
+
/* Overlay intensity */
|
|
153
208
|
.hero[data-overlay="light"]::before {
|
|
154
209
|
--hero-overlay: oklch(0% 0 0 / 0.2);
|
|
155
210
|
}
|
|
@@ -178,34 +233,48 @@
|
|
|
178
233
|
Pre-designed color schemes
|
|
179
234
|
======================================== */
|
|
180
235
|
|
|
181
|
-
/* Dark hero
|
|
236
|
+
/* Dark hero: a dark band. Its roles re-resolve dark (tokens.css role scope). */
|
|
182
237
|
.hero[data-theme="dark"] {
|
|
183
|
-
--hero-background:
|
|
184
|
-
--hero-text-color:
|
|
185
|
-
--hero-text-muted:
|
|
238
|
+
--hero-background: var(--band-inverse);
|
|
239
|
+
--hero-text-color: var(--color-text);
|
|
240
|
+
--hero-text-muted: var(--color-text-muted);
|
|
186
241
|
}
|
|
187
242
|
|
|
188
|
-
/* Accent
|
|
243
|
+
/* Accent hero: the solid accent band, shared with data-bg="accent-strong"
|
|
244
|
+
(src/fluid.css re-points the roles and button hooks). */
|
|
189
245
|
.hero[data-theme="accent"] {
|
|
190
246
|
--hero-background: var(--accent);
|
|
191
|
-
--hero-text-color: var(--
|
|
192
|
-
--hero-text-muted:
|
|
247
|
+
--hero-text-color: var(--on-accent);
|
|
248
|
+
--hero-text-muted: var(--on-accent);
|
|
193
249
|
}
|
|
194
250
|
|
|
195
|
-
/*
|
|
196
|
-
.hero[data-theme
|
|
251
|
+
/* Headings in a themed hero take the hero's text colour, not the page's. */
|
|
252
|
+
.hero[data-theme] :is(h1, h2, h3, h4, h5, h6, .hero__headline) {
|
|
253
|
+
color: var(--hero-text-color);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Section bands (data-bg, src/fluid.css) on a hero */
|
|
257
|
+
.hero:is([data-bg="tinted"], [data-bg="alt"]) {
|
|
197
258
|
--hero-background: var(--color-surface);
|
|
198
|
-
--hero-text-color: var(--color-text);
|
|
199
|
-
--hero-text-muted: var(--color-text-muted);
|
|
200
259
|
}
|
|
201
260
|
|
|
202
|
-
|
|
261
|
+
.hero[data-bg="accent"] {
|
|
262
|
+
--hero-background: var(--accent-subtle);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* Subtle: the default hero with a muted lede. */
|
|
266
|
+
.hero[data-theme="subtle"] { --hero-text-muted: var(--color-text-muted); }
|
|
267
|
+
|
|
268
|
+
/* Gradient hero: derives from the accent. Not a role band, so its ink
|
|
269
|
+
and focus are lifted here. */
|
|
203
270
|
.hero[data-theme="gradient"] {
|
|
271
|
+
--accent-ink: oklch(from var(--accent) max(l, 0.76) c h);
|
|
272
|
+
--color-focus: var(--accent-ink);
|
|
204
273
|
--hero-background: linear-gradient(
|
|
205
274
|
135deg,
|
|
206
|
-
color-mix(in oklch, var(--accent) 30%, oklch(
|
|
207
|
-
color-mix(in oklch, var(--accent) 20%, oklch(
|
|
208
|
-
color-mix(in oklch, var(--accent) 15%, oklch(
|
|
275
|
+
color-mix(in oklch, var(--accent) 30%, oklch(from var(--neutral-tint) 0.15 calc(c * 3) h)) 0%,
|
|
276
|
+
color-mix(in oklch, var(--accent) 20%, oklch(from var(--neutral-tint) 0.25 calc(c * 3) h)) 50%,
|
|
277
|
+
color-mix(in oklch, var(--accent) 15%, oklch(from var(--neutral-tint) 0.35 calc(c * 3) h)) 100%
|
|
209
278
|
);
|
|
210
279
|
--hero-text-color: oklch(95% 0.01 0);
|
|
211
280
|
--hero-text-muted: oklch(80% 0.01 0);
|
|
@@ -213,6 +282,26 @@
|
|
|
213
282
|
}
|
|
214
283
|
|
|
215
284
|
|
|
285
|
+
/* Legacy floor (see tokens.css): without relative colour the roles are
|
|
286
|
+
plain light values, so the dark hero goes light like every other dark
|
|
287
|
+
band (its text is the floor's dark text). The gradient keeps fixed
|
|
288
|
+
light text, so its paint is computed here from the default tint. */
|
|
289
|
+
@supports not (color: oklch(from red min(l, 0.5) c h)) {
|
|
290
|
+
.hero[data-theme="dark"] {
|
|
291
|
+
--hero-background: var(--color-background);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.hero[data-theme="gradient"] {
|
|
295
|
+
--hero-background: linear-gradient(
|
|
296
|
+
135deg,
|
|
297
|
+
color-mix(in oklch, var(--accent) 30%, oklch(15% 0.03 75)) 0%,
|
|
298
|
+
color-mix(in oklch, var(--accent) 20%, oklch(25% 0.03 75)) 50%,
|
|
299
|
+
color-mix(in oklch, var(--accent) 15%, oklch(35% 0.03 75)) 100%
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
216
305
|
/* ========================================
|
|
217
306
|
SPLIT HERO
|
|
218
307
|
Content + media side by side
|
|
@@ -229,7 +318,52 @@
|
|
|
229
318
|
inline-size: 100%;
|
|
230
319
|
block-size: 100%;
|
|
231
320
|
object-fit: cover;
|
|
232
|
-
border-radius: var(--radius-
|
|
321
|
+
border-radius: var(--radius-card);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
/* ========================================
|
|
326
|
+
DATA-ARRANGE="auto": the centered stack
|
|
327
|
+
|
|
328
|
+
Lays out flat hero markup as a centered grid: headline,
|
|
329
|
+
subheadline, actions, media. (The per-preset arrangements it
|
|
330
|
+
used to choose between went with the presets.)
|
|
331
|
+
|
|
332
|
+
Requires the flat hero markup (hero__headline, hero__subheadline,
|
|
333
|
+
hero__actions, optional hero__media as direct children). Markup
|
|
334
|
+
using a hero__content wrapper keeps its data-layout behavior —
|
|
335
|
+
the :has() guard leaves it untouched. data-arrange wins over
|
|
336
|
+
data-layout because components layer beats layouts layer.
|
|
337
|
+
======================================== */
|
|
338
|
+
|
|
339
|
+
.hero[data-arrange="auto"]:not(:has(> .hero__content)) {
|
|
340
|
+
display: grid;
|
|
341
|
+
grid-template-columns: minmax(0, 1fr);
|
|
342
|
+
grid-template-areas:
|
|
343
|
+
"headline"
|
|
344
|
+
"subheadline"
|
|
345
|
+
"actions"
|
|
346
|
+
"media";
|
|
347
|
+
align-content: center;
|
|
348
|
+
justify-items: start;
|
|
349
|
+
column-gap: var(--space-10);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.hero[data-arrange="auto"] > .hero__headline { grid-area: headline; }
|
|
353
|
+
.hero[data-arrange="auto"] > .hero__subheadline { grid-area: subheadline; }
|
|
354
|
+
.hero[data-arrange="auto"] > .hero__actions { grid-area: actions; }
|
|
355
|
+
.hero[data-arrange="auto"] > .hero__media { grid-area: media; }
|
|
356
|
+
|
|
357
|
+
/* On narrow viewports the media moves above the actions. */
|
|
358
|
+
@media (max-width: 48rem) {
|
|
359
|
+
.hero[data-arrange="auto"]:not(:has(> .hero__content)) {
|
|
360
|
+
grid-template-columns: minmax(0, 1fr);
|
|
361
|
+
grid-template-areas:
|
|
362
|
+
"headline"
|
|
363
|
+
"subheadline"
|
|
364
|
+
"media"
|
|
365
|
+
"actions";
|
|
366
|
+
}
|
|
233
367
|
}
|
|
234
368
|
|
|
235
369
|
|
|
@@ -253,41 +387,22 @@
|
|
|
253
387
|
|
|
254
388
|
/* ========================================
|
|
255
389
|
HERO BUTTON CONTRAST
|
|
256
|
-
|
|
390
|
+
Dark and accent heroes re-point the roles, so their buttons follow on
|
|
391
|
+
their own. The gradient hero is not a role band: its outline buttons
|
|
392
|
+
go white. `.button` too, so a link matches a <button> (#72).
|
|
257
393
|
======================================== */
|
|
258
394
|
|
|
259
|
-
|
|
260
|
-
.hero[data-theme="dark"] button[data-variant="outline"],
|
|
261
|
-
.hero[data-theme="gradient"] button[data-variant="outline"] {
|
|
395
|
+
.hero[data-theme="gradient"] :is(button, .button)[data-variant="outline"] {
|
|
262
396
|
--_btn-color: var(--white);
|
|
263
397
|
--_btn-border: var(--white);
|
|
264
|
-
--_btn-bg-hover:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
/* Accent theme: invert button colors for contrast */
|
|
268
|
-
.hero[data-theme="accent"] button:not([data-variant]) {
|
|
269
|
-
--_btn-color: var(--accent);
|
|
270
|
-
--_btn-bg: var(--white);
|
|
271
|
-
--_btn-bg-hover: var(--gray-95);
|
|
398
|
+
--_btn-bg-hover: var(--white);
|
|
399
|
+
--_btn-color-hover: var(--black);
|
|
272
400
|
}
|
|
273
401
|
|
|
274
|
-
.hero[data-theme="
|
|
275
|
-
--_btn-color: var(--
|
|
276
|
-
--_btn-border: var(--white);
|
|
277
|
-
--_btn-bg-hover: oklch(100% 0 0 / 0.15);
|
|
402
|
+
.hero:is([data-theme="gradient"], [data-overlay]) :is(button, .button)[data-variant="ghost"] {
|
|
403
|
+
--_btn-color: var(--hero-text-color);
|
|
278
404
|
}
|
|
279
405
|
|
|
280
406
|
|
|
281
|
-
/*
|
|
282
|
-
REDUCED MOTION
|
|
283
|
-
Respect user preferences
|
|
284
|
-
======================================== */
|
|
285
|
-
|
|
286
|
-
@media (prefers-reduced-motion: reduce) {
|
|
287
|
-
.hero,
|
|
288
|
-
.hero * {
|
|
289
|
-
animation: none !important;
|
|
290
|
-
transition: none !important;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
407
|
+
/* Reduced motion: the reset shortens every transition and animation. */
|
|
293
408
|
}
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
/* Good (Success family) - ≥80% */
|
|
32
32
|
[data-status="good"] {
|
|
33
33
|
--status-color: var(--color-success);
|
|
34
|
+
--status-fill: var(--color-success-fill);
|
|
35
|
+
--status-on: var(--color-success-on);
|
|
34
36
|
--status-bg: var(--color-success-subtle);
|
|
35
37
|
--status-text: var(--color-success);
|
|
36
38
|
--status-border: var(--color-success);
|
|
@@ -39,15 +41,18 @@
|
|
|
39
41
|
/* Moderate (Warning family) - 60-79% */
|
|
40
42
|
[data-status="moderate"] {
|
|
41
43
|
--status-color: var(--color-warning);
|
|
44
|
+
--status-fill: var(--color-warning-fill);
|
|
45
|
+
--status-on: var(--color-warning-on);
|
|
42
46
|
--status-bg: var(--color-warning-subtle);
|
|
43
47
|
--status-text: var(--color-warning);
|
|
44
|
-
--status-text: oklch(from var(--color-warning) calc(l - 0.2) c h);
|
|
45
48
|
--status-border: var(--color-warning);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
/* Severe (Error family) - <60% */
|
|
49
52
|
[data-status="severe"] {
|
|
50
53
|
--status-color: var(--color-error);
|
|
54
|
+
--status-fill: var(--color-error-fill);
|
|
55
|
+
--status-on: var(--color-error-on);
|
|
51
56
|
--status-bg: var(--color-error-subtle);
|
|
52
57
|
--status-text: var(--color-error);
|
|
53
58
|
--status-border: var(--color-error);
|
|
@@ -105,23 +110,12 @@
|
|
|
105
110
|
background-color: var(--_progress-track);
|
|
106
111
|
border-radius: var(--_progress-radius);
|
|
107
112
|
overflow: hidden;
|
|
108
|
-
/* Subtle inner shadow to define the track boundary
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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);
|
|
113
|
+
/* Subtle inner shadow to define the track boundary; a status-tinted
|
|
114
|
+
track, both a little stronger in dark mode */
|
|
115
|
+
--_progress-track: light-dark(
|
|
116
|
+
color-mix(in oklab, var(--status-color, var(--accent)) 15%, var(--color-surface-raised)),
|
|
117
|
+
color-mix(in oklab, var(--status-color, var(--accent)) 20%, var(--color-surface-raised))
|
|
118
|
+
);
|
|
125
119
|
}
|
|
126
120
|
|
|
127
121
|
/* Progress fill bar */
|
|
@@ -207,20 +201,20 @@
|
|
|
207
201
|
line-height: var(--leading-none);
|
|
208
202
|
color: var(--status-text, var(--color-text));
|
|
209
203
|
background-color: var(--status-bg, var(--color-surface));
|
|
210
|
-
border-radius: var(--radius-
|
|
204
|
+
border-radius: var(--radius-pill);
|
|
211
205
|
white-space: nowrap;
|
|
212
206
|
}
|
|
213
207
|
|
|
214
208
|
/* Filled variant - stronger emphasis */
|
|
215
209
|
.badge[data-variant="filled"] {
|
|
216
|
-
color: var(--
|
|
217
|
-
background-color: var(--status-
|
|
210
|
+
color: var(--status-on, var(--on-accent));
|
|
211
|
+
background-color: var(--status-fill, var(--accent));
|
|
218
212
|
}
|
|
219
213
|
|
|
220
214
|
/* Outline variant */
|
|
221
215
|
.badge[data-variant="outline"] {
|
|
222
216
|
background-color: transparent;
|
|
223
|
-
border:
|
|
217
|
+
border: var(--border-width) solid var(--status-border, var(--color-border));
|
|
224
218
|
}
|
|
225
219
|
|
|
226
220
|
/* Badge sizes */
|
|
@@ -254,8 +248,8 @@
|
|
|
254
248
|
.status-card {
|
|
255
249
|
padding: var(--space-4);
|
|
256
250
|
background-color: var(--status-bg, var(--color-surface));
|
|
257
|
-
border:
|
|
258
|
-
border-radius: var(--radius-
|
|
251
|
+
border: var(--border-width) solid var(--status-border, var(--color-border-subtle));
|
|
252
|
+
border-radius: var(--radius-card);
|
|
259
253
|
border-inline-start-width: 4px;
|
|
260
254
|
}
|
|
261
255
|
|
|
@@ -290,7 +284,7 @@
|
|
|
290
284
|
gap: var(--space-4);
|
|
291
285
|
padding: var(--space-4);
|
|
292
286
|
background-color: var(--color-surface);
|
|
293
|
-
border-radius: var(--radius-
|
|
287
|
+
border-radius: var(--radius-card);
|
|
294
288
|
font-size: var(--font-size-sm);
|
|
295
289
|
}
|
|
296
290
|
|
|
@@ -354,102 +348,14 @@
|
|
|
354
348
|
|
|
355
349
|
|
|
356
350
|
/* ========================================
|
|
357
|
-
MOTION
|
|
358
|
-
Each theme has its own progress animation style
|
|
351
|
+
MOTION
|
|
359
352
|
======================================== */
|
|
360
353
|
|
|
361
|
-
/*
|
|
362
|
-
|
|
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 {
|
|
354
|
+
/* Balanced, organic fill */
|
|
355
|
+
.progress__fill {
|
|
378
356
|
transition: inline-size var(--duration-slow) var(--ease-fluid);
|
|
379
357
|
}
|
|
380
358
|
|
|
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
359
|
|
|
387
|
-
/*
|
|
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
|
-
}
|
|
360
|
+
/* Reduced motion: the durations are 0 and the reset shortens the rest. */
|
|
455
361
|
}
|