@gemboss/website-ui 0.0.0-stage → 0.2.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 (44) hide show
  1. package/AGENTS.md +191 -0
  2. package/CHANGELOG.md +47 -0
  3. package/README.md +118 -2
  4. package/bin/gemboss-website.mjs +78 -0
  5. package/bin/lib/check.mjs +695 -0
  6. package/bin/lib/gap.mjs +189 -0
  7. package/bin/lib/init.mjs +99 -0
  8. package/bin/lib/upgrade.mjs +155 -0
  9. package/bin/lib/util.mjs +147 -0
  10. package/css/base.css +107 -0
  11. package/css/components.css +324 -0
  12. package/css/footer.css +53 -0
  13. package/css/header.css +231 -0
  14. package/css/layout.css +59 -0
  15. package/css/prose.css +119 -0
  16. package/css/sections.css +173 -0
  17. package/css/tokens.css +200 -0
  18. package/demo/index.html +703 -0
  19. package/demo/theme-example.html +133 -0
  20. package/docs/components.md +407 -0
  21. package/docs/markup-contract.md +148 -0
  22. package/docs/philosophy.md +69 -0
  23. package/docs/theming.md +96 -0
  24. package/js/website.js +186 -0
  25. package/package.json +50 -4
  26. package/skill/SKILL.md +49 -0
  27. package/system.json +122 -0
  28. package/templates/footer.html +40 -0
  29. package/templates/header.html +60 -0
  30. package/templates/page.html +134 -0
  31. package/templates/sections/cta.html +15 -0
  32. package/templates/sections/faq.html +24 -0
  33. package/templates/sections/features.html +32 -0
  34. package/templates/sections/hero.html +27 -0
  35. package/templates/sections/logos.html +15 -0
  36. package/templates/sections/pricing.html +51 -0
  37. package/templates/sections/prose.html +36 -0
  38. package/templates/sections/quote.html +13 -0
  39. package/templates/sections/showcase.html +25 -0
  40. package/templates/sections/split.html +27 -0
  41. package/templates/sections/stats.html +26 -0
  42. package/templates/sections/steps.html +17 -0
  43. package/website.css +1274 -0
  44. package/website.min.css +1 -0
package/css/base.css ADDED
@@ -0,0 +1,107 @@
1
+ /* base: reset, type, links, focus, skip link, reveal. Colours, radii and shadows only from tokens. */
2
+ *, *::before, *::after { box-sizing: border-box; }
3
+ [hidden] { display: none !important; }
4
+ html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
5
+ body {
6
+ margin: 0;
7
+ background: var(--gw-bg);
8
+ color: var(--gw-ink);
9
+ font-family: var(--gw-font);
10
+ font-size: var(--gw-body-size);
11
+ line-height: var(--gw-body-leading);
12
+ -webkit-font-smoothing: antialiased;
13
+ text-rendering: optimizeLegibility;
14
+ }
15
+ :where(h1, h2, h3, h4, h5, h6, p, ul, ol, dl, dd, figure, blockquote) { margin: 0; }
16
+ img, svg, video { display: block; max-width: 100%; }
17
+ img, video { height: auto; }
18
+ button, input, select, textarea { font: inherit; color: inherit; }
19
+ button { cursor: pointer; }
20
+ a { color: inherit; text-decoration: none; }
21
+ b, strong { font-weight: 600; }
22
+
23
+ /* the page: the footer stays at the bottom of a short page */
24
+ .gw-page { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }
25
+ .gw-page > main { flex: 1 0 auto; }
26
+
27
+ /* headings: h1/h2 are display (fluid), h3 and below are the heading scale */
28
+ h1, h2, h3, h4, h5, h6, .gw-h1, .gw-h2, .gw-h3, .gw-h4 {
29
+ font-weight: var(--gw-heading-weight);
30
+ letter-spacing: var(--gw-heading-tracking);
31
+ line-height: var(--gw-heading-leading);
32
+ text-wrap: balance;
33
+ }
34
+ h1, .gw-h1, h2, .gw-h2 { font-weight: var(--gw-display-weight); letter-spacing: var(--gw-display-tracking); }
35
+ h1, .gw-h1 { font-size: var(--gw-display-h1); }
36
+ h2, .gw-h2 { font-size: var(--gw-display-h2); }
37
+ h3, .gw-h3 { font-size: 24px; }
38
+ h4, .gw-h4 { font-size: 20px; font-weight: 600; letter-spacing: -0.01em; }
39
+ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
40
+
41
+ .gw-lead { font-size: 18px; line-height: 1.55; color: var(--gw-muted); max-width: 60ch; }
42
+ .gw-note { font-size: 14px; color: var(--gw-muted); }
43
+ .gw-dim { color: var(--gw-muted); }
44
+
45
+ /* the label above a heading: sentence case, muted, led by the 8px accent square */
46
+ .gw-label {
47
+ display: inline-flex;
48
+ align-items: center;
49
+ gap: 8px;
50
+ font-size: 13px;
51
+ font-weight: 500;
52
+ line-height: 1.4;
53
+ color: var(--gw-muted);
54
+ }
55
+ .gw-label::before {
56
+ content: "";
57
+ flex: none;
58
+ width: 8px;
59
+ height: 8px;
60
+ border-radius: var(--gw-radius-0);
61
+ background: var(--gw-accent);
62
+ }
63
+
64
+ /* links inside running text (and anywhere in .gw-prose) get a visible link style; everything else opts in with .gw-link */
65
+ :where(p, dd, td, figcaption, .gw-list--check li, .gw-prose) a:not([class]),
66
+ .gw-link {
67
+ color: var(--gw-accent-strong);
68
+ font-weight: 500;
69
+ text-decoration: underline;
70
+ text-decoration-thickness: 1px;
71
+ text-underline-offset: 4px;
72
+ text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
73
+ }
74
+ :where(p, dd, td, figcaption, .gw-list--check li, .gw-prose) a:not([class]):hover,
75
+ .gw-link:hover { text-decoration-color: currentColor; }
76
+
77
+ :focus-visible { outline: 2px solid var(--gw-accent); outline-offset: 2px; }
78
+ [id] { scroll-margin-top: calc(var(--gw-header-height) + 16px); }
79
+
80
+ .gw-skip {
81
+ position: absolute;
82
+ left: -9999px;
83
+ top: 8px;
84
+ z-index: 100;
85
+ padding: 12px 16px;
86
+ border-radius: var(--gw-radius-action);
87
+ background: var(--gw-action);
88
+ color: var(--gw-on-action);
89
+ font-size: 14px;
90
+ font-weight: 600;
91
+ }
92
+ .gw-skip:focus { left: 16px; }
93
+
94
+ /* scroll reveal: the script adds .is-in; the animation is a failsafe that shows it anyway after 2.5s */
95
+ .gw-reveal {
96
+ opacity: 0;
97
+ transform: translateY(24px);
98
+ transition: opacity var(--gw-reveal-ms) var(--gw-ease), transform var(--gw-reveal-ms) var(--gw-ease);
99
+ animation: gw-reveal-failsafe 1ms linear 2.5s forwards;
100
+ }
101
+ .gw-reveal.is-in { opacity: 1; transform: none; }
102
+ @keyframes gw-reveal-failsafe { to { opacity: 1; transform: none; } }
103
+
104
+ @media (prefers-reduced-motion: reduce) {
105
+ *, *::before, *::after { scroll-behavior: auto !important; }
106
+ .gw-reveal { opacity: 1; transform: none; transition: none; animation: none; }
107
+ }
@@ -0,0 +1,324 @@
1
+ /* components: buttons, links, chips, badges, cards, windows, icons, lists, tables, FAQ. */
2
+
3
+ /* actions are rectangles: 40px, radius 8, 14px/600, sentence case, ink fill (never the accent) */
4
+ .gw-btn {
5
+ position: relative;
6
+ display: inline-flex;
7
+ align-items: center;
8
+ justify-content: center;
9
+ gap: 8px;
10
+ max-width: 100%;
11
+ height: var(--gw-button-height);
12
+ padding: 0 20px;
13
+ border: 1px solid transparent;
14
+ border-radius: var(--gw-radius-action);
15
+ background: var(--gw-action);
16
+ color: var(--gw-on-action);
17
+ font-size: 14px;
18
+ font-weight: 600;
19
+ line-height: 1.4;
20
+ letter-spacing: 0;
21
+ text-align: center;
22
+ white-space: nowrap;
23
+ cursor: pointer;
24
+ }
25
+ .gw-btn:hover { background: var(--gw-action-hover); }
26
+ .gw-btn svg { flex: none; width: 16px; height: 16px; }
27
+ .gw-btn--secondary { background: transparent; border-color: var(--gw-line-strong); color: var(--gw-ink); }
28
+ .gw-btn--secondary:hover { background: transparent; border-color: var(--gw-ink); }
29
+ .gw-btn--ghost { background: transparent; color: var(--gw-ink); }
30
+ .gw-btn--ghost:hover { background: var(--gw-bg-soft); }
31
+ .gw-btn--on-night { background: var(--gw-on-night); color: var(--gw-night); }
32
+ .gw-btn--on-night:hover { background: var(--gw-on-night); opacity: 0.88; }
33
+ .gw-btn--sm { height: 36px; padding: 0 16px; }
34
+ .gw-btn--sm::after { content: ""; position: absolute; top: -6px; right: 0; bottom: -6px; left: 0; }
35
+ .gw-btn--lg { height: 48px; padding: 0 24px; font-size: 16px; }
36
+ .gw-btn--block { display: flex; width: 100%; }
37
+ .gw-btn:disabled, .gw-btn[aria-disabled="true"] { opacity: 0.48; cursor: not-allowed; pointer-events: none; }
38
+ /* pressed: the ink one step darker (the hover ink, as the dashboard's primary), nothing moves; the frameless variants
39
+ take one more step of tint */
40
+ .gw-btn:active { background: var(--gw-action-hover); }
41
+ .gw-btn--secondary:active { background: var(--gw-bg-soft); border-color: var(--gw-ink); }
42
+ .gw-btn--ghost:active { background: var(--gw-line); }
43
+ .gw-btn--on-night:active { background: var(--gw-on-night); opacity: 0.76; }
44
+ /* loading: aria-busy="true" on the <button>. The label stays (it keeps the width and the accessible name) but is not
45
+ painted; a 16px ring in the button's own ink turns in its place, the dashboard's spinner. ::before, because
46
+ .gw-btn--sm draws its hit area with ::after. website.js ignores presses on a busy button (no double submit). */
47
+ .gw-btn[aria-busy="true"] { cursor: progress; -webkit-text-fill-color: transparent; }
48
+ .gw-btn[aria-busy="true"] svg { opacity: 0; }
49
+ .gw-btn[aria-busy="true"]::before {
50
+ content: "";
51
+ position: absolute;
52
+ top: 50%;
53
+ left: 50%;
54
+ width: 16px;
55
+ height: 16px;
56
+ margin: -8px 0 0 -8px;
57
+ border: 2px solid color-mix(in srgb, currentColor 18%, transparent);
58
+ border-top-color: currentColor;
59
+ border-radius: var(--gw-radius-chip);
60
+ animation: gw-spin var(--gw-spin-ms) linear infinite;
61
+ }
62
+ @keyframes gw-spin { to { transform: rotate(360deg); } }
63
+ /* reduced motion: the ring stands still as an ellipsis, three dots in the button's ink */
64
+ @media (prefers-reduced-motion: reduce) {
65
+ .gw-btn[aria-busy="true"]::before {
66
+ border: 0;
67
+ border-radius: 0;
68
+ background:
69
+ radial-gradient(circle at 3px 50%, currentColor 1.5px, transparent 2px),
70
+ radial-gradient(circle at 8px 50%, currentColor 1.5px, transparent 2px),
71
+ radial-gradient(circle at 13px 50%, currentColor 1.5px, transparent 2px);
72
+ animation: none;
73
+ }
74
+ }
75
+
76
+ /* inline text link with an arrow that moves 2px on hover */
77
+ .gw-link--arrow {
78
+ display: inline-flex;
79
+ align-items: center;
80
+ gap: 6px;
81
+ min-height: 24px;
82
+ font-weight: 600;
83
+ text-decoration: none;
84
+ }
85
+ .gw-link--arrow::after { content: "\2192"; transition: transform 0.18s var(--gw-ease); }
86
+ .gw-link--arrow:hover::after { transform: translateX(2px); }
87
+
88
+ /* chips are pills: a small selectable token, not an action */
89
+ .gw-chip {
90
+ display: inline-flex;
91
+ align-items: center;
92
+ gap: 6px;
93
+ min-height: 32px;
94
+ padding: 4px 12px;
95
+ border: 1px solid var(--gw-line-strong);
96
+ border-radius: var(--gw-radius-chip);
97
+ background: var(--gw-surface);
98
+ color: var(--gw-ink-2);
99
+ font-size: 13px;
100
+ font-weight: 500;
101
+ line-height: 1.4;
102
+ white-space: nowrap;
103
+ }
104
+ :is(a, button, label).gw-chip { cursor: pointer; }
105
+ :is(a, button, label).gw-chip:hover { border-color: var(--gw-ink); color: var(--gw-ink); }
106
+ .gw-chip.is-active, .gw-chip[aria-pressed="true"], .gw-chip[aria-selected="true"] {
107
+ border-color: var(--gw-accent);
108
+ background: var(--gw-accent-soft);
109
+ color: var(--gw-accent-strong);
110
+ }
111
+
112
+ /* segmented control: one choice out of two to four (Monthly / Yearly), a radio group of buttons. The dashboard's look
113
+ (@gemboss/ui .ui-seg): one hairline group, radius 8, a hairline between options, the chosen one on the accent tint,
114
+ so it follows a brand's accent. The weight never changes with the choice: the labels do not move. */
115
+ .gw-segmented {
116
+ display: inline-flex;
117
+ max-width: 100%;
118
+ border: 1px solid var(--gw-line-strong);
119
+ border-radius: var(--gw-radius-action);
120
+ background: var(--gw-surface);
121
+ overflow: hidden;
122
+ overflow: clip;
123
+ vertical-align: middle;
124
+ }
125
+ .gw-segmented--block { display: flex; width: 100%; }
126
+ .gw-segmented > button {
127
+ display: inline-flex;
128
+ flex: 0 1 auto;
129
+ align-items: center;
130
+ justify-content: center;
131
+ min-width: 0;
132
+ height: calc(var(--gw-button-height) - 2px);
133
+ padding: 0 16px;
134
+ border: 0;
135
+ background: transparent;
136
+ color: var(--gw-muted);
137
+ font-size: 14px;
138
+ font-weight: 500;
139
+ line-height: 1.4;
140
+ white-space: nowrap;
141
+ cursor: pointer;
142
+ }
143
+ .gw-segmented--block > button { flex: 1 1 0; }
144
+ .gw-segmented > button + button { border-left: 1px solid var(--gw-line-strong); }
145
+ .gw-segmented > button:hover { background: var(--gw-bg-soft); color: var(--gw-ink); }
146
+ .gw-segmented > button:active { background: var(--gw-line); }
147
+ /* inside the group: the group clips its corners, so the ring sits within the option */
148
+ .gw-segmented > button:focus-visible { outline-offset: -2px; }
149
+ .gw-segmented > button[aria-checked="true"] { background: var(--gw-accent-soft); color: var(--gw-accent-strong); }
150
+
151
+ /* badges: small status, pill */
152
+ .gw-badge {
153
+ display: inline-flex;
154
+ align-items: center;
155
+ gap: 6px;
156
+ min-height: 24px;
157
+ padding: 2px 8px;
158
+ border-radius: var(--gw-radius-chip);
159
+ background: color-mix(in srgb, var(--gw-ink) 8%, transparent);
160
+ color: var(--gw-ink-2);
161
+ font-size: 12px;
162
+ font-weight: 500;
163
+ line-height: 1.4;
164
+ white-space: nowrap;
165
+ vertical-align: middle;
166
+ }
167
+ .gw-badge--accent { background: var(--gw-accent-soft); color: var(--gw-accent-strong); }
168
+ /* state hues fail 4.5:1 as small text on a tint, so the state is a dot and the words stay ink */
169
+ .gw-badge--success { background: var(--gw-success-soft); color: var(--gw-ink); }
170
+ .gw-badge--success::before, .gw-badge--warning::before { content: ""; flex: none; width: 6px; height: 6px; border-radius: var(--gw-radius-chip); background: var(--gw-success); }
171
+ .gw-badge--warning::before { background: var(--gw-warning); }
172
+
173
+ /* cards sit: white, 1px hairline, radius 12, the dashboard's resting shadow (Chris 24/10/2026: one language with the
174
+ app); no hover lift. A soft card is a tint and carries no shadow. */
175
+ .gw-card {
176
+ padding: 24px;
177
+ border: 1px solid var(--gw-line);
178
+ border-radius: var(--gw-radius-card);
179
+ background: var(--gw-surface);
180
+ box-shadow: var(--gw-shadow);
181
+ }
182
+ @media (min-width: 768px) { .gw-card { padding: 32px; } }
183
+ .gw-card--soft { background: var(--gw-bg-soft); box-shadow: none; }
184
+ .gw-card--link { display: block; color: inherit; }
185
+ .gw-card--link:hover { border-color: var(--gw-line-strong); }
186
+ :where(.gw-card, .gw-feature, .gw-plan) > :is(h3, h4) + p { margin-top: 8px; }
187
+ :where(.gw-card, .gw-feature) h3 { font-size: 20px; }
188
+ :where(.gw-card, .gw-feature) p { font-size: 16px; color: var(--gw-muted); }
189
+
190
+ /* a framed screen: a browser or app window around a screenshot or a drawn mock-up */
191
+ .gw-window {
192
+ overflow: hidden;
193
+ border: 1px solid var(--gw-line);
194
+ border-radius: var(--gw-radius-card);
195
+ background: var(--gw-surface);
196
+ color: var(--gw-ink);
197
+ box-shadow: var(--gw-shadow);
198
+ font-size: 13px;
199
+ line-height: 1.5;
200
+ }
201
+ .gw-window__bar {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: 12px;
205
+ min-height: 40px;
206
+ padding: 8px 16px;
207
+ border-bottom: 1px solid var(--gw-line);
208
+ color: var(--gw-muted);
209
+ font-size: 12px;
210
+ }
211
+ .gw-window__bar::before {
212
+ content: "";
213
+ flex: none;
214
+ width: 36px;
215
+ height: 8px;
216
+ background:
217
+ radial-gradient(circle at 4px 4px, var(--gw-line-strong) 4px, transparent 4px),
218
+ radial-gradient(circle at 18px 4px, var(--gw-line-strong) 4px, transparent 4px),
219
+ radial-gradient(circle at 32px 4px, var(--gw-line-strong) 4px, transparent 4px);
220
+ }
221
+ .gw-window__body { padding: 16px; }
222
+ .gw-window__body:has(> img:only-child) { padding: 0; }
223
+ .gw-window__body > img { width: 100%; }
224
+ /* what the numbers in the window are: "Illustrative data." under a drawn mock-up, or the source of real ones. The
225
+ last child of .gw-window, after __body (a <figcaption> when the window is a <figure>). */
226
+ .gw-window__caption {
227
+ margin: 0;
228
+ padding: 0 16px 12px;
229
+ color: var(--gw-muted);
230
+ font-size: 12px;
231
+ line-height: 1.5;
232
+ text-align: end;
233
+ }
234
+ .gw-window__body:has(> img:only-child) + .gw-window__caption { padding-top: 12px; }
235
+
236
+ /* icon: 20px inline stroke SVG (1.7 to 2px stroke, currentColor) */
237
+ .gw-icon { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 20px; height: 20px; }
238
+ .gw-icon svg { width: 20px; height: 20px; }
239
+
240
+ /* list with check marks */
241
+ .gw-list--check { display: grid; gap: 12px; padding: 0; list-style: none; }
242
+ .gw-list--check > li { position: relative; padding-left: 24px; }
243
+ .gw-list--check > li::before {
244
+ content: "";
245
+ position: absolute;
246
+ left: 0;
247
+ top: 6px;
248
+ top: calc((1lh - 16px) / 2);
249
+ width: 16px;
250
+ height: 16px;
251
+ background: var(--gw-accent);
252
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.4l2.9 2.8L12.5 5' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px no-repeat;
253
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.4l2.9 2.8L12.5 5' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px no-repeat;
254
+ }
255
+
256
+ /* table: the wrapper scrolls inside its own box on phones, the page never does */
257
+ .gw-table {
258
+ overflow-x: auto;
259
+ border: 1px solid var(--gw-line);
260
+ border-radius: var(--gw-radius-card);
261
+ background: var(--gw-surface);
262
+ -webkit-overflow-scrolling: touch;
263
+ }
264
+ .gw-table table { width: 100%; border-collapse: collapse; font-size: 14px; line-height: 1.5; }
265
+ .gw-table caption { padding: 16px; text-align: left; font-size: 13px; color: var(--gw-muted); }
266
+ .gw-table th, .gw-table td { padding: 12px 16px; border-bottom: 1px solid var(--gw-line); text-align: left; vertical-align: top; }
267
+ .gw-table thead th { background: var(--gw-bg-soft); color: var(--gw-ink); font-size: 13px; font-weight: 600; white-space: nowrap; }
268
+ .gw-table tbody th { font-weight: 500; }
269
+ .gw-table tbody tr:last-child > * { border-bottom: 0; }
270
+ .gw-table td { color: var(--gw-ink-2); font-variant-numeric: tabular-nums; }
271
+ .gw-table--compare table { min-width: 640px; }
272
+ .gw-table--compare td { text-align: center; }
273
+ .gw-table--compare thead th:not(:first-child) { text-align: center; }
274
+ .gw-table--compare tbody th { position: sticky; left: 0; z-index: 1; background: var(--gw-surface); min-width: 160px; }
275
+
276
+ /* FAQ: native details/summary, no script */
277
+ .gw-faq { display: grid; gap: 12px; }
278
+ .gw-faq__item {
279
+ padding: 0 24px;
280
+ border: 1px solid var(--gw-line);
281
+ border-radius: var(--gw-radius-card);
282
+ background: var(--gw-surface);
283
+ }
284
+ .gw-faq__item[open] { border-color: var(--gw-line-strong); }
285
+ .gw-faq__item > summary {
286
+ display: flex;
287
+ align-items: center;
288
+ justify-content: space-between;
289
+ gap: 16px;
290
+ min-height: 44px;
291
+ padding: 20px 0;
292
+ font-size: 18px;
293
+ font-weight: 600;
294
+ line-height: 1.4;
295
+ list-style: none;
296
+ cursor: pointer;
297
+ }
298
+ .gw-faq__item > summary::-webkit-details-marker { display: none; }
299
+ .gw-faq__item > summary::after {
300
+ content: "";
301
+ flex: none;
302
+ width: 16px;
303
+ height: 16px;
304
+ background:
305
+ linear-gradient(var(--gw-accent), var(--gw-accent)) center / 16px 2px no-repeat,
306
+ linear-gradient(var(--gw-accent), var(--gw-accent)) center / 2px 16px no-repeat;
307
+ transition: transform 0.2s var(--gw-ease);
308
+ }
309
+ .gw-faq__item[open] > summary::after { transform: rotate(45deg); }
310
+ .gw-faq__item > :not(summary) { margin: 0 0 20px; max-width: 68ch; color: var(--gw-muted); font-size: 16px; }
311
+
312
+ /* touch: every control is at least 44px tall, labels may wrap */
313
+ @media (max-width: 768px), (pointer: coarse) {
314
+ .gw-btn, .gw-btn--sm, .gw-btn--lg {
315
+ height: auto;
316
+ min-height: var(--gw-tap-min);
317
+ padding-block: 8px;
318
+ white-space: normal;
319
+ text-wrap: balance;
320
+ }
321
+ .gw-btn--lg { min-height: 48px; }
322
+ :is(a, button, label).gw-chip { min-height: var(--gw-tap-min); }
323
+ .gw-segmented > button { height: auto; min-height: var(--gw-tap-min); padding-block: 8px; white-space: normal; text-wrap: balance; }
324
+ }
package/css/footer.css ADDED
@@ -0,0 +1,53 @@
1
+ /* footer: brand block, link columns, bottom row. One hairline on top, white like the page. */
2
+ .gw-footer {
3
+ padding-block: 64px 40px;
4
+ border-top: 1px solid var(--gw-line);
5
+ background: var(--gw-bg);
6
+ color: var(--gw-ink);
7
+ font-size: 14px;
8
+ }
9
+ .gw-footer__grid {
10
+ display: grid;
11
+ grid-template-columns: repeat(2, minmax(0, 1fr));
12
+ gap: 32px 24px;
13
+ }
14
+ .gw-footer__brand { grid-column: 1 / -1; max-width: 320px; }
15
+ .gw-footer__brand p { margin-top: 16px; color: var(--gw-muted); }
16
+ .gw-footer__heading { margin: 0 0 8px; color: var(--gw-ink); font-size: 13px; font-weight: 600; letter-spacing: 0; }
17
+ .gw-footer__col ul { margin: 0; padding: 0; list-style: none; }
18
+ .gw-footer__col a { display: block; padding-block: 6px; color: var(--gw-muted); }
19
+ .gw-footer__col a:hover { color: var(--gw-ink); }
20
+ .gw-footer__col a[aria-current="page"] { color: var(--gw-accent-strong); }
21
+ .gw-footer__bottom {
22
+ display: flex;
23
+ flex-wrap: wrap;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ gap: 8px 24px;
27
+ margin-top: 48px;
28
+ padding-top: 24px;
29
+ border-top: 1px solid var(--gw-line);
30
+ color: var(--gw-muted);
31
+ font-size: 13px;
32
+ }
33
+ .gw-footer__bottom:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
34
+ .gw-footer__bottom ul { display: flex; flex-wrap: wrap; gap: 0 16px; margin: 0; padding: 0; list-style: none; }
35
+ .gw-footer__bottom a {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ min-width: 24px;
40
+ min-height: 24px;
41
+ color: var(--gw-muted);
42
+ }
43
+ .gw-footer__bottom a:hover { color: var(--gw-ink); }
44
+ @media (min-width: 768px) {
45
+ .gw-footer__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
46
+ }
47
+ @media (min-width: 1080px) {
48
+ .gw-footer__grid { grid-auto-flow: column; grid-template-columns: minmax(0, 1.6fr); grid-auto-columns: minmax(0, 1fr); }
49
+ .gw-footer__brand { grid-column: auto; }
50
+ }
51
+ @media (max-width: 768px), (pointer: coarse) {
52
+ .gw-footer__bottom a { min-height: var(--gw-tap-min); }
53
+ }