@fluid-theme/core 0.4.3 → 2.1.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.
Files changed (107) hide show
  1. package/AGENTS.md +6 -29
  2. package/CHANGELOG.md +564 -2
  3. package/CLAUDE.md +201 -14
  4. package/GEMINI.md +6 -45
  5. package/README.md +17 -8
  6. package/assets/js/compare.js +77 -0
  7. package/assets/js/fit-text.js +103 -0
  8. package/assets/js/lightbox.js +153 -0
  9. package/assets/js/scroll-reveal.js +72 -0
  10. package/assets/js/toc-build.mjs +86 -0
  11. package/assets/js/toc.js +384 -0
  12. package/dist/fluid-1x.min.css +4 -0
  13. package/dist/fluid-fonts-archivo.min.css +2 -0
  14. package/dist/fluid-fonts-bodoni.min.css +2 -0
  15. package/dist/fluid-fonts.min.css +2 -0
  16. package/dist/fluid-legacy-1x.css +3 -0
  17. package/dist/fluid-nav.min.css +3 -0
  18. package/dist/fluid-toc.min.css +3 -0
  19. package/dist/fluid-zero.min.css +3 -0
  20. package/dist/fluid.min.css +3 -2
  21. package/dist/fonts/OFL-archivo.txt +93 -0
  22. package/dist/fonts/OFL-bodoni-moda.txt +93 -0
  23. package/dist/fonts/OFL-hanken-grotesk.txt +93 -0
  24. package/dist/fonts/OFL-newsreader.txt +93 -0
  25. package/dist/fonts/OFL-schibsted-grotesk.txt +93 -0
  26. package/dist/fonts/archivo-latin-wdth-italic.woff2 +0 -0
  27. package/dist/fonts/archivo-latin-wdth-normal.woff2 +0 -0
  28. package/dist/fonts/bodoni-moda-latin-opsz-italic.woff2 +0 -0
  29. package/dist/fonts/bodoni-moda-latin-opsz-normal.woff2 +0 -0
  30. package/dist/fonts/hanken-grotesk-latin-wght-italic.woff2 +0 -0
  31. package/dist/fonts/hanken-grotesk-latin-wght-normal.woff2 +0 -0
  32. package/dist/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
  33. package/dist/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  34. package/dist/fonts/schibsted-grotesk-latin-wght-italic.woff2 +0 -0
  35. package/dist/fonts/schibsted-grotesk-latin-wght-normal.woff2 +0 -0
  36. package/fluid-agent-kit/README.md +1 -0
  37. package/fluid-agent-kit/skills/accessibility.md +3 -1
  38. package/fluid-agent-kit/skills/css-conventions.md +22 -5
  39. package/fluid-agent-kit/skills/image-optimization.md +2 -0
  40. package/fluid-agent-kit/skills/preset-composition.md +108 -0
  41. package/package.json +36 -11
  42. package/src/animations/scroll-reveal.css +75 -193
  43. package/src/components/accordion.css +6 -47
  44. package/src/components/alert.css +47 -88
  45. package/src/components/avatar.css +4 -20
  46. package/src/components/badge.css +25 -48
  47. package/src/components/button.css +69 -175
  48. package/src/components/card.css +146 -481
  49. package/src/components/chip.css +46 -12
  50. package/src/components/compare.css +234 -0
  51. package/src/components/dropdown.css +10 -67
  52. package/src/components/empty-state.css +4 -0
  53. package/src/components/form.css +113 -81
  54. package/src/components/gallery.css +252 -0
  55. package/src/components/hero.css +203 -88
  56. package/src/components/indicator.css +23 -117
  57. package/src/components/loading.css +7 -29
  58. package/src/components/modal.css +12 -65
  59. package/src/components/nav-modes.css +261 -0
  60. package/src/components/nav.css +324 -392
  61. package/src/components/search-input.css +4 -2
  62. package/src/components/step-indicator.css +26 -27
  63. package/src/components/table.css +64 -171
  64. package/src/components/toc.css +254 -0
  65. package/src/components/tooltip.css +5 -46
  66. package/src/core/base.css +393 -547
  67. package/src/core/reset.css +17 -5
  68. package/src/core/tokens.css +426 -2276
  69. package/src/fluid.css +258 -17
  70. package/src/fonts/OFL-newsreader.txt +93 -0
  71. package/src/fonts/OFL-schibsted-grotesk.txt +93 -0
  72. package/src/fonts/fluid-fonts.css +55 -0
  73. package/src/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
  74. package/src/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  75. package/src/fonts/schibsted-grotesk-latin-wght-italic.woff2 +0 -0
  76. package/src/fonts/schibsted-grotesk-latin-wght-normal.woff2 +0 -0
  77. package/src/layouts/positioning.css +6 -3
  78. package/src/legacy-1x.css +61 -0
  79. package/src/sections/cta.css +33 -101
  80. package/src/sections/features.css +42 -219
  81. package/src/sections/footer.css +53 -134
  82. package/src/sections/stats.css +40 -153
  83. package/src/sections/team.css +24 -113
  84. package/src/sections/testimonials.css +34 -148
  85. package/src/zero/base.css +166 -0
  86. package/src/zero/tokens.css +64 -0
  87. package/src/zero.css +20 -0
  88. package/starters/workflows/README.md +40 -0
  89. package/starters/workflows/dependabot.yml +21 -0
  90. package/starters/workflows/update-fluid-theme.yml +296 -0
  91. package/themes/fonts/OFL-archivo.txt +93 -0
  92. package/themes/fonts/OFL-bodoni-moda.txt +93 -0
  93. package/themes/fonts/OFL-hanken-grotesk.txt +93 -0
  94. package/themes/fonts/archivo-latin-wdth-italic.woff2 +0 -0
  95. package/themes/fonts/archivo-latin-wdth-normal.woff2 +0 -0
  96. package/themes/fonts/archivo.css +20 -0
  97. package/themes/fonts/bodoni-moda-latin-opsz-italic.woff2 +0 -0
  98. package/themes/fonts/bodoni-moda-latin-opsz-normal.woff2 +0 -0
  99. package/themes/fonts/bodoni.css +35 -0
  100. package/themes/fonts/hanken-grotesk-latin-wght-italic.woff2 +0 -0
  101. package/themes/fonts/hanken-grotesk-latin-wght-normal.woff2 +0 -0
  102. package/themes/gallery.css +105 -0
  103. package/themes/swiss.css +53 -0
  104. package/src/components/scroll-progress.css +0 -104
  105. package/src/components/settings-panel.css +0 -603
  106. package/src/components/themed-images.css +0 -229
  107. package/src/components/version-badge.css +0 -70
@@ -1,419 +1,241 @@
1
1
  /**
2
- * Fluid Framework - Card Component
2
+ * Fluid Theme - Card Component
3
3
  * @layer components
4
4
  *
5
- * Cards are containers that group related content.
6
- * They provide subtle elevation and visual boundaries.
7
- * Uses container queries for truly responsive behavior.
5
+ * Open by default: no box, padding, shadow or lift. Media in true colour at
6
+ * its natural ratio (or data-aspect), then the body under an ink hairline:
7
+ * eyebrow, serif title, dek, meta. In a .grid the cards share a hang line:
8
+ * image bottoms and hairlines line up across a row whatever each image's
9
+ * ratio, and the bodies hang from it. Hover underlines the title; nothing
10
+ * moves. A card with no media carries the rule on its own top edge, so a
11
+ * child theme that writes .card { border: … } replaces it.
8
12
  *
9
13
  * Usage:
10
- * <article class="card">...</article>
11
- * <div class="card" data-variant="elevated">...</div>
12
- * <article class="card" data-variant="outlined">...</article>
13
- * <a href="#" class="card" data-interactive>Clickable card</a>
14
- *
15
- * Structure (optional):
16
14
  * <article class="card">
17
- * <img class="card__media" src="..." alt="...">
18
- * <div class="card__content">
15
+ * <img class="card__media" src="..." alt="..." width="800" height="600">
16
+ * <div class="card__content"> (.card__body works too)
17
+ * <p class="eyebrow">Essay</p>
19
18
  * <h3 class="card__title">Title</h3>
20
- * <p class="card__description">Description</p>
19
+ * <p class="card__description">Dek</p>
20
+ * <p class="card__meta">12 May 2026</p>
21
21
  * </div>
22
22
  * <footer class="card__footer">Actions</footer>
23
23
  * </article>
24
+ *
25
+ * Boxed cards: data-variant="outlined" (hairline box), "filled" (surface),
26
+ * "elevated" (raised surface; a shadow only when --shadow-style is not 0).
27
+ * Opt-ins: data-treatment="duotone" (hue from --accent; wrap the image in
28
+ * .card__media-wrap), data-layout="horizontal", data-type="profile|stat|
29
+ * feature|testimonial|pricing".
24
30
  */
25
31
 
26
32
  @layer components {
27
- /* Base card */
28
33
  .card {
29
- /* Container for queries - only when explicitly enabled */
30
34
  container-name: card;
31
-
32
- /* Layout */
35
+ position: relative;
33
36
  display: flex;
34
37
  flex-direction: column;
35
-
36
- /* Prevent collapse in grid/flex layouts */
37
38
  min-inline-size: 0;
38
-
39
- /* Visual */
40
- background-color: var(--_card-bg, var(--color-surface-raised));
41
- border: var(--border-width) solid var(--_card-border, var(--color-border-subtle));
42
- border-radius: var(--radius-card, var(--radius-xl));
43
-
44
- /* Overflow: clip media but allow focus rings and shadows to paint */
39
+ color: inherit;
40
+ background-color: var(--_card-bg, transparent);
41
+ border: var(--border-width) solid var(--_card-border, transparent);
42
+ /* Only a boxed card is rounded: an open card paints its top edge only,
43
+ and a radius bent that hairline into an arc ("a smile") over it. */
44
+ border-radius: var(--_card-radius, 0);
45
+ box-shadow: var(--_card-shadow, none);
45
46
  overflow: clip;
46
47
  overflow-clip-margin: 4px;
47
-
48
- /* Transitions */
49
- transition:
50
- box-shadow var(--duration-normal) var(--ease-fluid),
51
- border-color var(--duration-normal) var(--ease-fluid),
52
- transform var(--duration-normal) var(--ease-settle);
53
48
  }
54
49
 
55
- /* Enable container queries only when needed */
56
- .card[data-container] {
57
- container-type: inline-size;
58
- }
50
+ .card[data-container] { container-type: inline-size; }
59
51
 
60
52
 
61
53
  /* ========================================
62
- VARIANTS
63
- Different visual treatments
54
+ BOXED VARIANTS
64
55
  ======================================== */
65
56
 
66
- /* Elevated - floating appearance */
67
- .card[data-variant="elevated"] {
68
- --_card-border: transparent;
69
- box-shadow: var(--shadow-md);
70
- }
71
-
72
- .card[data-variant="elevated"]:hover {
73
- box-shadow: var(--shadow-lg);
74
- }
75
-
76
- /* Dark mode: use accent glow since shadows are barely visible on dark */
77
- @media (prefers-color-scheme: dark) {
78
- .card[data-variant="elevated"] {
79
- box-shadow:
80
- 0 0 0 1px oklch(from var(--accent) l c h / 0.15),
81
- 0 4px 20px oklch(from var(--accent) l c h / 0.1);
82
- }
83
-
84
- .card[data-variant="elevated"]:hover {
85
- box-shadow:
86
- 0 0 0 1px oklch(from var(--accent) l c h / 0.25),
87
- 0 8px 30px oklch(from var(--accent) l c h / 0.15);
88
- }
89
- }
90
-
91
- /* Also support explicit dark theme */
92
- [data-theme="dark"] .card[data-variant="elevated"] {
93
- box-shadow:
94
- 0 0 0 1px oklch(from var(--accent) l c h / 0.15),
95
- 0 4px 20px oklch(from var(--accent) l c h / 0.1);
96
- }
97
-
98
- [data-theme="dark"] .card[data-variant="elevated"]:hover {
99
- box-shadow:
100
- 0 0 0 1px oklch(from var(--accent) l c h / 0.25),
101
- 0 8px 30px oklch(from var(--accent) l c h / 0.15);
102
- }
103
-
104
- /* Outlined - border emphasis */
105
- .card[data-variant="outlined"] {
106
- --_card-bg: transparent;
57
+ .card:is([data-variant="outlined"], [data-variant="filled"], [data-variant="elevated"]) {
58
+ --_card-pad: var(--space-m);
59
+ --_card-rule: transparent;
107
60
  --_card-border: var(--color-border);
61
+ --_card-radius: var(--radius-card);
108
62
  }
109
63
 
110
- /* Ghost - minimal, content-focused */
111
- .card[data-variant="ghost"] {
112
- --_card-bg: transparent;
113
- --_card-border: transparent;
114
- }
64
+ .card[data-variant="filled"] { --_card-bg: var(--color-surface); --_card-border: transparent; }
115
65
 
116
- /* Filled - solid background */
117
- .card[data-variant="filled"] {
118
- --_card-bg: var(--color-surface);
119
- --_card-border: transparent;
66
+ .card[data-variant="elevated"] {
67
+ --_card-bg: var(--color-surface-raised);
68
+ --_card-shadow: var(--shadow-md);
120
69
  }
121
70
 
122
71
 
123
72
  /* ========================================
124
73
  INTERACTIVE CARDS
125
- Clickable/hoverable cards
126
74
  ======================================== */
127
75
 
128
76
  .card[data-interactive],
129
77
  a.card {
130
78
  cursor: pointer;
131
79
  text-decoration: none;
132
- color: inherit;
133
- }
134
-
135
- .card[data-interactive]:hover,
136
- a.card:hover {
137
- --_card-border: var(--accent);
138
- box-shadow: var(--shadow-sm);
139
80
  }
140
81
 
141
- .card[data-interactive]:active,
142
- a.card:active {
143
- transform: scale(0.99);
82
+ :is(.card[data-interactive], a.card):hover .card__title {
83
+ text-decoration-line: underline;
84
+ text-decoration-color: var(--accent-ink);
85
+ text-decoration-thickness: 0.06em;
86
+ text-underline-offset: 0.22em;
144
87
  }
145
88
 
146
- .card[data-interactive]:focus-visible,
147
- a.card:focus-visible {
148
- outline: 2px solid transparent;
149
- box-shadow:
150
- var(--shadow-glow),
151
- 0 0 0 2px var(--color-background),
152
- 0 0 0 4px var(--accent);
89
+ :is(.card[data-interactive], a.card):focus-visible {
90
+ outline: var(--focus-ring-width) solid var(--color-focus);
91
+ outline-offset: 2px;
153
92
  }
154
93
 
155
94
 
156
95
  /* ========================================
157
- CARD STRUCTURE
158
- Optional semantic sections
96
+ STRUCTURE
159
97
  ======================================== */
160
98
 
161
- /* Media - images, video, etc. */
162
99
  .card__media {
163
- /* Full width, maintain aspect */
164
100
  inline-size: 100%;
165
101
  block-size: auto;
166
- object-fit: cover;
167
-
168
- /* Remove from flow for overlay positioning */
169
102
  flex-shrink: 0;
170
-
171
- /* Auto-apply theme-aware duotone tint */
172
- filter: grayscale(100%) contrast(1.1);
173
- transition: filter var(--duration-normal) var(--ease-fluid);
103
+ border-radius: var(--radius-card);
174
104
  }
175
105
 
176
- /* Duotone wrapper for blend mode overlay */
177
- .card__media-wrap {
178
- position: relative;
179
- overflow: hidden;
106
+ :is(.card__media, .card__media-wrap) + :is(.card__content, .card__body) {
107
+ margin-block-start: var(--_card-pad, var(--space-s));
108
+ border-block-start: var(--border-width) solid var(--_card-rule, var(--color-border-strong));
180
109
  }
181
110
 
182
- .card__media-wrap::after {
183
- content: "";
184
- position: absolute;
185
- inset: 0;
186
- background: linear-gradient(
187
- 135deg,
188
- var(--duotone-dark, oklch(0.3 0.1 220)) 0%,
189
- var(--duotone-light, oklch(0.7 0.1 200)) 100%
190
- );
191
- mix-blend-mode: multiply;
192
- opacity: 0.85;
193
- pointer-events: none;
194
- transition: opacity var(--duration-normal) var(--ease-fluid);
111
+ /* An open card with no media: the rule is the card's own top edge. */
112
+ .card:not([data-variant]):not(:has(> :is(.card__media, .card__media-wrap))) {
113
+ border-block-start-color: var(--_card-rule, var(--color-border-strong));
195
114
  }
196
115
 
197
- /* Hover: reveal more of original image */
198
- .card:hover .card__media {
199
- filter: grayscale(60%) contrast(1.05);
200
- }
201
-
202
- .card:hover .card__media-wrap::after {
203
- opacity: 0.6;
204
- }
205
-
206
- /* Opt-out of duotone effect */
207
- .card__media[data-no-duotone],
208
- .card[data-no-duotone] .card__media {
209
- filter: none;
210
- }
211
-
212
- /* Focal point control for card media */
213
- .card__media[data-focus="top"] { object-position: center top; }
214
- .card__media[data-focus="bottom"] { object-position: center bottom; }
215
- .card__media[data-focus="left"] { object-position: left center; }
216
- .card__media[data-focus="right"] { object-position: right center; }
217
- .card__media[data-focus="center"] { object-position: center center; }
218
-
219
- /* Custom focal point via CSS custom properties */
220
- .card__media[style*="--focus"] {
221
- object-position: var(--focus-x, 50%) var(--focus-y, 50%);
222
- }
223
-
224
- /* Aspect ratio variants for media */
225
- .card__media[data-aspect="square"] {
226
- aspect-ratio: 1;
227
- }
228
-
229
- .card__media[data-aspect="video"] {
230
- aspect-ratio: 16 / 9;
231
- }
232
-
233
- .card__media[data-aspect="portrait"] {
234
- aspect-ratio: 3 / 4;
235
- }
236
-
237
- .card__media[data-aspect="wide"] {
238
- aspect-ratio: 21 / 9;
239
- }
240
-
241
- /* Content area */
242
- .card__content {
116
+ :is(.card__content, .card__body) {
243
117
  flex: 1;
244
- padding: var(--space-5);
245
118
  display: flex;
246
119
  flex-direction: column;
247
- gap: var(--space-3);
120
+ gap: var(--space-xs);
121
+ padding: var(--_card-pad, 0);
122
+ padding-block-start: var(--_card-pad, var(--space-s));
123
+ }
124
+
125
+ /* The hang line: in a .grid, a card with media spans two rows of the
126
+ grid (three with a footer) as a subgrid, the media bottom-aligned in
127
+ the first. Needs subgrid; without it cards keep the flex column. */
128
+ @supports (grid-template-rows: subgrid) {
129
+ .grid > .card:has(> .card__media, > .card__media-wrap):not([data-layout]) {
130
+ display: grid;
131
+ grid-row: span 2;
132
+ grid-template-rows: subgrid;
133
+ row-gap: 0;
134
+ }
135
+
136
+ .grid > .card:has(> .card__media, > .card__media-wrap):has(> .card__footer):not([data-layout]) { grid-row: span 3; }
137
+
138
+ .grid > .card > :is(.card__media, .card__media-wrap) { align-self: end; }
248
139
  }
249
140
 
250
- /* Title */
141
+ .card :is(.eyebrow, p) { margin: 0; }
142
+
251
143
  .card__title {
252
- font-size: var(--font-size-lg);
253
- font-weight: var(--weight-semibold);
254
- line-height: var(--leading-tight);
144
+ font-family: var(--font-heading);
145
+ font-size: var(--step-1);
146
+ font-weight: calc(var(--weight-medium) + var(--_dark-weight));
147
+ line-height: var(--leading-heading);
148
+ letter-spacing: var(--tracking-normal);
149
+ text-wrap: balance;
255
150
  margin: 0;
256
151
  }
257
152
 
258
- /* Description */
259
- .card__description {
260
- color: var(--color-text-muted);
261
- margin: 0;
262
- }
153
+ /* A title link is not a link in running text: no underline at rest,
154
+ the accent underline on hover and focus (spec §9). */
155
+ .card__title a { text-decoration-color: transparent; }
156
+ .card__title a:is(:hover, :focus-visible) { text-decoration-color: var(--accent-ink); }
157
+
263
158
 
264
- /* Metadata - tags, dates, etc. */
265
159
  .card__meta {
266
160
  display: flex;
267
161
  flex-wrap: wrap;
268
162
  gap: var(--space-2);
269
- font-size: var(--font-size-sm);
270
- color: var(--color-text-subtle);
163
+ font-size: var(--step--1);
164
+ color: var(--color-text-muted);
165
+ font-variant-numeric: lining-nums proportional-nums; /* tabular widens the comma */
271
166
  }
272
167
 
273
- /* Footer - actions, links */
274
168
  .card__footer {
275
- padding: var(--space-4) var(--space-5);
276
- border-block-start: 1px solid var(--color-border-subtle);
277
169
  display: flex;
278
170
  gap: var(--space-3);
279
171
  align-items: center;
172
+ padding-block: var(--space-s);
173
+ padding-inline: var(--_card-pad, 0);
174
+ border-block-start: var(--border-width) solid var(--color-border);
280
175
  }
281
176
 
282
- /* Push footer actions to end */
283
- .card__footer[data-align="end"] {
284
- justify-content: flex-end;
285
- }
286
-
287
- .card__footer[data-align="between"] {
288
- justify-content: space-between;
289
- }
290
-
291
-
292
- /* ========================================
293
- SIZE VARIANTS
294
- Compact for lists, spacious for features
295
- ======================================== */
296
-
297
- .card[data-size="sm"] .card__content {
298
- padding: var(--space-4);
299
- gap: var(--space-2);
300
- }
301
-
302
- .card[data-size="sm"] .card__title {
303
- font-size: var(--font-size-base);
304
- }
305
-
306
- .card[data-size="sm"] .card__footer {
307
- padding: var(--space-3) var(--space-4);
308
- }
177
+ .card__footer[data-align="end"] { justify-content: flex-end; }
178
+ .card__footer[data-align="between"] { justify-content: space-between; }
309
179
 
310
- .card[data-size="lg"] .card__content {
311
- padding: var(--space-6);
312
- gap: var(--space-4);
313
- }
180
+ .card[data-size="sm"] .card__title { font-size: var(--step-0); }
181
+ .card[data-size="lg"] .card__title { font-size: var(--step-2); }
314
182
 
315
- .card[data-size="lg"] .card__title {
316
- font-size: var(--font-size-xl);
317
- }
318
-
319
- .card[data-size="lg"] .card__footer {
320
- padding: var(--space-5) var(--space-6);
183
+ @container card (min-width: 30rem) {
184
+ .card[data-layout="horizontal"] { flex-direction: row; gap: var(--space-m); }
185
+ .card[data-layout="horizontal"] .card__media { inline-size: 40%; max-inline-size: 16rem; }
186
+ .card[data-layout="horizontal"] .card__media + :is(.card__content, .card__body) { margin-block-start: 0; }
321
187
  }
322
188
 
323
189
 
324
190
  /* ========================================
325
- CONTAINER QUERY RESPONSIVE
326
- Cards adapt to their container, not viewport
191
+ DUOTONE (opt-in): the hue comes from --accent
327
192
  ======================================== */
328
193
 
329
- /* Compact card in narrow containers */
330
- @container card (max-width: 20rem) {
331
- .card__content {
332
- padding: var(--space-4);
333
- }
334
-
335
- .card__title {
336
- font-size: var(--font-size-base);
337
- }
338
-
339
- .card__footer {
340
- padding: var(--space-3) var(--space-4);
341
- flex-direction: column;
342
- align-items: stretch;
343
- }
194
+ .card__media-wrap {
195
+ position: relative;
196
+ overflow: hidden;
344
197
  }
345
198
 
346
- /* Horizontal layout in wider containers */
347
- @container card (min-width: 30rem) {
348
- .card[data-layout="horizontal"] {
349
- flex-direction: row;
350
- }
351
-
352
- .card[data-layout="horizontal"] .card__media {
353
- inline-size: 40%;
354
- max-inline-size: 16rem;
355
- block-size: auto;
356
- }
199
+ .card[data-treatment="duotone"] .card__media { filter: grayscale(100%) contrast(1.1); }
357
200
 
358
- .card[data-layout="horizontal"] .card__content {
359
- justify-content: center;
360
- }
201
+ .card[data-treatment="duotone"] .card__media-wrap::after {
202
+ content: "";
203
+ position: absolute;
204
+ inset: 0;
205
+ background: linear-gradient(
206
+ 135deg,
207
+ var(--duotone-dark, oklch(from var(--accent) 0.3 c h)) 0%,
208
+ var(--duotone-light, oklch(from var(--accent) 0.8 calc(c * 0.5) h)) 100%
209
+ );
210
+ mix-blend-mode: multiply;
211
+ opacity: 0.85;
212
+ pointer-events: none;
361
213
  }
362
214
 
363
215
 
364
216
  /* ========================================
365
217
  SPECIAL CARD TYPES
366
- Common card patterns
367
218
  ======================================== */
368
219
 
369
- /* Profile card */
370
- .card[data-type="profile"] {
371
- text-align: center;
372
- }
373
-
374
220
  .card[data-type="profile"] .card__media {
375
221
  inline-size: 6rem;
376
222
  block-size: 6rem;
377
223
  border-radius: var(--radius-full);
378
- margin: var(--space-5) auto 0;
379
224
  }
380
225
 
381
- /* Stat card */
382
- .card[data-type="stat"] .card__content {
383
- text-align: center;
384
- gap: var(--space-2);
385
- }
386
-
387
- .card[data-type="stat"] .card__value {
388
- font-size: var(--font-size-3xl);
389
- font-weight: var(--weight-bold);
390
- line-height: var(--leading-none);
391
- color: var(--accent);
392
- }
393
-
394
- .card[data-type="stat"] .card__title {
395
- font-size: var(--font-size-sm);
396
- font-weight: var(--weight-normal);
397
- color: var(--color-text-muted);
398
- }
399
-
400
- /* Feature card - emphasized */
401
- .card[data-type="feature"] {
402
- text-align: center;
403
- padding: var(--space-6);
226
+ :is(.card[data-type="stat"] .card__value, .card[data-type="pricing"] .card__price) {
227
+ font-family: var(--font-heading);
228
+ font-size: var(--step-4);
229
+ font-weight: calc(var(--weight-display) + var(--_dark-weight));
230
+ line-height: 1;
231
+ font-variant-numeric: lining-nums tabular-nums;
404
232
  }
405
233
 
406
234
  .card[data-type="feature"] .card__icon {
407
- inline-size: 3rem;
408
- block-size: 3rem;
409
- margin-inline: auto;
410
- margin-block-end: var(--space-4);
411
- color: var(--accent);
412
- }
413
-
414
- /* Testimonial card - quote with attribution */
415
- .card[data-type="testimonial"] .card__content {
416
- gap: var(--space-4);
235
+ inline-size: 2.5rem;
236
+ block-size: 2.5rem;
237
+ margin-block-end: var(--space-s);
238
+ color: var(--accent-ink);
417
239
  }
418
240
 
419
241
  .card[data-type="testimonial"] .card__rating {
@@ -423,77 +245,45 @@
423
245
  }
424
246
 
425
247
  .card[data-type="testimonial"] .card__quote {
248
+ font-family: var(--font-heading);
249
+ font-size: var(--step-1);
426
250
  font-style: italic;
427
- line-height: var(--leading-relaxed);
428
- color: var(--color-text);
429
- }
430
-
431
- .card[data-type="testimonial"] .card__quote::before {
432
- content: "\201C";
433
- font-size: var(--font-size-3xl);
434
- font-style: normal;
435
- line-height: 0;
436
- vertical-align: -0.35em;
437
- color: var(--accent);
438
- margin-inline-end: var(--space-1);
251
+ line-height: 1.4;
439
252
  }
440
253
 
441
254
  .card[data-type="testimonial"] .card__attribution {
442
255
  display: flex;
443
256
  align-items: center;
444
257
  gap: var(--space-3);
445
- font-size: var(--font-size-sm);
258
+ font-size: var(--step--1);
446
259
  }
447
260
 
448
- .card[data-type="testimonial"] .card__author {
449
- font-weight: var(--weight-semibold);
450
- font-style: normal;
451
- color: var(--color-text);
452
- }
453
-
454
- .card[data-type="testimonial"] .card__role {
455
- color: var(--color-text-muted);
456
- }
261
+ .card[data-type="testimonial"] .card__author { font-weight: 600; font-style: normal; }
262
+ .card[data-type="testimonial"] .card__role { color: var(--color-text-muted); }
457
263
 
458
- /* Pricing card - plan comparison */
459
- .card[data-type="pricing"] {
460
- text-align: center;
461
- overflow: visible;
462
- position: relative;
463
- }
464
-
465
- .card[data-type="pricing"] .card__price {
466
- font-size: var(--font-size-4xl);
467
- font-weight: var(--weight-bold);
468
- line-height: var(--leading-none);
469
- margin-block-end: var(--space-1);
470
- }
264
+ .card[data-type="pricing"] { overflow: visible; }
471
265
 
472
266
  .card[data-type="pricing"] .card__price-period {
473
- font-size: var(--font-size-sm);
267
+ font-family: var(--font-body);
268
+ font-size: var(--step--1);
474
269
  color: var(--color-text-muted);
475
- font-weight: var(--weight-normal);
476
270
  }
477
271
 
478
- /* Featured/popular pricing card */
479
- .card[data-type="pricing"][data-featured] {
480
- border: 2px solid var(--accent);
481
- transform: scale(1.03);
482
- }
272
+ .card[data-type="pricing"][data-featured] { --_card-border: var(--color-border-strong); border-width: var(--border-width-thick); }
483
273
 
484
274
  .card[data-type="pricing"][data-featured] > .badge {
485
275
  position: absolute;
486
276
  inset-block-start: 0;
487
- inset-inline-start: 50%;
488
- transform: translate(-50%, -50%);
277
+ inset-inline: 0;
278
+ margin-inline: auto;
279
+ inline-size: fit-content;
280
+ translate: 0 -50%;
489
281
  }
490
282
 
491
- /* Pricing feature list */
492
283
  .card[data-type="pricing"] .card__features {
493
284
  list-style: none;
494
285
  padding: 0;
495
286
  margin: 0;
496
- text-align: start;
497
287
  }
498
288
 
499
289
  .card[data-type="pricing"] .card__features li {
@@ -501,11 +291,8 @@
501
291
  align-items: center;
502
292
  gap: var(--space-2);
503
293
  padding-block: var(--space-2);
504
- border-block-end: var(--border-width) solid var(--color-border-subtle);
505
- }
506
-
507
- .card[data-type="pricing"] .card__features li:last-child {
508
- border-block-end: none;
294
+ margin: 0;
295
+ border-block-end: var(--border-width) solid var(--color-border);
509
296
  }
510
297
 
511
298
  .card[data-type="pricing"] .card__features .check {
@@ -516,135 +303,13 @@
516
303
 
517
304
  /* ========================================
518
305
  CARD GROUPS
519
- Multiple cards together
520
306
  ======================================== */
521
307
 
522
308
  .card-group {
523
309
  display: grid;
524
- gap: var(--space-5);
525
- grid-template-columns: repeat(
526
- auto-fit,
527
- minmax(min(100%, var(--card-min-width, 18rem)), 1fr)
528
- );
529
- }
530
-
531
- /* Equal height cards */
532
- .card-group[data-equal-height] > .card {
533
- block-size: 100%;
534
- }
535
-
536
- .card-group[data-equal-height] > .card .card__content {
537
- flex: 1;
310
+ gap: var(--space-l) var(--space-m);
311
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--card-min-width, 18rem)), 1fr));
538
312
  }
539
313
 
540
-
541
- /* ========================================
542
- THEME-SPECIFIC CARD STYLES
543
- Motion styles affect card appearance
544
- ======================================== */
545
-
546
- /* Still: Crisp, no-nonsense borders */
547
- [data-motion="still"] .card {
548
- border-width: 1px;
549
- border-color: var(--color-border);
550
- box-shadow: none;
551
- }
552
-
553
- /* Serene: Extra soft, pillowy cards with gentle shadows */
554
- [data-motion="serene"] .card {
555
- box-shadow: var(--shadow-sm);
556
- }
557
- [data-motion="serene"] .card[data-variant="elevated"] {
558
- box-shadow: var(--shadow-md);
559
- }
560
- [data-motion="serene"] .card:hover {
561
- box-shadow: var(--shadow-md);
562
- }
563
-
564
- /* Cascade: Prominent layered shadows, bubbly elevated feel */
565
- [data-motion="cascade"] .card {
566
- box-shadow:
567
- 0 4px 12px oklch(0 0 0 / 0.06),
568
- 0 12px 28px oklch(0 0 0 / 0.1);
569
- }
570
- [data-motion="cascade"] .card:hover {
571
- transform: translateY(-4px);
572
- box-shadow:
573
- 0 8px 20px oklch(0 0 0 / 0.08),
574
- 0 20px 40px oklch(0 0 0 / 0.14);
575
- }
576
- [data-motion="cascade"] .card[data-variant="elevated"] {
577
- box-shadow:
578
- 0 8px 20px oklch(0 0 0 / 0.08),
579
- 0 24px 48px oklch(0 0 0 / 0.12);
580
- }
581
-
582
- /* Rapids: Hard offset shadows, energetic hover */
583
- [data-motion="rapids"] .card {
584
- box-shadow: var(--shadow-offset);
585
- transition:
586
- transform 150ms var(--ease-fluid),
587
- box-shadow 150ms var(--ease-fluid);
588
- }
589
- [data-motion="rapids"] .card:hover {
590
- transform: translate(-2px, -2px);
591
- box-shadow: var(--shadow-offset-hover);
592
- }
593
- [data-motion="rapids"] .card:active {
594
- transform: translate(1px, 1px);
595
- box-shadow: 2px 2px 0 oklch(0 0 0 / 0.15);
596
- }
597
-
598
- /* Tsunami: 3D depth effect with dramatic shadows */
599
- [data-motion="tsunami"] .card {
600
- transform-style: preserve-3d;
601
- transition: transform 0.4s var(--ease-fluid), box-shadow 0.4s var(--ease-fluid);
602
- box-shadow:
603
- 0 15px 35px oklch(0 0 0 / 0.2),
604
- 0 5px 15px oklch(0 0 0 / 0.1);
605
- }
606
- [data-motion="tsunami"] .card:hover {
607
- transform: translateY(-8px) scale(1.01);
608
- box-shadow:
609
- 0 25px 50px oklch(0 0 0 / 0.25),
610
- 0 10px 20px oklch(0 0 0 / 0.15),
611
- 0 0 40px oklch(from var(--accent) l c h / 0.15);
612
- }
613
-
614
-
615
- /* ========================================
616
- REDUCED MOTION
617
- ======================================== */
618
-
619
- @media (prefers-reduced-motion: reduce) {
620
- .card {
621
- transition: none;
622
- }
623
-
624
- .card[data-interactive]:active,
625
- a.card:active {
626
- transform: none;
627
- }
628
-
629
- [data-motion="cascade"] .card:hover,
630
- [data-motion="rapids"] .card:hover,
631
- [data-motion="rapids"] .card:active,
632
- [data-motion="tsunami"] .card:hover {
633
- transform: none;
634
- transition: none;
635
- }
636
-
637
- [data-motion="rapids"] .card {
638
- transition: none;
639
- }
640
-
641
- [data-motion="tsunami"] .card {
642
- transition: none;
643
- }
644
-
645
- .card__media,
646
- .card__media-wrap::after {
647
- transition: none;
648
- }
649
- }
314
+ .card-group[data-equal-height] > .card { block-size: 100%; }
650
315
  }