@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.
Files changed (80) hide show
  1. package/AGENTS.md +6 -29
  2. package/CHANGELOG.md +543 -2
  3. package/CLAUDE.md +193 -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.min.css +2 -0
  14. package/dist/fluid-legacy-1x.css +3 -0
  15. package/dist/fluid-nav.min.css +3 -0
  16. package/dist/fluid-toc.min.css +3 -0
  17. package/dist/fluid-zero.min.css +3 -0
  18. package/dist/fluid.min.css +3 -2
  19. package/dist/fonts/OFL-newsreader.txt +93 -0
  20. package/dist/fonts/OFL-schibsted-grotesk.txt +93 -0
  21. package/dist/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
  22. package/dist/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  23. package/dist/fonts/schibsted-grotesk-latin-wght-italic.woff2 +0 -0
  24. package/dist/fonts/schibsted-grotesk-latin-wght-normal.woff2 +0 -0
  25. package/fluid-agent-kit/README.md +1 -0
  26. package/fluid-agent-kit/skills/accessibility.md +3 -1
  27. package/fluid-agent-kit/skills/css-conventions.md +22 -5
  28. package/fluid-agent-kit/skills/image-optimization.md +2 -0
  29. package/fluid-agent-kit/skills/preset-composition.md +108 -0
  30. package/package.json +29 -3
  31. package/src/animations/scroll-reveal.css +75 -193
  32. package/src/components/accordion.css +6 -47
  33. package/src/components/alert.css +47 -88
  34. package/src/components/avatar.css +4 -20
  35. package/src/components/badge.css +25 -48
  36. package/src/components/button.css +69 -175
  37. package/src/components/card.css +146 -481
  38. package/src/components/chip.css +46 -12
  39. package/src/components/compare.css +234 -0
  40. package/src/components/dropdown.css +10 -67
  41. package/src/components/empty-state.css +4 -0
  42. package/src/components/form.css +113 -81
  43. package/src/components/gallery.css +252 -0
  44. package/src/components/hero.css +203 -88
  45. package/src/components/indicator.css +23 -117
  46. package/src/components/loading.css +7 -29
  47. package/src/components/modal.css +12 -65
  48. package/src/components/nav-modes.css +261 -0
  49. package/src/components/nav.css +324 -392
  50. package/src/components/search-input.css +4 -2
  51. package/src/components/step-indicator.css +26 -27
  52. package/src/components/table.css +64 -171
  53. package/src/components/toc.css +254 -0
  54. package/src/components/tooltip.css +5 -46
  55. package/src/core/base.css +393 -547
  56. package/src/core/reset.css +17 -5
  57. package/src/core/tokens.css +426 -2276
  58. package/src/fluid.css +258 -17
  59. package/src/fonts/OFL-newsreader.txt +93 -0
  60. package/src/fonts/OFL-schibsted-grotesk.txt +93 -0
  61. package/src/fonts/fluid-fonts.css +55 -0
  62. package/src/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
  63. package/src/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  64. package/src/fonts/schibsted-grotesk-latin-wght-italic.woff2 +0 -0
  65. package/src/fonts/schibsted-grotesk-latin-wght-normal.woff2 +0 -0
  66. package/src/layouts/positioning.css +6 -3
  67. package/src/legacy-1x.css +61 -0
  68. package/src/sections/cta.css +33 -101
  69. package/src/sections/features.css +42 -219
  70. package/src/sections/footer.css +53 -134
  71. package/src/sections/stats.css +40 -153
  72. package/src/sections/team.css +24 -113
  73. package/src/sections/testimonials.css +34 -148
  74. package/src/zero/base.css +166 -0
  75. package/src/zero/tokens.css +64 -0
  76. package/src/zero.css +20 -0
  77. package/src/components/scroll-progress.css +0 -104
  78. package/src/components/settings-panel.css +0 -603
  79. package/src/components/themed-images.css +0 -229
  80. package/src/components/version-badge.css +0 -70
@@ -64,108 +64,86 @@
64
64
 
65
65
  /* ========================================
66
66
  BASE FOOTER
67
+ Its own grammar, not another tinted band: the page colour under a
68
+ thick ink rule (a tinted section above it no longer runs into it),
69
+ the brand in the heading face like the nav's. Content on the
70
+ .container edges. Links in the text colour, no underline at rest;
71
+ hover and aria-current underline them.
67
72
  ======================================== */
68
73
 
69
74
  .site-footer {
70
- --_footer-bg: var(--color-surface);
71
- --_footer-text: var(--color-text-muted);
72
- --_footer-border: var(--color-border-subtle);
73
- --_footer-padding: var(--space-10);
75
+ --_footer-border: var(--color-border);
76
+ --_footer-padding: var(--space-xl);
74
77
 
75
- background-color: var(--_footer-bg);
76
- color: var(--_footer-text);
78
+ background-color: var(--_footer-bg, var(--color-background));
79
+ color: var(--color-text-muted);
77
80
  padding-block: var(--_footer-padding);
78
- padding-inline: var(--space-5);
79
- border-block-start: var(--border-width) solid var(--_footer-border);
81
+ padding-inline: max(var(--page-edge), (100% - var(--container-content)) / 2);
82
+ border-block-start: var(--border-width-thick) solid var(--_footer-rule, var(--color-border-strong));
83
+ font-size: var(--step--1);
80
84
  }
81
85
 
82
- .site-footer a {
83
- color: var(--_footer-text);
84
- text-decoration: none;
85
- transition: color var(--duration-fast) var(--ease-fluid);
86
+ .site-footer :is(nav:not(.nav), .site-footer__links) a { color: var(--color-text); }
87
+
88
+ .site-footer a:not(.nav *) {
89
+ color: inherit;
90
+ text-decoration-line: none;
86
91
  }
87
92
 
88
- .site-footer a:hover {
93
+ .site-footer a:not(.nav *):is(:hover, [aria-current]) {
89
94
  color: var(--color-text);
95
+ text-decoration-line: underline;
90
96
  }
91
97
 
92
98
  .site-footer a:focus-visible {
93
- outline: 2px solid var(--color-focus);
99
+ outline: var(--focus-ring-width) solid var(--color-focus);
94
100
  outline-offset: 2px;
95
- border-radius: var(--radius-sm);
96
101
  }
97
102
 
98
-
99
- /* ========================================
100
- SIMPLE (default)
101
- Copyright + horizontal links
102
- ======================================== */
103
-
103
+ /* Simple (default): copyright + a row of links */
104
104
  .site-footer__inner {
105
105
  display: flex;
106
106
  flex-wrap: wrap;
107
107
  justify-content: space-between;
108
108
  align-items: center;
109
- gap: var(--space-4);
110
- max-inline-size: var(--container-xl);
111
- margin-inline: auto;
109
+ gap: var(--space-s) var(--space-m);
112
110
  }
113
111
 
114
- .site-footer__copyright {
115
- font-size: var(--font-size-sm);
116
- margin: 0;
117
- }
112
+ .site-footer__copyright { margin: 0; }
118
113
 
119
114
  .site-footer:not([data-type]) .site-footer__links {
120
115
  display: flex;
121
116
  flex-wrap: wrap;
122
- gap: var(--space-5);
123
- font-size: var(--font-size-sm);
117
+ gap: var(--space-s) var(--space-m);
124
118
  }
125
119
 
126
-
127
- /* ========================================
128
- STANDARD
129
- Multi-column grid with bottom bar
130
- ======================================== */
131
-
132
- .site-footer[data-type="standard"],
133
- .site-footer[data-type="mega"] {
134
- --_footer-padding: var(--space-16);
135
- }
120
+ /* Standard and mega: columns over a bottom bar */
121
+ .site-footer:is([data-type="standard"], [data-type="mega"]) { --_footer-padding: var(--space-2xl) var(--space-xl); }
136
122
 
137
123
  .site-footer__grid {
138
124
  display: grid;
139
- gap: var(--space-8);
140
- grid-template-columns: repeat(
141
- auto-fit,
142
- minmax(min(100%, 12rem), 1fr)
143
- );
144
- max-inline-size: var(--container-xl);
145
- margin-inline: auto;
125
+ gap: var(--space-l) var(--space-m);
126
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
146
127
  }
147
128
 
148
129
  .site-footer__column {
149
130
  display: flex;
150
131
  flex-direction: column;
151
- gap: var(--space-3);
132
+ gap: var(--space-s);
152
133
  }
153
134
 
154
135
  .site-footer__title {
155
- font-size: var(--font-size-sm);
156
- font-weight: var(--weight-semibold, 600);
157
- color: var(--color-text);
136
+ font: calc(600 + var(--_dark-weight)) var(--step--2) / 1.2 var(--font-body);
158
137
  text-transform: uppercase;
159
- letter-spacing: var(--tracking-wider);
138
+ letter-spacing: var(--tracking-caps);
139
+ color: var(--color-text);
160
140
  margin: 0;
161
- margin-block-end: var(--space-2);
162
141
  }
163
142
 
164
143
  .site-footer__column .site-footer__links {
165
144
  display: flex;
166
145
  flex-direction: column;
167
- gap: var(--space-2);
168
- font-size: var(--font-size-sm);
146
+ gap: var(--space-xs);
169
147
  }
170
148
 
171
149
  .site-footer__bottom {
@@ -173,71 +151,42 @@
173
151
  flex-wrap: wrap;
174
152
  justify-content: space-between;
175
153
  align-items: center;
176
- gap: var(--space-4);
177
- max-inline-size: var(--container-xl);
178
- margin-inline: auto;
179
- margin-block-start: var(--space-10);
180
- padding-block-start: var(--space-6);
154
+ gap: var(--space-s) var(--space-m);
155
+ margin-block-start: var(--space-xl);
156
+ padding-block-start: var(--space-m);
181
157
  border-block-start: var(--border-width) solid var(--_footer-border);
182
- font-size: var(--font-size-sm);
183
- }
184
-
185
-
186
- /* ========================================
187
- MEGA FOOTER
188
- Brand section + columns + bottom bar
189
- ======================================== */
190
-
191
- .site-footer__brand {
192
- max-inline-size: 20rem;
193
- margin-inline: auto;
194
- margin-block-end: var(--space-10);
195
158
  }
196
159
 
197
- .site-footer[data-type="mega"] .site-footer__brand {
198
- max-inline-size: var(--container-xl);
199
- margin-inline: auto;
200
- margin-block-end: var(--space-10);
201
- }
160
+ .site-footer__brand:not(.site-footer__column) { margin-block-end: var(--space-xl); }
202
161
 
203
- .site-footer__brand h2 {
204
- font-size: var(--font-size-xl);
205
- font-weight: var(--weight-bold);
162
+ /* The brand, as the nav sets it (a column can be the brand too:
163
+ <div class="site-footer__column site-footer__brand">). */
164
+ .site-footer__brand :is(h2, .site-footer__title) {
165
+ font: calc(500 + var(--_dark-weight)) var(--step-1) / 1.2 var(--font-heading);
166
+ text-transform: none;
167
+ letter-spacing: var(--tracking-heading);
206
168
  color: var(--color-text);
207
- margin: 0;
208
- margin-block-end: var(--space-2);
169
+ margin: 0; margin-block-end: var(--space-xs);
209
170
  }
210
171
 
211
172
  .site-footer__brand p {
212
- font-size: var(--font-size-sm);
213
173
  color: var(--color-text-muted);
214
- margin: 0;
215
- margin-block-end: var(--space-4);
216
- max-inline-size: 20rem;
174
+ margin: 0; margin-block-end: var(--space-s);
175
+ max-inline-size: 28rem;
217
176
  }
218
177
 
219
178
  .site-footer__social {
220
179
  display: flex;
221
- gap: var(--space-3);
180
+ gap: var(--space-1);
181
+ margin-inline-start: -0.75rem;
222
182
  }
223
183
 
224
184
  .site-footer__social a {
225
185
  display: flex;
226
186
  align-items: center;
227
187
  justify-content: center;
228
- inline-size: 2.25rem;
229
- block-size: 2.25rem;
230
- border-radius: var(--radius-full);
231
- color: var(--color-text-muted);
232
- background-color: color-mix(in oklch, var(--color-text) 8%, transparent);
233
- transition:
234
- color var(--duration-fast) var(--ease-fluid),
235
- background-color var(--duration-fast) var(--ease-fluid);
236
- }
237
-
238
- .site-footer__social a:hover {
239
- color: var(--white);
240
- background-color: var(--accent);
188
+ inline-size: 2.75rem;
189
+ block-size: 2.75rem;
241
190
  }
242
191
 
243
192
  .site-footer__social svg {
@@ -245,39 +194,9 @@
245
194
  block-size: 1.125rem;
246
195
  }
247
196
 
248
-
249
- /* ========================================
250
- DARK VARIANT
251
- Darker background for contrast with page
252
- ======================================== */
253
-
197
+ /* Dark: a dark band. Its roles re-resolve dark (tokens.css role scope). */
254
198
  .site-footer[data-dark] {
255
- --_footer-bg: var(--theme-dark, var(--gray-15));
256
- --_footer-text: var(--gray-60);
257
- --_footer-border: var(--gray-20);
258
- }
259
-
260
- .site-footer[data-dark] a:hover {
261
- color: var(--white);
262
- }
263
-
264
- .site-footer[data-dark] .site-footer__title {
265
- color: var(--gray-80);
266
- }
267
-
268
- .site-footer[data-dark] .site-footer__brand h2 {
269
- color: var(--white);
270
- }
271
-
272
-
273
- /* ========================================
274
- ACCESSIBILITY
275
- ======================================== */
276
-
277
- @media (prefers-reduced-motion: reduce) {
278
- .site-footer a,
279
- .site-footer__social a {
280
- transition: none;
281
- }
199
+ --_footer-bg: var(--band-inverse);
200
+ --_footer-rule: transparent;
282
201
  }
283
202
  }
@@ -32,203 +32,90 @@
32
32
  */
33
33
 
34
34
  @layer components {
35
+ /* The band's padding and edges, and the header (a section head), are in
36
+ src/fluid.css (layouts). */
35
37
 
36
- /* ========================================
37
- BASE STATS SECTION
38
- ======================================== */
39
-
40
- .stats {
41
- padding-block: var(--space-16);
42
- padding-inline: var(--space-5);
43
- max-inline-size: var(--container-xl);
44
- margin-inline: auto;
45
- }
46
-
47
- .stats__header {
48
- text-align: center;
49
- max-inline-size: var(--container-md);
50
- margin-inline: auto;
51
- margin-block-end: var(--space-12);
52
- }
53
-
54
- .stats__header h2 {
55
- font-size: var(--font-size-3xl);
56
- font-weight: var(--weight-bold);
57
- color: var(--color-text);
58
- margin-block-end: var(--space-3);
59
- }
60
-
61
- .stats__header p {
62
- font-size: var(--font-size-lg);
63
- color: var(--color-text-muted);
64
- }
65
-
66
-
67
- /* ========================================
68
- STATS GRID
69
- ======================================== */
38
+ .stats__header p:not(.eyebrow, .lede) { color: var(--color-text-muted); } /* the label voice owns the eyebrow's colour */
70
39
 
71
40
  .stats__grid {
72
41
  display: grid;
73
- gap: var(--space-6);
74
- grid-template-columns: repeat(
75
- auto-fit,
76
- minmax(min(100%, 10rem), 1fr)
77
- );
42
+ gap: var(--space-l) var(--space-m);
43
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
78
44
  margin: 0;
79
45
  padding: 0;
80
46
  }
81
47
 
82
48
 
83
49
  /* ========================================
84
- INDIVIDUAL STAT
50
+ STAT: a serif numeral under a 2px ink rule, the label beneath. No tile.
85
51
  ======================================== */
86
52
 
87
53
  .stat {
88
- --_stat-padding: var(--space-6);
89
- --_stat-bg: var(--color-surface);
90
- --_stat-border: var(--color-border-subtle);
91
- --_stat-radius: var(--radius-lg);
92
-
93
54
  display: flex;
94
55
  flex-direction: column;
95
- align-items: center;
96
- text-align: center;
97
- gap: var(--space-2);
98
- padding: var(--_stat-padding);
99
- background-color: var(--_stat-bg);
100
- border: var(--border-width) solid var(--_stat-border);
101
- border-radius: var(--_stat-radius);
56
+ gap: var(--space-xs);
57
+ padding-block-start: var(--space-s);
58
+ border-block-start: var(--border-width-thick) solid var(--_stat-border, var(--color-border-strong));
102
59
  }
103
60
 
104
61
  .stat__value {
105
- font-size: var(--font-size-4xl);
106
- font-weight: var(--weight-bold);
107
- color: var(--accent);
108
- line-height: 1.1;
62
+ order: -1;
63
+ font-family: var(--font-heading);
64
+ font-size: var(--step-4);
65
+ font-weight: calc(var(--weight-display) + var(--_dark-weight));
66
+ line-height: 1;
67
+ letter-spacing: var(--tracking-heading);
68
+ font-variant-numeric: lining-nums tabular-nums;
69
+ color: var(--color-text);
109
70
  margin: 0;
110
71
  }
111
72
 
112
73
  .stat__label {
113
- font-size: var(--font-size-sm);
74
+ font-size: var(--step--1);
75
+ font-weight: 500;
114
76
  color: var(--color-text-muted);
115
- font-weight: var(--weight-medium, 500);
116
77
  }
117
78
 
118
79
  .stat__icon {
119
80
  display: flex;
120
- align-items: center;
121
- justify-content: center;
122
- color: var(--accent);
123
- font-size: var(--font-size-xl);
124
- margin-block-end: var(--space-2);
81
+ color: var(--accent-ink);
82
+ margin-block-end: var(--space-xs);
125
83
  }
126
84
 
127
85
  .stat__icon svg {
128
- inline-size: 1.5em;
129
- block-size: 1.5em;
86
+ inline-size: 1.75rem;
87
+ block-size: 1.75rem;
130
88
  stroke: currentColor;
131
- stroke-width: 2;
132
- }
133
-
134
-
135
- /* ========================================
136
- BORDERLESS VARIANT
137
- Minimal stat blocks without cards
138
- ======================================== */
139
-
140
- .stats[data-variant="borderless"] .stat {
141
- --_stat-bg: transparent;
142
- --_stat-border: transparent;
89
+ stroke-width: 1.5;
143
90
  }
144
91
 
145
- .stats[data-variant="borderless"] .stat__value {
146
- font-size: var(--font-size-5xl);
147
- }
148
-
149
-
150
- /* ========================================
151
- DIVIDER VARIANT
152
- Stats separated by vertical lines
153
- ======================================== */
154
-
155
- .stats[data-variant="divider"] .stats__grid {
156
- display: flex;
157
- flex-wrap: wrap;
158
- justify-content: center;
159
- gap: 0;
160
- }
92
+ /* Borderless: no rule, a larger numeral. */
93
+ .stats[data-variant="borderless"] .stat { --_stat-border: transparent; }
94
+ .stats[data-variant="borderless"] .stat__value { font-size: var(--step-5); }
161
95
 
96
+ /* Divider: hairlines between the stats instead of rules above them. */
162
97
  .stats[data-variant="divider"] .stat {
163
- --_stat-bg: transparent;
164
- --_stat-border: transparent;
165
- border-radius: 0;
166
- padding-inline: var(--space-8);
167
- border-inline-end: var(--border-width) solid var(--color-border-subtle);
168
- }
169
-
170
- .stats[data-variant="divider"] .stat:last-child {
171
- border-inline-end: none;
98
+ border-block-start: 0;
99
+ padding-block-start: 0;
100
+ padding-inline-end: var(--space-m);
101
+ border-inline-end: var(--border-width) solid var(--color-border);
172
102
  }
173
103
 
174
- @media (max-width: 40rem) {
175
- .stats[data-variant="divider"] .stat {
176
- border-inline-end: none;
177
- border-block-end: var(--border-width) solid var(--color-border-subtle);
178
- padding-inline: var(--space-6);
179
- padding-block: var(--space-4);
180
- }
181
-
182
- .stats[data-variant="divider"] .stat:last-child {
183
- border-block-end: none;
184
- }
185
- }
104
+ .stats[data-variant="divider"] .stat:last-child { border-inline-end: 0; }
186
105
 
187
106
 
188
107
  /* ========================================
189
- COUNT-UP ANIMATION
190
- Plays once when section enters viewport
108
+ ENTRANCE (opt-in: data-animate). A short rise and fade, once.
191
109
  ======================================== */
192
110
 
193
111
  @keyframes stat-fade-up {
194
- from {
195
- opacity: 0;
196
- transform: translateY(1rem);
197
- }
198
- to {
199
- opacity: 1;
200
- transform: translateY(0);
201
- }
202
- }
203
-
204
- .stats[data-animate] .stat {
205
- animation: stat-fade-up var(--duration-slow) var(--ease-settle) both;
112
+ from { opacity: 0; translate: 0 12px; }
206
113
  }
207
114
 
208
- .stats[data-animate] .stat:nth-child(2) {
209
- animation-delay: 100ms;
210
- }
211
-
212
- .stats[data-animate] .stat:nth-child(3) {
213
- animation-delay: 200ms;
214
- }
215
-
216
- .stats[data-animate] .stat:nth-child(4) {
217
- animation-delay: 300ms;
218
- }
219
-
220
- .stats[data-animate] .stat:nth-child(5) {
221
- animation-delay: 400ms;
222
- }
223
-
224
-
225
- /* ========================================
226
- ACCESSIBILITY
227
- ======================================== */
228
-
229
- @media (prefers-reduced-motion: reduce) {
230
- .stats[data-animate] .stat {
231
- animation: none;
232
- }
115
+ @media (prefers-reduced-motion: no-preference) {
116
+ .stats[data-animate] .stat { animation: stat-fade-up var(--duration-slow) var(--ease-settle) both; }
117
+ .stats[data-animate] .stat:nth-child(2) { animation-delay: 100ms; }
118
+ .stats[data-animate] .stat:nth-child(3) { animation-delay: 200ms; }
119
+ .stats[data-animate] .stat:nth-child(4) { animation-delay: 300ms; }
233
120
  }
234
121
  }