@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
package/src/core/base.css CHANGED
@@ -1,10 +1,11 @@
1
1
  /**
2
- * Fluid Framework - Base Element Styles
2
+ * Fluid Theme - Base Element Styles
3
3
  * @layer base
4
4
  *
5
- * Default styles for native HTML elements.
6
- * These establish the rhythm and feel without requiring classes.
7
- * Every element should feel intentional, refined, unhurried.
5
+ * Native elements, no classes needed. A quality-press page: a serif for
6
+ * display and headings, a grotesk for text and labels, one accent used like
7
+ * spice, square corners, no shadows. Nothing moves on hover except colour
8
+ * and underline.
8
9
  */
9
10
 
10
11
  @layer base {
@@ -13,11 +14,12 @@
13
14
  ======================================== */
14
15
 
15
16
  body {
16
- font-family: var(--font-body, var(--font-system));
17
- font-size: var(--font-size-base);
18
- line-height: var(--leading-normal);
17
+ font-family: var(--font-body);
18
+ font-size: var(--step-0);
19
+ line-height: var(--leading-body);
19
20
  color: var(--color-text);
20
21
  background-color: var(--color-background);
22
+ text-wrap: pretty;
21
23
  }
22
24
 
23
25
  ::selection {
@@ -27,88 +29,141 @@
27
29
 
28
30
 
29
31
  /* ========================================
30
- TYPOGRAPHY - Headings
31
- Confident but not aggressive
32
+ HEADINGS
33
+ A big, obvious ladder: serif h1-h3 at the scale's top, a change of face
34
+ at h4, the label voice at h5 and h6. Every weight adds --_dark-weight
35
+ (30 in dark mode and on dark bands).
32
36
  ======================================== */
33
37
 
34
38
  h1, h2, h3, h4, h5, h6 {
35
- font-family: var(--font-heading, var(--font-system));
36
39
  color: var(--color-text);
37
- font-weight: var(--weight-semibold);
38
- line-height: var(--leading-tight);
39
- letter-spacing: var(--tracking-tight);
40
- margin-block-start: 0;
41
- margin-block-end: var(--space-4);
42
- }
43
-
44
- h1 {
45
- font-size: var(--font-size-4xl);
46
- font-weight: var(--weight-bold);
47
- letter-spacing: -0.03em;
48
- }
49
-
40
+ margin-block: 0 var(--space-4);
41
+ }
42
+
43
+ /* The trim is over the cap height only: trimming the alphabetic baseline
44
+ too let descenders (p, q, g) hang into the next block at display sizes. */
45
+ h1, h2, h3 {
46
+ font-family: var(--font-heading);
47
+ font-weight: calc(var(--weight-display) + var(--_dark-weight));
48
+ line-height: var(--leading-heading);
49
+ text-wrap: balance;
50
+ text-box: trim-start cap alphabetic;
51
+ }
52
+
53
+ /* --h1-size, --h2-size, --h3-size (knobs, no default) set a heading's
54
+ size outright; unset, the steps. legacy-1x sets them to the 1.x clamps,
55
+ so the steps (also the nav brand, lede, card title) keep their values. */
56
+ h1 { font-size: var(--h1-size, var(--step-4)); letter-spacing: var(--tracking-tight); }
57
+ /* --weight-heading (a knob, no default) sets h2 and h3 to one weight;
58
+ unset, h2 follows the display weight and h3 sits 100 above it, but
59
+ never above 700 unless the display is (display 700 gives h3 700, not
60
+ an h3 heavier than its h2). */
50
61
  h2 {
51
- font-size: var(--font-size-3xl);
52
- letter-spacing: -0.02em;
62
+ font-size: var(--h2-size, var(--step-2));
63
+ font-weight: calc(var(--weight-heading, var(--weight-display)) + var(--_dark-weight));
64
+ letter-spacing: var(--tracking-heading);
53
65
  }
54
-
55
66
  h3 {
56
- font-size: var(--font-size-2xl);
57
- letter-spacing: -0.015em;
67
+ font-size: var(--h3-size, var(--step-1));
68
+ font-weight: calc(var(--weight-heading, min(var(--weight-display) + 100, max(var(--weight-display), 700))) + var(--_dark-weight));
69
+ letter-spacing: var(--tracking-normal);
58
70
  }
59
71
 
60
72
  h4 {
61
- font-size: var(--font-size-xl);
62
- letter-spacing: var(--tracking-tight);
63
- }
64
-
65
- h5 {
73
+ font-family: var(--font-body);
66
74
  font-size: var(--font-size-lg);
67
- font-weight: var(--weight-medium);
68
- letter-spacing: var(--tracking-normal);
75
+ font-weight: calc(650 + var(--_dark-weight));
76
+ line-height: 1.3;
77
+ }
78
+
79
+ /* The label voice: every small uppercase label is this one rule. Longhands,
80
+ so nothing else is reset; lining but proportional figures (tabular ones
81
+ gap the comma: "GOGH , 1889"; a numeric head takes .num); :where(), so
82
+ each element keeps its own low specificity. */
83
+ :where(.eyebrow, h5, h6, thead th, .plate, cite, legend, dt, .toc__label) {
84
+ font-family: var(--font-body);
85
+ font-size: var(--step--2);
86
+ font-weight: calc(var(--weight-semibold) + var(--_dark-weight));
87
+ line-height: var(--leading-tight);
88
+ font-style: normal;
89
+ text-transform: uppercase;
90
+ letter-spacing: var(--tracking-caps);
91
+ font-variant-numeric: lining-nums;
92
+ color: var(--color-text-muted);
69
93
  }
70
94
 
71
- h6 {
72
- font-size: var(--font-size-base);
73
- font-weight: var(--weight-medium);
74
- letter-spacing: var(--tracking-normal);
75
- }
95
+ .eyebrow { color: var(--accent-ink); margin-block: 0 var(--space-s); }
96
+ :where(h5, .plate) { color: var(--color-text); }
97
+ h6 { color: var(--color-text-muted); }
98
+
76
99
 
77
- /* Subheadings following main headings */
78
- :is(h1, h2, h3) + :is(h2, h3, h4) {
79
- margin-block-start: var(--space-2);
100
+ /* ========================================
101
+ DISPLAY HEADLINES
102
+ The font size is a fixed share of the column's width, so a hand break
103
+ (<br>) stays put and every line keeps filling the column as the screen
104
+ grows. --fit is the longest line's width in em (characters × 0.5,
105
+ rounded up; fit-text.js measures it once for [data-fit]). Optical size
106
+ is pinned so glyph width scales linearly with size. The cap is also
107
+ 15svh, so a three-line headline leaves the lede and actions above the
108
+ fold on a 900px-tall laptop (160px type pushed them under it). Setting
109
+ --hero-headline-size on :root overrides the fit.
110
+ ======================================== */
111
+
112
+ .display,
113
+ .hero__headline {
114
+ font-family: var(--font-display);
115
+ font-weight: calc(var(--weight-display) + var(--_dark-weight));
116
+ font-size: var(--hero-headline-size,
117
+ clamp(var(--step-4), 100cqi / var(--fit, 8), min(var(--display-max), 15svh)));
118
+ font-variation-settings: "opsz" 72;
119
+ line-height: 1;
120
+ letter-spacing: var(--tracking-display);
121
+ text-wrap: balance;
122
+ text-box: trim-start cap alphabetic;
80
123
  }
81
124
 
82
125
 
83
126
  /* ========================================
84
- TYPOGRAPHY - Body Text
85
- Room to breathe
127
+ TEXT
86
128
  ======================================== */
87
129
 
88
130
  p {
89
- margin-block-end: var(--space-5);
90
- max-inline-size: var(--container-prose);
131
+ margin-block: 0 var(--space-5);
132
+ max-inline-size: var(--measure);
91
133
  }
92
134
 
93
- /* Last child shouldn't add extra space */
94
- p:last-child {
95
- margin-block-end: 0;
96
- }
135
+ p:last-child { margin-block-end: 0; }
97
136
 
98
- strong, b {
99
- font-weight: var(--weight-semibold);
137
+ .lede {
138
+ font-family: var(--font-heading);
139
+ font-weight: calc(var(--weight-light) + var(--_dark-weight));
140
+ font-size: var(--step-1);
141
+ line-height: 1.4;
142
+ letter-spacing: -0.005em;
143
+ max-inline-size: 30em;
100
144
  }
101
145
 
102
- em, i {
103
- font-style: italic;
146
+ :is(.meta, figcaption) {
147
+ font-family: var(--font-body);
148
+ font-size: var(--step--1);
149
+ font-weight: 400;
150
+ line-height: 1.45;
151
+ letter-spacing: 0.01em;
152
+ color: var(--color-text-muted);
153
+ /* Lining but proportional: tabular figures in these faces also widen
154
+ the comma and full stop, which gaps running caption text. */
155
+ font-variant-numeric: lining-nums proportional-nums;
104
156
  }
105
157
 
158
+ strong, b { font-weight: calc(var(--weight-semibold) + var(--_dark-weight)); }
159
+
160
+ em, i { font-style: italic; }
161
+
106
162
  small {
107
- font-size: var(--font-size-sm);
163
+ font-size: var(--step--1);
108
164
  color: var(--color-text-muted);
109
165
  }
110
166
 
111
- /* Subscript and superscript */
112
167
  sub, sup {
113
168
  font-size: 75%;
114
169
  line-height: 0;
@@ -116,231 +171,128 @@
116
171
  vertical-align: baseline;
117
172
  }
118
173
 
119
- sup {
120
- inset-block-start: -0.5em;
121
- }
122
-
123
- sub {
124
- inset-block-end: -0.25em;
125
- }
174
+ sup { inset-block-start: -0.5em; }
175
+ sub { inset-block-end: -0.25em; }
126
176
 
127
177
 
128
178
  /* ========================================
129
179
  LINKS
130
- Subtle, inviting interaction
180
+ Text colour; the underline marks the link (WCAG 1.4.1). Hover turns
181
+ the underline to the accent and thickens it. Only colour and the
182
+ underline transition.
131
183
  ======================================== */
132
184
 
133
185
  a {
134
- color: var(--accent);
135
- text-decoration: underline;
136
- text-decoration-thickness: 1px;
137
- text-underline-offset: 0.15em;
186
+ color: inherit;
187
+ text-decoration-line: underline;
188
+ text-decoration-color: color-mix(in oklch, var(--accent-ink) 55%, transparent);
189
+ text-decoration-thickness: 0.06em;
190
+ text-underline-offset: 0.22em;
191
+ text-decoration-skip-ink: auto;
138
192
  transition:
139
- color var(--duration-normal) var(--ease-fluid),
140
- text-decoration-color var(--duration-normal) var(--ease-fluid);
193
+ color var(--duration-fast) var(--ease-fluid),
194
+ text-decoration-color var(--duration-fast) var(--ease-fluid),
195
+ text-decoration-thickness var(--duration-fast) var(--ease-fluid);
141
196
  }
142
197
 
143
198
  a:hover {
144
- color: var(--accent-hover);
145
- text-decoration-thickness: 2px;
199
+ text-decoration-color: var(--accent-ink);
200
+ text-decoration-thickness: 0.12em;
146
201
  }
147
202
 
148
- /* Links that look like text but reveal on hover */
149
- a[data-subtle] {
150
- color: inherit;
151
- text-decoration: none;
152
- }
153
-
154
- a[data-subtle]:hover {
155
- color: var(--accent);
156
- text-decoration: underline;
157
- }
203
+ /* Links that look like text and show their underline on hover */
204
+ a[data-subtle] { text-decoration-line: none; }
205
+ a[data-subtle]:hover { text-decoration-line: underline; }
158
206
 
159
207
 
160
208
  /* ========================================
161
209
  LISTS
162
- Structured with breathing room
163
- Theme-specific list markers
164
210
  ======================================== */
165
211
 
166
212
  ul, ol {
167
213
  padding-inline-start: var(--space-6);
168
- margin-block-end: var(--space-5);
214
+ margin-block: 0 var(--space-5);
169
215
  }
170
216
 
171
- li {
172
- margin-block-end: var(--space-2);
173
- }
174
-
175
- li:last-child {
176
- margin-block-end: 0;
177
- }
178
-
179
- /* Nested lists */
180
- li > ul,
181
- li > ol {
182
- margin-block-start: var(--space-2);
183
- margin-block-end: 0;
184
- }
185
-
186
- /* === Themed list markers ===
187
- Each child theme gets a distinctive marker
188
- Note: Uses :not([class]) to avoid overriding nav dropdowns and other styled lists */
189
-
190
- /* Still: Clean disc (default - minimal) */
191
- [data-motion="still"] ul:not([class]) {
192
- list-style-type: disc;
193
- }
194
-
195
- /* Serene: Open circle - elegant, understated */
196
- [data-motion="serene"] ul:not([class]) {
197
- list-style-type: circle;
198
- }
199
-
200
- /* Stream: Dash - modern, horizontal emphasis */
201
- [data-motion="stream"] ul:not([class]) {
202
- list-style-type: none;
203
- padding-inline-start: var(--space-5);
204
- }
205
- [data-motion="stream"] ul:not([class]) > li {
206
- position: relative;
207
- }
208
- [data-motion="stream"] ul:not([class]) > li::before {
209
- content: "—";
210
- position: absolute;
211
- inset-inline-start: calc(var(--space-5) * -1);
212
- color: var(--accent);
213
- font-weight: var(--weight-bold);
214
- }
217
+ /* Chrome draws markers with tabular figures, which in these faces widen
218
+ the full stop ("1 ."). */
219
+ ::marker { font-variant-numeric: lining-nums proportional-nums; }
215
220
 
216
- /* Cascade: Filled circle - friendly, rounded */
217
- [data-motion="cascade"] ul:not([class]) {
218
- list-style-type: none;
219
- padding-inline-start: var(--space-6);
220
- }
221
- [data-motion="cascade"] ul:not([class]) > li {
222
- position: relative;
223
- }
224
- [data-motion="cascade"] ul:not([class]) > li::before {
225
- content: "";
226
- position: absolute;
227
- inset-inline-start: calc(var(--space-6) * -1);
228
- inset-block-start: 0.55em;
229
- inline-size: 0.5em;
230
- block-size: 0.5em;
231
- background: var(--accent);
232
- border-radius: var(--radius-full);
233
- }
221
+ li { margin-block-end: var(--space-2); }
222
+ li:last-child { margin-block-end: 0; }
234
223
 
235
- /* Rapids: Chevron - energetic, forward motion */
236
- [data-motion="rapids"] ul:not([class]) {
237
- list-style-type: none;
238
- padding-inline-start: var(--space-6);
239
- }
240
- [data-motion="rapids"] ul:not([class]) > li {
241
- position: relative;
242
- }
243
- [data-motion="rapids"] ul:not([class]) > li::before {
244
- content: "";
245
- position: absolute;
246
- inset-inline-start: calc(var(--space-6) * -1);
247
- inset-block-start: 0.4em;
248
- inline-size: 0;
249
- block-size: 0;
250
- border-block-start: 0.35em solid transparent;
251
- border-block-end: 0.35em solid transparent;
252
- border-inline-start: 0.5em solid var(--accent);
253
- }
254
-
255
- /* Tsunami: Square - bold, impactful */
256
- [data-motion="tsunami"] ul:not([class]) {
257
- list-style-type: none;
258
- padding-inline-start: var(--space-6);
259
- }
260
- [data-motion="tsunami"] ul:not([class]) > li {
261
- position: relative;
262
- }
263
- [data-motion="tsunami"] ul:not([class]) > li::before {
264
- content: "";
265
- position: absolute;
266
- inset-inline-start: calc(var(--space-6) * -1);
267
- inset-block-start: 0.5em;
268
- inline-size: 0.5em;
269
- block-size: 0.5em;
270
- background: var(--accent);
271
- }
224
+ li > :is(ul, ol) { margin-block: var(--space-2) 0; }
272
225
 
273
- /* Definition lists */
274
- dl {
275
- margin-block-end: var(--space-5);
276
- }
226
+ /* A term in the label voice (above), its value flush under it. */
227
+ dl { margin-block: 0 var(--space-5); }
277
228
 
278
- dt {
279
- font-weight: var(--weight-semibold);
280
- margin-block-end: var(--space-1);
281
- }
229
+ dt { margin-block-end: var(--space-1); }
282
230
 
283
231
  dd {
284
- margin-inline-start: var(--space-6);
232
+ margin-inline-start: 0;
285
233
  margin-block-end: var(--space-4);
286
- color: var(--color-text-muted);
287
234
  }
288
235
 
289
236
 
290
237
  /* ========================================
291
- BLOCKQUOTES
292
- Thoughtful, set apart
238
+ QUOTES
293
239
  ======================================== */
294
240
 
295
241
  blockquote {
296
- position: relative;
297
- padding-inline-start: var(--space-6);
298
- margin-block: var(--space-6);
299
- border-inline-start: 3px solid var(--color-border);
300
- color: var(--color-text-muted);
242
+ margin-inline: 0;
243
+ margin-block: var(--space-l);
244
+ padding-inline-start: 1.25em;
245
+ border-inline-start: var(--border-width-thick) solid var(--accent);
246
+ font-family: var(--font-heading);
247
+ font-size: var(--step-1);
301
248
  font-style: italic;
302
- }
303
-
304
- blockquote::before {
305
- content: none; /* No decorative quotes - let content speak */
249
+ line-height: 1.4;
306
250
  }
307
251
 
308
252
  blockquote cite {
309
253
  display: block;
310
- margin-block-start: var(--space-3);
311
- font-size: var(--font-size-sm);
312
- font-style: normal;
313
- color: var(--color-text-subtle);
254
+ margin-block-start: var(--space-s);
314
255
  }
315
256
 
316
- blockquote cite::before {
317
- content: "— ";
257
+ /* A pull quote repeats the text, so it is a <p class="pullquote">, not
258
+ an <aside> (a complementary landmark nested in the article) and not a
259
+ <blockquote> (it quotes no one else). */
260
+ .pullquote {
261
+ margin-inline: 0;
262
+ margin-block: var(--space-l);
263
+ padding-block: var(--space-m);
264
+ border-block: var(--border-width) solid var(--color-border-strong);
265
+ font-family: var(--font-heading);
266
+ font-size: var(--step-3);
267
+ font-weight: calc(var(--weight-light) + var(--_dark-weight));
268
+ line-height: var(--leading-heading);
269
+ letter-spacing: var(--tracking-heading);
270
+ text-wrap: balance;
318
271
  }
319
272
 
320
273
 
321
274
  /* ========================================
322
275
  CODE
323
- Technical but approachable
324
276
  ======================================== */
325
277
 
278
+ :is(code, kbd, samp, pre) { font-family: var(--font-mono); }
279
+
326
280
  code {
327
- font-family: var(--font-mono);
328
281
  font-size: 0.9em;
329
282
  background-color: var(--color-surface);
330
- padding-inline: var(--space-1-5);
331
- padding-block: var(--space-0-5);
332
- border-radius: var(--radius-sm);
283
+ padding: 0.1em 0.3em;
284
+ border-radius: var(--radius-control);
333
285
  }
334
286
 
335
287
  pre {
336
- font-family: var(--font-mono);
337
- font-size: var(--font-size-sm);
288
+ font-size: var(--step--1);
338
289
  line-height: var(--leading-relaxed);
339
290
  background-color: var(--color-surface);
340
- padding: var(--space-5);
341
- border-radius: var(--radius-lg);
291
+ border: var(--border-width) solid var(--color-border);
292
+ border-radius: var(--radius-card);
293
+ padding: var(--space-m);
342
294
  overflow-x: auto;
343
- margin-block-end: var(--space-5);
295
+ margin-block: 0 var(--space-5);
344
296
  tab-size: 2;
345
297
  }
346
298
 
@@ -348,22 +300,13 @@
348
300
  background: none;
349
301
  padding: 0;
350
302
  font-size: inherit;
351
- border-radius: 0;
352
303
  }
353
304
 
354
305
  kbd {
355
- font-family: var(--font-mono);
356
306
  font-size: 0.875em;
357
- background-color: var(--color-surface-raised);
358
- border: 1px solid var(--color-border);
359
- border-radius: var(--radius-sm);
360
- padding-inline: var(--space-1-5);
361
- padding-block: var(--space-0-5);
362
- box-shadow: var(--shadow-xs);
363
- }
364
-
365
- samp {
366
- font-family: var(--font-mono);
307
+ border: var(--border-width) solid var(--color-border-strong);
308
+ border-radius: var(--radius-control);
309
+ padding: 0.1em 0.35em;
367
310
  }
368
311
 
369
312
  var {
@@ -373,82 +316,151 @@
373
316
 
374
317
 
375
318
  /* ========================================
376
- HORIZONTAL RULES
377
- Quiet separation
319
+ RULES
378
320
  ======================================== */
379
321
 
380
322
  hr {
381
323
  border: none;
382
- border-block-start: 1px solid var(--color-border-subtle);
383
- margin-block: var(--space-10);
324
+ border-block-start: var(--border-width) solid var(--color-border);
325
+ max-inline-size: var(--measure);
326
+ margin-block: var(--space-xl);
327
+ }
328
+
329
+ hr[data-variant="ornament"] {
330
+ border: 0;
331
+ text-align: center;
332
+ overflow: visible;
333
+ }
334
+
335
+ /* Three asterisks in the heading face: the asterism glyph (⁂) is not in
336
+ the latin subset of the fonts file, so it fell back to another face. */
337
+ hr[data-variant="ornament"]::before {
338
+ content: "*\2003*\2003*";
339
+ font: var(--step-1) / 1 var(--font-heading);
340
+ color: var(--color-text-muted);
384
341
  }
385
342
 
386
343
 
387
344
  /* ========================================
388
345
  MEDIA
389
- Graceful presentation
346
+ Images in true colour at their natural ratio. data-aspect crops any
347
+ img or video; data-focus (on it or a wrapper) sets the crop's focal point.
390
348
  ======================================== */
391
349
 
392
- figure {
393
- margin-block: var(--space-6);
350
+ :is(img, video) { object-position: var(--focus, 50% 50%); }
351
+
352
+ [data-aspect="square"] { --_aspect: 1; }
353
+ [data-aspect="video"] { --_aspect: 16 / 9; }
354
+ [data-aspect="portrait"] { --_aspect: 3 / 4; }
355
+ [data-aspect="wide"] { --_aspect: 21 / 9; }
356
+
357
+ :is(img, video)[data-aspect] {
358
+ aspect-ratio: var(--_aspect, auto);
359
+ object-fit: cover;
360
+ }
361
+
362
+ [data-focus="top"] { --focus: 50% 0; }
363
+ [data-focus="bottom"] { --focus: 50% 100%; }
364
+ [data-focus="left"] { --focus: 0 50%; }
365
+ [data-focus="right"] { --focus: 100% 50%; }
366
+ [data-focus="center"] { --focus: 50% 50%; }
367
+
368
+ figure { margin: 0; margin-block: var(--space-l); }
369
+
370
+ figure :is(img, picture, video) {
371
+ inline-size: 100%;
372
+ border-radius: var(--radius-card);
373
+ }
374
+
375
+ /* An uncropped image is never taller than the screen: a portrait at the
376
+ column's full width ran two screens tall. The box keeps the column's
377
+ width and the width/height attributes' ratio, so a lazy image holds
378
+ its space before it loads (inline-size: auto made it 0 × 0); a
379
+ portrait capped at 85svh is fitted inside the box at the start edge,
380
+ never squashed. Full-bleed figures and the hero plate are not capped:
381
+ they are the full width at their natural ratio. */
382
+ figure:not([data-bleed="full"], .alignfull, .hero__plate) > :is(img, video):not([data-aspect]),
383
+ figure:not([data-bleed="full"], .alignfull, .hero__plate) > picture > img:not([data-aspect]) {
384
+ block-size: auto;
385
+ max-block-size: 85svh;
386
+ object-fit: contain;
387
+ object-position: var(--focus, 0% 50%);
394
388
  }
395
389
 
396
390
  figcaption {
397
- margin-block-start: var(--space-3);
398
- font-size: var(--font-size-sm);
399
- color: var(--color-text-muted);
400
- text-align: center;
391
+ margin-block-start: var(--space-xs);
392
+ max-inline-size: 48ch;
393
+ }
394
+
395
+ /* A credit in <small>, a plate number in .plate or b, a work title in <i>. */
396
+ figcaption :is(.plate, b) {
397
+ margin-inline-end: 0.75em;
398
+ }
399
+
400
+ figcaption small { font-size: inherit; }
401
+
402
+ @container (inline-size > 60rem) {
403
+ figure[data-caption="side"] {
404
+ display: grid;
405
+ grid-template-columns: 1fr 16rem;
406
+ column-gap: var(--space-m);
407
+ align-items: end;
408
+ }
401
409
  }
402
410
 
403
411
 
404
412
  /* ========================================
405
413
  TABLES
406
- Clean, readable data
414
+ Label-voice head over a 2px ink rule, hairline rows, tabular figures.
407
415
  ======================================== */
408
416
 
417
+ /* Lining but proportional: tabular figures also widen the comma, which
418
+ gapped prose cells ("Boston , MA"). Numeric cells opt in below. */
409
419
  table {
410
420
  inline-size: 100%;
411
- margin-block-end: var(--space-5);
421
+ margin-block: 0 var(--space-5);
412
422
  border-collapse: collapse;
413
- font-variant-numeric: tabular-nums;
423
+ font-size: var(--step--1);
424
+ font-variant-numeric: lining-nums proportional-nums;
414
425
  }
415
426
 
416
- th, td {
417
- padding: var(--space-3) var(--space-4);
427
+ :is(th, td) {
428
+ padding-block: 0.85em;
429
+ padding-inline: 0 var(--space-4);
418
430
  text-align: start;
419
- border-block-end: 1px solid var(--color-border-subtle);
431
+ border-block-end: var(--border-width) solid var(--color-border);
420
432
  }
421
433
 
422
- th {
423
- font-weight: var(--weight-semibold);
424
- color: var(--color-text);
425
- background-color: var(--color-surface);
434
+ :is(td, th):is([data-type="number"], .num) {
435
+ font-variant-numeric: lining-nums tabular-nums;
436
+ text-align: end;
426
437
  }
427
438
 
428
- /* Striped rows - subtle */
429
- tbody tr:nth-child(even) {
430
- background-color: oklch(from var(--shadow-color) l c h / 0.02);
431
- }
439
+ th { font-weight: calc(var(--weight-semibold) + var(--_dark-weight)); }
432
440
 
433
- /* Hover state - gentle highlight */
434
- tbody tr {
435
- transition: background-color var(--duration-normal) var(--ease-fluid);
441
+ thead th {
442
+ vertical-align: bottom;
443
+ border-block-end: var(--border-width-thick) solid var(--color-border-strong);
436
444
  }
437
445
 
438
- tbody tr:hover {
439
- background-color: oklch(from var(--shadow-color) l c h / 0.04);
446
+ caption {
447
+ caption-side: top;
448
+ text-align: start;
449
+ margin-block-end: var(--space-xs);
440
450
  }
441
451
 
442
452
 
443
453
  /* ========================================
444
- FORMS - Inputs
445
- Inviting, not intimidating
454
+ FORMS
455
+ Square fields on the page colour with an ink edge, 3rem tall, body-size
456
+ text (which also stops iOS zooming on focus). Focus is an outline, so an
457
+ invalid field keeps its error border and still shows focus.
446
458
  ======================================== */
447
459
 
448
460
  label {
449
461
  display: block;
450
- font-size: var(--font-size-sm);
451
- font-weight: var(--weight-medium);
462
+ font-size: var(--step--1);
463
+ font-weight: calc(var(--weight-semibold) + var(--_dark-weight));
452
464
  margin-block-end: var(--space-2);
453
465
  color: var(--color-text);
454
466
  }
@@ -457,195 +469,149 @@
457
469
  textarea,
458
470
  select {
459
471
  inline-size: 100%;
460
- padding: var(--space-3) var(--space-4);
461
- font-size: var(--font-size-base);
462
- line-height: var(--leading-normal);
472
+ min-block-size: 3rem;
473
+ padding: 0.6em 0.8em;
474
+ font: inherit;
475
+ font-size: var(--step-0);
476
+ line-height: 1.4;
463
477
  color: var(--color-text);
464
478
  background-color: var(--color-background);
465
- border: 1px solid var(--color-border);
466
- border-radius: var(--radius-input, var(--radius-md));
467
- /* Subtle 3D effect - layered shadows create depth */
468
- box-shadow:
469
- inset 0 1px 2px oklch(from var(--shadow-color) l c h / 0.04),
470
- 0 1px 0 oklch(from var(--theme-light) l c h / 0.6);
471
- transition:
472
- border-color var(--duration-normal) var(--ease-fluid),
473
- box-shadow var(--duration-normal) var(--ease-fluid),
474
- background-color var(--duration-normal) var(--ease-fluid);
475
- }
476
-
477
- input:hover,
478
- textarea:hover,
479
- select:hover {
480
- border-color: var(--color-text-subtle);
481
- }
482
-
483
- input:focus,
484
- textarea:focus,
485
- select:focus {
486
- outline: 2px solid transparent;
487
- border-color: var(--accent);
488
- /* Focus: glow ring + maintain subtle 3D */
489
- box-shadow:
490
- var(--shadow-glow),
491
- inset 0 1px 2px oklch(from var(--shadow-color) l c h / 0.03);
492
- /* Soft focus background - subtle tint */
493
- background-color: color-mix(in oklch, var(--accent) 8%, var(--color-background));
494
- }
495
-
496
- /* Dark mode: stronger tint needed for visibility */
497
- @media (prefers-color-scheme: dark) {
498
- input:focus,
499
- textarea:focus,
500
- select:focus {
501
- background-color: color-mix(in oklch, var(--accent) 15%, var(--color-background));
502
- }
479
+ border: var(--border-width) solid var(--color-border-strong);
480
+ border-radius: var(--radius-control);
481
+ transition: border-color var(--duration-fast) var(--ease-fluid);
503
482
  }
504
483
 
505
- [data-theme="dark"] input:focus,
506
- [data-theme="dark"] textarea:focus,
507
- [data-theme="dark"] select:focus {
508
- background-color: color-mix(in oklch, var(--accent) 15%, var(--color-background));
484
+ :is(input, textarea, select):focus {
485
+ outline: var(--focus-ring-width) solid var(--color-focus);
486
+ outline-offset: 1px;
509
487
  }
510
488
 
511
- input::placeholder,
512
- textarea::placeholder {
489
+ :is(input, textarea)::placeholder {
513
490
  color: var(--color-text-subtle);
514
491
  opacity: 1;
515
492
  }
516
493
 
517
- /* Disabled state */
518
- input:disabled,
519
- textarea:disabled,
520
- select:disabled {
521
- opacity: 0.6;
494
+ :is(input, textarea, select):disabled {
495
+ opacity: 0.5;
522
496
  cursor: not-allowed;
523
- background-color: var(--color-surface);
524
497
  }
525
498
 
526
- textarea {
527
- min-block-size: 8rem;
528
- resize: vertical;
499
+ /* Bare checkboxes and radios keep a box shape: the text-field rules above
500
+ (full width, padding, border) would turn <label><input type="checkbox">
501
+ Text</label> into a full-width bar. Same weight as `input`, so the custom
502
+ .checkbox / .radio / .switch inputs (one class) still win. */
503
+ input:where([type="checkbox"], [type="radio"]) {
504
+ inline-size: 1.25rem;
505
+ block-size: 1.25rem;
506
+ min-block-size: 0;
507
+ padding: 0;
508
+ margin: 0;
509
+ margin-inline-end: var(--space-2);
510
+ vertical-align: -0.2em;
511
+ border: 0;
512
+ accent-color: var(--accent);
513
+ cursor: pointer;
529
514
  }
530
515
 
516
+ input:where([type="checkbox"], [type="radio"]):focus { outline: 0; }
531
517
 
532
- /* ========================================
533
- FORMS - Buttons
534
- Clear call to action, never garish
535
- ======================================== */
518
+ input:where([type="checkbox"], [type="radio"]):focus-visible {
519
+ outline: var(--focus-ring-width) solid var(--color-focus);
520
+ outline-offset: 2px;
521
+ }
536
522
 
537
- button {
538
- display: inline-flex;
539
- align-items: center;
540
- justify-content: center;
541
- gap: var(--space-2);
542
- padding: var(--space-3) var(--space-5);
543
- font-size: var(--font-size-base);
544
- font-weight: var(--weight-medium);
545
- line-height: var(--leading-none);
546
- color: var(--white);
547
- background-color: var(--accent);
548
- border: none;
549
- border-radius: var(--radius-md);
550
- cursor: pointer;
551
- transition:
552
- background-color var(--duration-normal) var(--ease-fluid),
553
- transform var(--duration-fast) var(--ease-settle),
554
- box-shadow var(--duration-normal) var(--ease-fluid);
523
+ /* Date and time boxes take their cell's width. iOS Safari sizes a native
524
+ date/time input to its own content and ignores inline-size, so in a grid
525
+ it ran into the next column (#65). Without the native appearance it
526
+ behaves like a text field. */
527
+ input:where([type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"]) {
528
+ display: block;
529
+ min-inline-size: 0;
530
+ max-inline-size: 100%;
531
+ -webkit-appearance: none;
532
+ appearance: none;
555
533
  }
556
534
 
557
- button:hover {
558
- background-color: var(--accent-hover);
559
- box-shadow: var(--shadow-sm);
535
+ input:where([type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"])::-webkit-date-and-time-value {
536
+ text-align: start;
560
537
  }
561
538
 
562
- button:active {
563
- transform: scale(0.98);
564
- box-shadow: none;
539
+ textarea {
540
+ min-block-size: 8rem;
541
+ resize: vertical;
565
542
  }
566
543
 
567
- button:disabled {
568
- opacity: 0.6;
569
- cursor: not-allowed;
544
+ /* Fieldset: no box, a top hairline, the legend in the label voice. */
545
+ fieldset {
546
+ margin: 0;
547
+ margin-block-end: var(--space-l);
548
+ padding: 0;
549
+ padding-block-start: var(--space-s);
550
+ border: 0;
551
+ border-block-start: var(--border-width) solid var(--color-border);
552
+ min-inline-size: 0;
570
553
  }
571
554
 
572
- button:disabled:hover {
573
- background-color: var(--accent);
574
- box-shadow: none;
575
- transform: none;
555
+ legend {
556
+ float: inline-start;
557
+ inline-size: 100%;
558
+ padding: 0;
559
+ margin-block-end: var(--space-m);
576
560
  }
577
561
 
562
+ legend + * { clear: both; }
563
+
578
564
 
579
565
  /* ========================================
580
- INTERACTIVE ELEMENTS
581
- Native HTML interactivity
566
+ DETAILS
582
567
  ======================================== */
583
568
 
584
569
  details {
585
- margin-block-end: var(--space-4);
586
- border: 1px solid var(--color-border-subtle);
587
- border-radius: var(--radius-md);
588
- padding: var(--space-4);
570
+ margin-block: 0 var(--space-4);
571
+ border-block: var(--border-width) solid var(--color-border);
589
572
  }
590
573
 
574
+ details + details { border-block-start: 0; margin-block-start: calc(-1 * var(--space-4)); }
575
+
591
576
  summary {
592
- font-weight: var(--weight-medium);
577
+ display: flex;
578
+ flex-wrap: wrap;
579
+ align-items: center;
580
+ gap: 0 var(--space-2);
581
+ min-block-size: 2.75rem;
582
+ padding-block: var(--space-2);
583
+ font-weight: calc(var(--weight-semibold) + var(--_dark-weight));
593
584
  cursor: pointer;
594
- padding: var(--space-2);
595
- margin: calc(var(--space-4) * -1);
596
- margin-block-end: 0;
597
- border-radius: var(--radius-md);
598
- transition: background-color var(--duration-normal) var(--ease-fluid);
599
585
  }
600
586
 
601
- summary:hover {
602
- background-color: var(--color-surface);
603
- }
587
+ details[open] > summary { margin-block-end: var(--space-s); }
604
588
 
605
- details[open] summary {
606
- margin-block-end: var(--space-4);
607
- border-block-end: 1px solid var(--color-border-subtle);
608
- border-end-start-radius: 0;
609
- border-end-end-radius: 0;
610
- }
611
-
612
- /* Hide default marker in webkit */
613
- summary::-webkit-details-marker {
614
- display: none;
615
- }
616
-
617
- summary::marker {
618
- content: "";
619
- }
589
+ summary::-webkit-details-marker { display: none; }
590
+ summary::marker { content: ""; }
620
591
 
592
+ /* The chevron is decoration: empty alt text keeps it out of the
593
+ summary's accessible name (and so out of a landmark's name). */
621
594
  summary::before {
622
595
  content: "▸";
596
+ content: "▸" / "";
623
597
  display: inline-block;
624
- margin-inline-end: var(--space-2);
625
- transition: transform var(--duration-normal) var(--ease-settle);
598
+ transition: rotate var(--duration-fast) var(--ease-settle);
626
599
  }
627
600
 
628
- details[open] summary::before {
629
- transform: rotate(90deg);
630
- }
601
+ /* Logical: the marker points along the inline direction and turns down. */
602
+ summary:dir(rtl)::before { scale: -1 1; }
603
+ details[open] > summary::before { rotate: 90deg; }
604
+ details[open] > summary:dir(rtl)::before { rotate: -90deg; }
631
605
 
632
606
 
633
607
  /* ========================================
634
608
  SEMANTIC ELEMENTS
635
- Meaningful markup, meaningful styles
636
609
  ======================================== */
637
610
 
638
611
  mark {
639
- background-color: oklch(90% 0.08 90);
612
+ background-color: light-dark(oklch(90% 0.08 90), oklch(40% 0.08 90));
613
+ color: var(--color-text);
640
614
  padding-inline: var(--space-1);
641
- border-radius: var(--radius-sm);
642
- }
643
-
644
- @media (prefers-color-scheme: dark) {
645
- mark {
646
- background-color: oklch(40% 0.08 90);
647
- color: oklch(95% 0.02 90);
648
- }
649
615
  }
650
616
 
651
617
  abbr[title] {
@@ -658,96 +624,49 @@
658
624
  margin-block-end: var(--space-5);
659
625
  }
660
626
 
661
- time {
662
- font-variant-numeric: tabular-nums;
663
- }
627
+ time { font-variant-numeric: lining-nums; }
664
628
 
665
629
 
666
630
  /* ========================================
667
631
  METER & PROGRESS
668
- Visual feedback with viewport animation
669
632
  ======================================== */
670
633
 
671
634
  meter,
672
635
  progress {
673
636
  inline-size: 100%;
674
637
  block-size: var(--space-2);
675
- border-radius: var(--radius-full);
676
638
  overflow: hidden;
677
639
  }
678
640
 
679
641
  progress {
680
642
  appearance: none;
643
+ border: 0;
681
644
  background-color: var(--color-surface);
682
645
  }
683
646
 
684
- progress::-webkit-progress-bar {
685
- background-color: var(--color-surface);
686
- border-radius: var(--radius-full);
687
- }
688
-
689
- progress::-webkit-progress-value {
690
- background-color: var(--accent);
691
- border-radius: var(--radius-full);
692
- /* Animate to draw attention when visible */
693
- transition: inline-size var(--duration-slow) var(--ease-fluid);
694
- }
695
-
696
- progress::-moz-progress-bar {
697
- background-color: var(--accent);
698
- border-radius: var(--radius-full);
699
- transition: inline-size var(--duration-slow) var(--ease-fluid);
700
- }
701
-
702
- /* Animated progress - starts at 0 and fills to value when in viewport */
703
- progress[data-animate] {
704
- --progress-value: 73; /* Default: NSW easter egg */
705
- }
706
-
707
- progress[data-animate]::-webkit-progress-value {
708
- animation: progress-fill var(--duration-slower) var(--ease-fluid) forwards;
709
- animation-play-state: paused;
710
- }
711
-
712
- progress[data-animate][data-visible]::-webkit-progress-value {
713
- animation-play-state: running;
714
- }
715
-
716
- @keyframes progress-fill {
717
- from { inline-size: 0; }
718
- to { inline-size: 100%; }
719
- }
720
-
721
- @media (prefers-reduced-motion: reduce) {
722
- progress[data-animate]::-webkit-progress-value {
723
- animation: none;
724
- }
725
- }
647
+ progress::-webkit-progress-bar { background-color: var(--color-surface); }
648
+ progress::-webkit-progress-value { background-color: var(--accent); }
649
+ progress::-moz-progress-bar { background-color: var(--accent); }
726
650
 
727
651
 
728
652
  /* ========================================
729
653
  DIALOG
730
- Modal overlay styling
731
654
  ======================================== */
732
655
 
733
656
  dialog {
734
657
  max-inline-size: min(90vw, var(--container-sm));
735
658
  padding: var(--space-6);
736
- border: 1px solid var(--color-border-subtle);
737
- border-radius: var(--radius-xl);
738
- box-shadow: var(--shadow-xl);
659
+ color: var(--color-text);
739
660
  background-color: var(--color-background);
661
+ border: var(--border-width) solid var(--color-border-strong);
662
+ border-radius: var(--radius-card);
740
663
  }
741
664
 
742
- dialog::backdrop {
743
- background-color: oklch(from var(--shadow-color) l c h / 0.5);
744
- backdrop-filter: blur(4px);
745
- }
665
+ dialog::backdrop { background-color: oklch(0 0 0 / 0.5); }
746
666
 
747
667
 
748
668
  /* ========================================
749
669
  SKIP LINK
750
- Accessibility: Jump to main content
751
670
  Hidden until focused for keyboard users
752
671
  ======================================== */
753
672
 
@@ -758,77 +677,24 @@
758
677
  z-index: var(--z-max);
759
678
  padding: var(--space-3) var(--space-5);
760
679
  background-color: var(--accent);
761
- color: var(--white);
762
- font-weight: var(--weight-medium);
680
+ color: var(--on-accent);
681
+ font-weight: var(--weight-semibold);
763
682
  text-decoration: none;
764
- border-radius: var(--radius-md);
765
- box-shadow: var(--shadow-lg);
766
- transform: translateY(-200%);
767
- transition: transform var(--duration-fast) var(--ease-fluid);
683
+ translate: 0 -200%;
768
684
  }
769
685
 
770
686
  .skip-link:focus {
771
- transform: translateY(0);
772
- outline: 2px solid transparent;
773
- box-shadow:
774
- var(--shadow-lg),
775
- 0 0 0 3px var(--color-background),
776
- 0 0 0 5px var(--accent);
687
+ translate: none;
688
+ outline: var(--focus-ring-width) solid var(--color-focus);
689
+ outline-offset: 2px;
690
+ /* A halo in the page colour: the link lands on the nav bar, which is
691
+ the page's other scheme. */
692
+ box-shadow: 0 0 0 calc(2px + var(--focus-ring-width)) var(--color-background);
777
693
  }
778
694
 
779
695
 
780
696
  /* ========================================
781
- THEME-SPECIFIC TYPOGRAPHY
782
- Motion styles affect text presentation
783
- ======================================== */
784
-
785
- /* Still: Clean, precise, Swiss-style typography */
786
- [data-motion="still"] h1,
787
- [data-motion="still"] h2,
788
- [data-motion="still"] h3 {
789
- font-weight: var(--weight-medium);
790
- letter-spacing: 0.02em;
791
- text-transform: uppercase;
792
- }
793
-
794
- /* Serene: Elegant, classical typography */
795
- [data-motion="serene"] h1,
796
- [data-motion="serene"] h2 {
797
- font-weight: var(--weight-normal);
798
- letter-spacing: 0;
799
- font-style: italic;
800
- }
801
-
802
- /* Rapids: Bold, italic, aggressive sports feel */
803
- [data-motion="rapids"] h1,
804
- [data-motion="rapids"] h2,
805
- [data-motion="rapids"] h3 {
806
- font-weight: 900;
807
- font-style: italic;
808
- text-transform: uppercase;
809
- letter-spacing: -0.02em;
810
- line-height: 0.95;
811
- }
812
- [data-motion="rapids"] h1 {
813
- font-size: clamp(var(--font-size-3xl), 8vw, var(--font-size-5xl));
814
- }
815
-
816
- /* Tsunami: Maximum impact, dramatic typography */
817
- [data-motion="tsunami"] h1,
818
- [data-motion="tsunami"] h2 {
819
- font-weight: 900;
820
- text-transform: uppercase;
821
- letter-spacing: -0.03em;
822
- line-height: 0.9;
823
- }
824
- [data-motion="tsunami"] h1 {
825
- font-size: clamp(var(--font-size-4xl), 10vw, 6rem);
826
- }
827
-
828
-
829
- /* ========================================
830
- PRINT STYLES
831
- Clean output for paper
697
+ PRINT
832
698
  ======================================== */
833
699
 
834
700
  @media print {
@@ -837,48 +703,28 @@
837
703
  line-height: 1.5;
838
704
  }
839
705
 
840
- h1, h2, h3, h4, h5, h6 {
841
- page-break-after: avoid;
842
- break-after: avoid;
843
- }
706
+ h1, h2, h3, h4, h5, h6 { break-after: avoid; }
844
707
 
845
- p, blockquote, ul, ol, table {
846
- page-break-inside: avoid;
847
- break-inside: avoid;
848
- }
708
+ p, blockquote, ul, ol, table { break-inside: avoid; }
849
709
 
850
- a {
851
- color: inherit;
852
- text-decoration: underline;
853
- }
710
+ a { color: inherit; }
854
711
 
855
- /* Show URLs for external links */
856
712
  a[href^="http"]::after {
857
713
  content: " (" attr(href) ")";
858
714
  font-size: 0.8em;
859
715
  color: var(--color-text-muted);
860
716
  }
861
717
 
862
- pre, code {
863
- background-color: var(--color-surface-raised, #f5f5f5);
864
- }
865
-
866
- /* Hide overlays and fixed UI on print */
867
718
  .modal,
868
719
  .toast-container,
869
- .settings-fab,
870
- .settings-drawer,
871
- .scroll-progress,
872
720
  .nav {
873
721
  display: none !important;
874
722
  }
875
723
 
876
- /* Preserve badge/alert colors on print */
877
724
  .badge,
878
725
  .alert {
879
- -webkit-print-color-adjust: exact;
880
726
  print-color-adjust: exact;
881
- border: 1px solid currentColor;
727
+ border: var(--border-width) solid currentColor;
882
728
  }
883
729
  }
884
730
  }