@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
@@ -15,79 +15,51 @@
15
15
  */
16
16
 
17
17
  @layer components {
18
- /* Base button - the foundation */
18
+ /* Base button: sans 600, square, no shadow, no transform. Colour comes
19
+ through the --_btn-* hooks, so a band (data-bg="inverse|accent-strong",
20
+ a themed hero) re-points <button> and .button alike. */
19
21
  button,
20
22
  [role="button"],
21
23
  .button {
22
- /* Reset */
23
24
  appearance: none;
24
- border: none;
25
- background: none;
26
-
27
- /* Layout */
28
25
  display: inline-flex;
29
26
  align-items: center;
30
27
  justify-content: center;
31
28
  gap: var(--space-2);
32
-
33
- /* Sizing */
34
- padding-block: var(--_btn-padding-block, var(--space-3));
35
- padding-inline: var(--_btn-padding-inline, var(--space-5));
36
- min-block-size: var(--_btn-min-height, 2.75rem);
37
-
38
- /* Typography */
39
- font-family: inherit;
40
- font-size: var(--_btn-font-size, var(--font-size-base));
41
- font-weight: var(--weight-medium);
29
+ padding-block: var(--_btn-padding-block, 0.9em);
30
+ padding-inline: var(--_btn-padding-inline, 1.6em);
31
+ min-block-size: var(--_btn-min-height, 3rem);
32
+ font-family: var(--font-body);
33
+ font-size: var(--_btn-font-size, var(--step-0));
34
+ font-weight: calc(var(--weight-semibold) + var(--_dark-weight));
35
+ letter-spacing: 0.01em;
42
36
  line-height: var(--leading-none);
43
37
  text-decoration: none;
44
38
  white-space: nowrap;
45
-
46
- /* Visual */
47
- color: var(--_btn-color, var(--white));
48
- background-color: var(--_btn-bg, var(--accent));
39
+ color: var(--_btn-color, var(--on-accent-fill));
40
+ background-color: var(--_btn-bg, var(--accent-fill));
49
41
  border: var(--border-width) solid var(--_btn-border, transparent);
50
- border-radius: var(--radius-button, var(--radius-md));
51
- box-shadow: var(--_btn-shadow, var(--shadow-xs)); /* Subtle depth at rest */
42
+ border-radius: var(--radius-control);
52
43
  cursor: pointer;
53
-
54
- /* Transitions - smooth like flowing water */
55
44
  transition:
56
- background-color var(--duration-normal) var(--ease-fluid),
57
- border-color var(--duration-normal) var(--ease-fluid),
58
- color var(--duration-normal) var(--ease-fluid),
59
- box-shadow var(--duration-normal) var(--ease-fluid),
60
- transform var(--duration-fast) var(--ease-settle);
45
+ background-color var(--duration-fast) var(--ease-fluid),
46
+ border-color var(--duration-fast) var(--ease-fluid),
47
+ color var(--duration-fast) var(--ease-fluid),
48
+ text-decoration-color var(--duration-fast) var(--ease-fluid);
61
49
  }
62
50
 
63
- /* Hover state - gentle lift */
64
- button:hover:not(:disabled),
65
- [role="button"]:hover:not(:disabled),
66
- .button:hover:not(:disabled) {
67
- background-color: var(--_btn-bg-hover, var(--accent-hover));
68
- box-shadow: var(--shadow-sm);
51
+ :is(button, [role="button"], .button):hover:not(:disabled) {
52
+ color: var(--_btn-color-hover, var(--_btn-color, var(--on-accent-fill)));
53
+ background-color: var(--_btn-bg-hover, var(--accent-fill-hover));
69
54
  }
70
55
 
71
- /* Active state - subtle press */
72
- button:active:not(:disabled),
73
- [role="button"]:active:not(:disabled),
74
- .button:active:not(:disabled) {
75
- transform: scale(0.98);
76
- box-shadow: none;
56
+ /* Focus: a real outline, so no shadow a site adds can hide it (#50),
57
+ and it survives forced-colors mode. */
58
+ :is(button, [role="button"], .button):focus-visible {
59
+ outline: var(--focus-ring-width) solid var(--color-focus);
60
+ outline-offset: 2px;
77
61
  }
78
62
 
79
- /* Focus state - clear ring */
80
- button:focus-visible,
81
- [role="button"]:focus-visible,
82
- .button:focus-visible {
83
- outline: 2px solid transparent;
84
- box-shadow:
85
- var(--shadow-glow),
86
- 0 0 0 2px var(--color-background),
87
- 0 0 0 4px var(--accent);
88
- }
89
-
90
- /* Disabled state */
91
63
  button:disabled,
92
64
  [role="button"][aria-disabled="true"],
93
65
  .button:disabled {
@@ -99,54 +71,60 @@
99
71
 
100
72
  /* ========================================
101
73
  VARIANTS
102
- Each with its own personality
103
74
  ======================================== */
104
75
 
105
- /* Secondary - understated, supportive */
106
- button[data-variant="secondary"],
107
- .button[data-variant="secondary"] {
76
+ :is(button, .button)[data-variant="secondary"] {
108
77
  --_btn-color: var(--color-text);
109
78
  --_btn-bg: var(--color-surface);
110
79
  --_btn-bg-hover: var(--color-surface-raised);
111
80
  --_btn-border: var(--color-border);
112
81
  }
113
82
 
114
- button[data-variant="secondary"]:hover:not(:disabled),
115
- .button[data-variant="secondary"]:hover:not(:disabled) {
116
- --_btn-border: var(--color-text-subtle);
83
+ /* Outline: an ink edge; hover inverts to an ink fill. */
84
+ :is(button, .button)[data-variant="outline"] {
85
+ --_btn-color: var(--color-text);
86
+ --_btn-bg: transparent;
87
+ --_btn-border: var(--color-border-strong);
88
+ --_btn-bg-hover: var(--color-text);
89
+ --_btn-color-hover: var(--color-background);
117
90
  }
118
91
 
119
- /* Ghost - minimal, text-like */
120
- button[data-variant="ghost"],
121
- .button[data-variant="ghost"] {
92
+ /* Ghost: underlined text and an arrow that steps forward on hover.
93
+ data-arrow="none" drops the arrow. */
94
+ :is(button, .button)[data-variant="ghost"] {
122
95
  --_btn-color: var(--color-text);
123
96
  --_btn-bg: transparent;
124
- --_btn-bg-hover: var(--color-surface);
125
- --_btn-border: transparent;
126
- --_btn-shadow: none; /* No shadow for ghost buttons */
97
+ --_btn-bg-hover: transparent;
98
+ --_btn-padding-inline: 0;
99
+ text-decoration-line: underline;
100
+ text-decoration-color: color-mix(in oklch, var(--accent-ink) 55%, transparent);
101
+ text-decoration-thickness: 0.06em;
102
+ text-underline-offset: 0.22em;
127
103
  }
128
104
 
129
- /* Outline - bordered, no fill */
130
- button[data-variant="outline"],
131
- .button[data-variant="outline"] {
132
- --_btn-color: var(--accent);
133
- --_btn-bg: transparent;
134
- --_btn-bg-hover: var(--accent-subtle);
135
- --_btn-border: var(--accent);
136
- --_btn-shadow: none; /* Border provides visual boundary */
105
+ :is(button, .button)[data-variant="ghost"]:hover {
106
+ text-decoration-color: var(--accent-ink);
137
107
  }
138
108
 
139
- /* Danger - for destructive actions */
140
- button[data-variant="danger"],
141
- .button[data-variant="danger"] {
142
- --_btn-color: var(--white);
143
- --_btn-bg: var(--color-error);
144
- --_btn-bg-hover: color-mix(in oklch, var(--color-error) 85%, var(--black));
109
+ :is(button, .button)[data-variant="ghost"]:not([data-icon], [data-arrow="none"], [aria-current], [aria-disabled="true"])::after {
110
+ content: "→";
111
+ transition: translate var(--duration-fast) var(--ease-settle);
145
112
  }
146
113
 
147
- /* Subtle danger - less alarming */
148
- button[data-variant="danger-subtle"],
149
- .button[data-variant="danger-subtle"] {
114
+ :is(button, .button)[data-variant="ghost"]:dir(rtl)::after { content: "←"; }
115
+
116
+ @media (prefers-reduced-motion: no-preference) {
117
+ :is(button, .button)[data-variant="ghost"]:hover::after { translate: 0.2em; }
118
+ :is(button, .button)[data-variant="ghost"]:dir(rtl):hover::after { translate: -0.2em; }
119
+ }
120
+
121
+ :is(button, .button)[data-variant="danger"] {
122
+ --_btn-color: var(--color-error-on);
123
+ --_btn-bg: var(--color-error-fill);
124
+ --_btn-bg-hover: color-mix(in oklch, var(--color-error-fill) 85%, var(--black));
125
+ }
126
+
127
+ :is(button, .button)[data-variant="danger-subtle"] {
150
128
  --_btn-color: var(--color-error);
151
129
  --_btn-bg: var(--color-error-subtle);
152
130
  --_btn-bg-hover: color-mix(in oklch, var(--color-error-subtle) 80%, var(--color-error));
@@ -165,7 +143,6 @@
165
143
  --_btn-padding-inline: var(--space-2);
166
144
  --_btn-font-size: var(--font-size-xs);
167
145
  --_btn-min-height: 1.75rem;
168
- border-radius: var(--radius-sm);
169
146
  }
170
147
 
171
148
  /* Small */
@@ -184,7 +161,6 @@
184
161
  --_btn-padding-inline: var(--space-6);
185
162
  --_btn-font-size: var(--font-size-lg);
186
163
  --_btn-min-height: 3.25rem;
187
- border-radius: var(--radius-lg);
188
164
  }
189
165
 
190
166
  /* Extra large */
@@ -194,7 +170,6 @@
194
170
  --_btn-padding-inline: var(--space-8);
195
171
  --_btn-font-size: var(--font-size-xl);
196
172
  --_btn-min-height: 4rem;
197
- border-radius: var(--radius-lg);
198
173
  }
199
174
 
200
175
 
@@ -219,7 +194,7 @@
219
194
  /* Pill shape */
220
195
  button[data-pill],
221
196
  .button[data-pill] {
222
- border-radius: var(--radius-full);
197
+ border-radius: var(--radius-pill);
223
198
  }
224
199
 
225
200
  /* Loading state */
@@ -240,11 +215,11 @@
240
215
  block-size: 1.25em;
241
216
  margin-inline-start: -0.625em;
242
217
  margin-block-start: -0.625em;
243
- border: 2px solid currentColor;
218
+ border: var(--border-width-thick) solid currentColor;
244
219
  border-inline-end-color: transparent;
245
220
  border-radius: var(--radius-full);
246
221
  animation: button-spin 0.6s linear infinite;
247
- color: var(--_btn-color, var(--white));
222
+ color: var(--_btn-color, var(--on-accent));
248
223
  opacity: 0.8;
249
224
  }
250
225
 
@@ -255,50 +230,6 @@
255
230
  }
256
231
 
257
232
 
258
- /* ========================================
259
- TSUNAMI MOTION EFFECTS
260
- Bold, wave-like interactions
261
- Uses transform only to avoid CLS
262
- ======================================== */
263
-
264
- /* Tsunami button hover - noticeable growth like a wave rising */
265
- [data-motion="tsunami"] button:hover:not(:disabled),
266
- [data-motion="tsunami"] [role="button"]:hover:not(:disabled),
267
- [data-motion="tsunami"] .button:hover:not(:disabled) {
268
- transform: scale(1.08);
269
- box-shadow: var(--shadow-lg);
270
- transition:
271
- transform var(--duration-normal) var(--ease-fluid),
272
- box-shadow var(--duration-normal) var(--ease-fluid),
273
- background-color var(--duration-normal) var(--ease-fluid);
274
- }
275
-
276
- /* Tsunami button active/click - crash down effect */
277
- [data-motion="tsunami"] button:active:not(:disabled),
278
- [data-motion="tsunami"] [role="button"]:active:not(:disabled),
279
- [data-motion="tsunami"] .button:active:not(:disabled) {
280
- transform: scale(0.92) translateY(4px);
281
- box-shadow: var(--shadow-xs);
282
- transition:
283
- transform 150ms var(--ease-settle),
284
- box-shadow 150ms var(--ease-settle);
285
- }
286
-
287
- /* Tsunami button leave - wave crash with bounce settle */
288
- @keyframes tsunami-button-settle {
289
- 0% { transform: scale(1.08); }
290
- 40% { transform: scale(0.95) translateY(3px); }
291
- 70% { transform: scale(1.02) translateY(-1px); }
292
- 100% { transform: scale(1) translateY(0); }
293
- }
294
-
295
- [data-motion="tsunami"] button:not(:hover):not(:active):not(:disabled),
296
- [data-motion="tsunami"] [role="button"]:not(:hover):not(:active):not([aria-disabled="true"]),
297
- [data-motion="tsunami"] .button:not(:hover):not(:active):not(:disabled) {
298
- animation: tsunami-button-settle var(--duration-normal) var(--ease-settle);
299
- }
300
-
301
-
302
233
  /* ========================================
303
234
  BUTTON GROUPS
304
235
  Multiple buttons together
@@ -317,14 +248,14 @@
317
248
 
318
249
  .button-group > button:first-child,
319
250
  .button-group > .button:first-child {
320
- border-start-start-radius: var(--radius-md);
321
- border-end-start-radius: var(--radius-md);
251
+ border-start-start-radius: var(--radius-control);
252
+ border-end-start-radius: var(--radius-control);
322
253
  }
323
254
 
324
255
  .button-group > button:last-child,
325
256
  .button-group > .button:last-child {
326
- border-start-end-radius: var(--radius-md);
327
- border-end-end-radius: var(--radius-md);
257
+ border-start-end-radius: var(--radius-control);
258
+ border-end-end-radius: var(--radius-control);
328
259
  }
329
260
 
330
261
  .button-group > button:not(:first-child),
@@ -345,43 +276,6 @@
345
276
  ======================================== */
346
277
 
347
278
  @media (prefers-reduced-motion: reduce) {
348
- button,
349
- [role="button"],
350
- .button {
351
- transition: none;
352
- }
353
-
354
- button:active:not(:disabled),
355
- [role="button"]:active:not(:disabled),
356
- .button:active:not(:disabled) {
357
- transform: none;
358
- }
359
-
360
- button[data-loading]::after,
361
- .button[data-loading]::after {
362
- animation: none;
363
- /* Static indicator instead of spinning */
364
- opacity: 0.6;
365
- }
366
-
367
- [data-motion="tsunami"] button:hover:not(:disabled),
368
- [data-motion="tsunami"] [role="button"]:hover:not(:disabled),
369
- [data-motion="tsunami"] .button:hover:not(:disabled) {
370
- transform: none;
371
- transition: none;
372
- }
373
-
374
- [data-motion="tsunami"] button:active:not(:disabled),
375
- [data-motion="tsunami"] [role="button"]:active:not(:disabled),
376
- [data-motion="tsunami"] .button:active:not(:disabled) {
377
- transform: none;
378
- transition: none;
379
- }
380
-
381
- [data-motion="tsunami"] button:not(:hover):not(:active):not(:disabled),
382
- [data-motion="tsunami"] [role="button"]:not(:hover):not(:active):not([aria-disabled="true"]),
383
- [data-motion="tsunami"] .button:not(:hover):not(:active):not(:disabled) {
384
- animation: none;
385
- }
279
+ :is(button, .button)[data-loading]::after { animation: none; opacity: 0.6; }
386
280
  }
387
281
  }