@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
@@ -45,7 +45,7 @@
45
45
 
46
46
  .search-input__clear {
47
47
  --_btn-bg: transparent;
48
- --_btn-shadow: none;
48
+ --_btn-bg-hover: var(--color-surface);
49
49
 
50
50
  position: absolute;
51
51
  inset-inline-end: var(--space-2);
@@ -71,7 +71,8 @@
71
71
 
72
72
  .search-input__clear:focus-visible {
73
73
  color: var(--color-text);
74
- box-shadow: var(--shadow-glow);
74
+ outline: var(--focus-ring-width) solid var(--color-focus);
75
+ outline-offset: 1px;
75
76
  }
76
77
 
77
78
  /* Hidden by default, shown via JS when input has value */
@@ -113,6 +114,7 @@
113
114
  }
114
115
 
115
116
 
117
+
116
118
  /* ========================================
117
119
  REDUCED MOTION
118
120
  ======================================== */
@@ -38,6 +38,7 @@
38
38
  }
39
39
 
40
40
  .steps__item {
41
+ --_steps-marker: 2.5rem;
41
42
  flex: 1;
42
43
  display: flex;
43
44
  flex-direction: column;
@@ -48,19 +49,28 @@
48
49
  margin: 0;
49
50
  }
50
51
 
51
- /* Connector line between steps */
52
- .steps__item:not(:last-child)::after {
52
+ /* Connector line between steps: half on each side of the boundary, so
53
+ neither half leaves its own item (visual-scan TEXT, 2026-10-29). */
54
+ .steps__item:not(:last-child)::after,
55
+ .steps__item:not(:first-child)::before {
53
56
  content: "";
54
57
  position: absolute;
55
- inset-block-start: calc(1.25rem / 2);
56
- inset-inline-start: calc(50% + 1rem);
57
- inline-size: calc(100% - 2rem);
58
+ inset-block-start: calc(var(--_steps-marker) / 2 - 1px);
58
59
  block-size: 2px;
59
60
  background: var(--color-border);
60
61
  z-index: 0;
61
62
  }
62
63
 
63
- .steps__item[data-status="complete"]:not(:last-child)::after {
64
+ .steps__item:not(:last-child)::after {
65
+ inset-inline: calc(50% + var(--_steps-marker) / 2 + 0.25rem) 0;
66
+ }
67
+
68
+ .steps__item:not(:first-child)::before {
69
+ inset-inline: 0 calc(50% + var(--_steps-marker) / 2 + 0.25rem);
70
+ }
71
+
72
+ .steps__item[data-status="complete"]:not(:last-child)::after,
73
+ .steps__item[data-status="complete"] + .steps__item::before {
64
74
  background: var(--accent);
65
75
  }
66
76
 
@@ -69,10 +79,10 @@
69
79
  display: flex;
70
80
  align-items: center;
71
81
  justify-content: center;
72
- inline-size: 2.5rem;
73
- block-size: 2.5rem;
82
+ inline-size: var(--_steps-marker);
83
+ block-size: var(--_steps-marker);
74
84
  border-radius: var(--radius-full);
75
- border: 2px solid var(--color-border);
85
+ border: var(--border-width-thick) solid var(--color-border);
76
86
  background: var(--color-background);
77
87
  font-size: var(--font-size-sm);
78
88
  font-weight: var(--weight-semibold);
@@ -89,7 +99,7 @@
89
99
  .steps__item[data-status="current"] .steps__marker {
90
100
  border-color: var(--accent);
91
101
  background: var(--accent);
92
- color: var(--white);
102
+ color: var(--on-accent);
93
103
  box-shadow: 0 0 0 3px var(--accent-subtle);
94
104
  }
95
105
 
@@ -97,7 +107,7 @@
97
107
  .steps__item[data-status="complete"] .steps__marker {
98
108
  border-color: var(--accent);
99
109
  background: var(--accent);
100
- color: var(--white);
110
+ color: var(--on-accent);
101
111
  }
102
112
 
103
113
  /* Checkmark for completed steps */
@@ -136,18 +146,12 @@
136
146
  SMALL VARIANT
137
147
  ======================================== */
138
148
 
149
+ .steps[data-size="sm"] .steps__item { --_steps-marker: 1.75rem; }
150
+
139
151
  .steps[data-size="sm"] .steps__marker {
140
- inline-size: 1.75rem;
141
- block-size: 1.75rem;
142
152
  font-size: var(--font-size-xs);
143
153
  }
144
154
 
145
- .steps[data-size="sm"] .steps__item:not(:last-child)::after {
146
- inset-block-start: calc(1.75rem / 2 - 1px);
147
- inset-inline-start: calc(50% + 0.75rem);
148
- inline-size: calc(100% - 1.5rem);
149
- }
150
-
151
155
  .steps[data-size="sm"] .steps__label {
152
156
  font-size: var(--font-size-xs);
153
157
  }
@@ -162,20 +166,15 @@
162
166
  font-size: var(--font-size-xs);
163
167
  }
164
168
 
169
+ .steps__item:not(.steps[data-size="sm"] *) { --_steps-marker: 2rem; }
170
+
165
171
  .steps__marker {
166
- inline-size: 2rem;
167
- block-size: 2rem;
168
172
  font-size: var(--font-size-xs);
169
173
  }
170
-
171
- .steps__item:not(:last-child)::after {
172
- inset-block-start: calc(1rem - 1px);
173
- inset-inline-start: calc(50% + 0.75rem);
174
- inline-size: calc(100% - 1.5rem);
175
- }
176
174
  }
177
175
 
178
176
 
177
+
179
178
  /* ========================================
180
179
  REDUCED MOTION
181
180
  ======================================== */
@@ -33,158 +33,76 @@
33
33
  * Features:
34
34
  * data-hover — highlight rows on hover
35
35
  * data-sticky-col — sticky first column
36
+ * data-responsive — below 40rem each row becomes a card: a body <th> is its
37
+ * title, <td data-label="Price"> shows "Price … value", and a
38
+ * <td> without data-label (a name, actions) spans the card
36
39
  */
37
40
 
38
41
  @layer components {
39
- /* ========================================
40
- TABLE WRAPPER
41
- Provides horizontal scroll overflow
42
- ======================================== */
43
-
42
+ /* The wrap only scrolls: no wrapper card. */
44
43
  .table-wrap {
45
44
  overflow-x: auto;
46
45
  overscroll-behavior-x: contain;
47
- border: var(--border-width) solid var(--color-border-subtle);
48
- border-radius: var(--radius-lg);
49
-
50
- /* Scrollbar styling */
51
46
  scrollbar-width: thin;
52
47
  scrollbar-color: var(--color-border) transparent;
53
48
  }
54
49
 
55
- .table-wrap::-webkit-scrollbar {
56
- block-size: 6px;
57
- }
58
-
59
- .table-wrap::-webkit-scrollbar-track {
60
- background: transparent;
61
- }
62
-
63
- .table-wrap::-webkit-scrollbar-thumb {
64
- background: var(--color-border);
65
- border-radius: var(--radius-full);
66
- }
67
-
68
-
69
- /* ========================================
70
- BASE TABLE
71
- ======================================== */
72
-
50
+ /* The base table (base.css) carries the look: label-voice head over a
51
+ 2px ink rule, hairline rows, tabular lining figures, no zebra, no hover. */
73
52
  .table {
74
53
  inline-size: 100%;
75
- border-collapse: collapse;
76
- font-size: var(--font-size-sm);
77
- line-height: var(--leading-normal);
78
- text-align: start;
54
+ line-height: 1.45;
79
55
  }
80
56
 
81
- /* Header cells */
82
- .table th {
83
- padding: var(--space-3) var(--space-4);
84
- font-weight: var(--weight-semibold);
85
- font-size: var(--font-size-xs);
86
- text-transform: uppercase;
87
- letter-spacing: var(--tracking-wider);
88
- color: var(--color-text-muted);
89
- background: var(--color-surface);
90
- border-block-end: var(--border-width-thick) solid var(--color-border-subtle);
91
- white-space: nowrap;
57
+ .table thead th {
92
58
  position: relative;
59
+ white-space: nowrap;
93
60
  }
94
61
 
95
- /* Body cells */
96
- .table td {
97
- padding: var(--space-3) var(--space-4);
98
- border-block-end: var(--border-width) solid var(--color-border-subtle);
99
- vertical-align: middle;
100
- }
101
-
102
- /* Last row: no bottom border */
103
- .table tbody tr:last-child td {
104
- border-block-end: none;
105
- }
62
+ /* Row headers (a <th> in the body) wrap; only column headers stay on one
63
+ line, so a long name cannot push the table off a phone. */
64
+ .table tbody th { white-space: normal; }
106
65
 
107
- /* Alignment via data attribute */
108
- .table [data-align="end"],
109
- .table [data-align="right"] {
110
- text-align: end;
111
- }
112
-
113
- .table [data-align="center"] {
114
- text-align: center;
115
- }
66
+ .table td { vertical-align: middle; }
116
67
 
117
- /* Numeric data: tabular-nums for alignment */
118
- .table [data-type="numeric"] {
119
- font-variant-numeric: tabular-nums;
120
- text-align: end;
121
- }
68
+ .table :is([data-align="end"], [data-align="right"], [data-type="numeric"]) { text-align: end; }
69
+ .table [data-align="center"] { text-align: center; }
122
70
 
123
71
 
124
72
  /* ========================================
125
- TABLE VARIANTS
73
+ VARIANTS
126
74
  ======================================== */
127
75
 
128
- /* Striped: alternating row backgrounds */
129
- .table[data-variant="striped"] tbody tr:nth-child(even) {
130
- background: var(--color-surface);
131
- }
76
+ .table[data-variant="striped"] tbody tr:nth-child(even) { background: var(--color-surface); }
132
77
 
133
- /* Bordered: all cell borders */
134
- .table[data-variant="bordered"] th,
135
- .table[data-variant="bordered"] td {
136
- border: var(--border-width) solid var(--color-border-subtle);
78
+ .table[data-variant="bordered"] :is(th, td) {
79
+ border: var(--border-width) solid var(--color-border);
80
+ padding-inline: var(--space-3);
137
81
  }
138
82
 
139
- /* Compact: tighter spacing */
140
- .table[data-variant="compact"] th,
141
- .table[data-variant="compact"] td {
142
- padding: var(--space-2) var(--space-3);
143
- }
83
+ .table[data-variant="compact"] :is(th, td) { padding-block: var(--space-2); }
144
84
 
85
+ .table[data-hover] tbody tr:hover { background: var(--color-surface); }
145
86
 
146
- /* ========================================
147
- INTERACTIVE FEATURES
148
- ======================================== */
149
-
150
- /* Hover highlight */
151
- .table[data-hover] tbody tr {
152
- transition: background-color var(--duration-fast) var(--ease-fluid);
153
- }
154
-
155
- .table[data-hover] tbody tr:hover {
156
- background: color-mix(in oklch, var(--accent) 5%, var(--color-background));
157
- }
87
+ .table[data-selectable] tbody tr { cursor: pointer; }
88
+ .table[data-selectable] tbody tr[aria-selected="true"] { background: var(--accent-subtle); }
158
89
 
159
- /* Selectable rows */
160
- .table[data-selectable] tbody tr {
161
- cursor: pointer;
162
- }
163
-
164
- .table[data-selectable] tbody tr[aria-selected="true"] {
165
- background: color-mix(in oklch, var(--accent) 10%, var(--color-background));
166
- }
167
-
168
- /* Sticky first column */
169
- .table[data-sticky-col] th:first-child,
170
- .table[data-sticky-col] td:first-child {
90
+ /* Sticky first column: its edge is a logical hairline (RTL-safe). */
91
+ .table[data-sticky-col] :is(th, td):first-child {
171
92
  position: sticky;
172
93
  inset-inline-start: 0;
173
94
  z-index: var(--z-raised);
174
- background: var(--color-surface-raised);
175
- box-shadow: 2px 0 4px oklch(from var(--shadow-color) l c h / 0.06);
95
+ background: var(--color-background);
96
+ border-inline-end: var(--border-width) solid var(--color-border);
176
97
  }
177
98
 
178
- .table[data-sticky-col] th:first-child {
179
- background: var(--color-surface);
180
- z-index: calc(var(--z-raised) + 1);
181
- }
99
+ .table[data-sticky-col] th:first-child { z-index: calc(var(--z-raised) + 1); }
182
100
 
183
- /* Sticky header */
184
101
  .table[data-sticky-header] thead th {
185
102
  position: sticky;
186
103
  inset-block-start: 0;
187
104
  z-index: var(--z-raised);
105
+ background: var(--color-background);
188
106
  }
189
107
 
190
108
 
@@ -198,9 +116,7 @@
198
116
  padding-inline-end: var(--space-6);
199
117
  }
200
118
 
201
- .table th[data-sortable]:hover {
202
- background: color-mix(in oklch, var(--accent) 8%, var(--color-surface));
203
- }
119
+ .table th[data-sortable]:hover { color: var(--color-text); }
204
120
 
205
121
  .table th[data-sortable]::after {
206
122
  content: "";
@@ -230,28 +146,17 @@
230
146
  }
231
147
 
232
148
 
233
- /* ========================================
234
- EMPTY STATE
235
- ======================================== */
236
-
237
149
  .table__empty {
238
150
  text-align: center;
239
- padding: var(--space-10) var(--space-5);
240
- color: var(--color-text-subtle);
241
- }
242
-
243
- .table__empty td {
244
- border: none;
151
+ padding-block: var(--space-xl);
152
+ color: var(--color-text-muted);
245
153
  }
246
154
 
247
-
248
- /* ========================================
249
- TABLE CAPTION & FOOTER
250
- ======================================== */
155
+ .table__empty td { border: none; }
251
156
 
252
157
  .table caption {
253
- padding: var(--space-3) var(--space-4);
254
- font-size: var(--font-size-sm);
158
+ padding-block: var(--space-s);
159
+ font-size: var(--step--1);
255
160
  color: var(--color-text-muted);
256
161
  text-align: start;
257
162
  caption-side: bottom;
@@ -259,39 +164,12 @@
259
164
 
260
165
  .table tfoot td {
261
166
  font-weight: var(--weight-semibold);
262
- background: var(--color-surface);
263
- border-block-start: var(--border-width-thick) solid var(--color-border-subtle);
264
- }
265
-
266
-
267
- /* ========================================
268
- THEME-SPECIFIC STYLES
269
- ======================================== */
270
-
271
- /* Still: crisp borders */
272
- [data-motion="still"] .table-wrap {
273
- border-radius: 0;
274
- }
275
-
276
- /* Serene: soft rounded */
277
- [data-motion="serene"] .table-wrap {
278
- border-radius: var(--radius-xl);
279
- }
280
-
281
- /* Rapids: hard offset shadow */
282
- [data-motion="rapids"] .table-wrap {
283
- box-shadow: var(--shadow-offset);
284
- }
285
-
286
- /* Tsunami: asymmetric corners */
287
- [data-motion="tsunami"] .table-wrap {
288
- border-radius: var(--radius-asymmetric, 0 24px 0 0);
167
+ border-block-start: var(--border-width-thick) solid var(--color-border-strong);
289
168
  }
290
169
 
291
170
 
292
171
  /* ========================================
293
- RESPONSIVE: Stacked layout on mobile
294
- Apply data-responsive to enable
172
+ RESPONSIVE: stacked rows on phones (data-responsive)
295
173
  ======================================== */
296
174
 
297
175
  @media (max-width: 40rem) {
@@ -306,8 +184,8 @@
306
184
 
307
185
  .table[data-responsive] tbody tr {
308
186
  display: block;
309
- padding: var(--space-4);
310
- border-block-end: var(--border-width) solid var(--color-border-subtle);
187
+ padding-block: var(--space-4);
188
+ border-block-end: var(--border-width) solid var(--color-border);
311
189
  }
312
190
 
313
191
  .table[data-responsive] tbody tr:last-child {
@@ -322,11 +200,35 @@
322
200
  padding: var(--space-2) 0;
323
201
  border: none;
324
202
  text-align: end;
203
+ overflow-wrap: anywhere; /* hostnames, URLs and codes wrap instead of widening the page */
204
+ }
205
+
206
+ /* A row header (the item's or page's name) is the card's title */
207
+ .table[data-responsive] tbody th {
208
+ display: block;
209
+ padding: 0;
210
+ padding-block-end: var(--space-2);
211
+ font-size: var(--step-0);
212
+ text-transform: none;
213
+ letter-spacing: normal;
214
+ color: var(--color-text);
215
+ background: none;
216
+ border: none;
217
+ overflow-wrap: anywhere;
218
+ }
219
+
220
+ /* A cell with no data-label (a name, a row of actions) takes the full width,
221
+ start-aligned, instead of being pushed to the end beside an empty label */
222
+ .table[data-responsive] td:not([data-label]) {
223
+ display: block;
224
+ text-align: start;
325
225
  }
326
226
 
327
227
  /* Use data-label for row headers on mobile */
328
228
  .table[data-responsive] td::before {
329
229
  content: attr(data-label);
230
+ flex-shrink: 0; /* the label keeps its width; the value wraps */
231
+ overflow-wrap: normal;
330
232
  font-weight: var(--weight-semibold);
331
233
  color: var(--color-text);
332
234
  text-align: start;
@@ -338,13 +240,4 @@
338
240
  }
339
241
 
340
242
 
341
- /* ========================================
342
- REDUCED MOTION
343
- ======================================== */
344
-
345
- @media (prefers-reduced-motion: reduce) {
346
- .table[data-hover] tbody tr {
347
- transition: none;
348
- }
349
- }
350
243
  }