@gemboss/website-ui 0.0.0-stage → 0.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 (42) hide show
  1. package/AGENTS.md +174 -0
  2. package/CHANGELOG.md +19 -0
  3. package/README.md +117 -2
  4. package/bin/gemboss-website.mjs +78 -0
  5. package/bin/lib/check.mjs +642 -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 +236 -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/sections.css +173 -0
  16. package/css/tokens.css +198 -0
  17. package/demo/index.html +629 -0
  18. package/demo/theme-example.html +133 -0
  19. package/docs/components.md +299 -0
  20. package/docs/markup-contract.md +115 -0
  21. package/docs/philosophy.md +69 -0
  22. package/docs/theming.md +95 -0
  23. package/js/website.js +149 -0
  24. package/package.json +50 -4
  25. package/skill/SKILL.md +48 -0
  26. package/system.json +120 -0
  27. package/templates/footer.html +40 -0
  28. package/templates/header.html +60 -0
  29. package/templates/page.html +134 -0
  30. package/templates/sections/cta.html +15 -0
  31. package/templates/sections/faq.html +24 -0
  32. package/templates/sections/features.html +32 -0
  33. package/templates/sections/hero.html +25 -0
  34. package/templates/sections/logos.html +15 -0
  35. package/templates/sections/pricing.html +39 -0
  36. package/templates/sections/quote.html +13 -0
  37. package/templates/sections/showcase.html +24 -0
  38. package/templates/sections/split.html +25 -0
  39. package/templates/sections/stats.html +26 -0
  40. package/templates/sections/steps.html +17 -0
  41. package/website.css +1064 -0
  42. 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 get a visible link style; everything else opts in with .gw-link */
65
+ :where(p, dd, td, figcaption, .gw-list--check li) 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) 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,236 @@
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
+
39
+ /* inline text link with an arrow that moves 2px on hover */
40
+ .gw-link--arrow {
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: 6px;
44
+ min-height: 24px;
45
+ font-weight: 600;
46
+ text-decoration: none;
47
+ }
48
+ .gw-link--arrow::after { content: "\2192"; transition: transform 0.18s var(--gw-ease); }
49
+ .gw-link--arrow:hover::after { transform: translateX(2px); }
50
+
51
+ /* chips are pills: a small selectable token, not an action */
52
+ .gw-chip {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ gap: 6px;
56
+ min-height: 32px;
57
+ padding: 4px 12px;
58
+ border: 1px solid var(--gw-line-strong);
59
+ border-radius: var(--gw-radius-chip);
60
+ background: var(--gw-surface);
61
+ color: var(--gw-ink-2);
62
+ font-size: 13px;
63
+ font-weight: 500;
64
+ line-height: 1.4;
65
+ white-space: nowrap;
66
+ }
67
+ :is(a, button, label).gw-chip { cursor: pointer; }
68
+ :is(a, button, label).gw-chip:hover { border-color: var(--gw-ink); color: var(--gw-ink); }
69
+ .gw-chip.is-active, .gw-chip[aria-pressed="true"], .gw-chip[aria-selected="true"] {
70
+ border-color: var(--gw-accent);
71
+ background: var(--gw-accent-soft);
72
+ color: var(--gw-accent-strong);
73
+ }
74
+
75
+ /* badges: small status, pill */
76
+ .gw-badge {
77
+ display: inline-flex;
78
+ align-items: center;
79
+ gap: 6px;
80
+ min-height: 24px;
81
+ padding: 2px 8px;
82
+ border-radius: var(--gw-radius-chip);
83
+ background: color-mix(in srgb, var(--gw-ink) 8%, transparent);
84
+ color: var(--gw-ink-2);
85
+ font-size: 12px;
86
+ font-weight: 500;
87
+ line-height: 1.4;
88
+ white-space: nowrap;
89
+ vertical-align: middle;
90
+ }
91
+ .gw-badge--accent { background: var(--gw-accent-soft); color: var(--gw-accent-strong); }
92
+ /* state hues fail 4.5:1 as small text on a tint, so the state is a dot and the words stay ink */
93
+ .gw-badge--success { background: var(--gw-success-soft); color: var(--gw-ink); }
94
+ .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); }
95
+ .gw-badge--warning::before { background: var(--gw-warning); }
96
+
97
+ /* cards sit: white, 1px hairline, radius 12, the dashboard's resting shadow (Chris 24/10/2026: one language with the
98
+ app); no hover lift. A soft card is a tint and carries no shadow. */
99
+ .gw-card {
100
+ padding: 24px;
101
+ border: 1px solid var(--gw-line);
102
+ border-radius: var(--gw-radius-card);
103
+ background: var(--gw-surface);
104
+ box-shadow: var(--gw-shadow);
105
+ }
106
+ @media (min-width: 768px) { .gw-card { padding: 32px; } }
107
+ .gw-card--soft { background: var(--gw-bg-soft); box-shadow: none; }
108
+ .gw-card--link { display: block; color: inherit; }
109
+ .gw-card--link:hover { border-color: var(--gw-line-strong); }
110
+ :where(.gw-card, .gw-feature, .gw-plan) > :is(h3, h4) + p { margin-top: 8px; }
111
+ :where(.gw-card, .gw-feature) h3 { font-size: 20px; }
112
+ :where(.gw-card, .gw-feature) p { font-size: 16px; color: var(--gw-muted); }
113
+
114
+ /* a framed screen: a browser or app window around a screenshot or a drawn mock-up */
115
+ .gw-window {
116
+ overflow: hidden;
117
+ border: 1px solid var(--gw-line);
118
+ border-radius: var(--gw-radius-card);
119
+ background: var(--gw-surface);
120
+ color: var(--gw-ink);
121
+ box-shadow: var(--gw-shadow);
122
+ font-size: 13px;
123
+ line-height: 1.5;
124
+ }
125
+ .gw-window__bar {
126
+ display: flex;
127
+ align-items: center;
128
+ gap: 12px;
129
+ min-height: 40px;
130
+ padding: 8px 16px;
131
+ border-bottom: 1px solid var(--gw-line);
132
+ color: var(--gw-muted);
133
+ font-size: 12px;
134
+ }
135
+ .gw-window__bar::before {
136
+ content: "";
137
+ flex: none;
138
+ width: 36px;
139
+ height: 8px;
140
+ background:
141
+ radial-gradient(circle at 4px 4px, var(--gw-line-strong) 4px, transparent 4px),
142
+ radial-gradient(circle at 18px 4px, var(--gw-line-strong) 4px, transparent 4px),
143
+ radial-gradient(circle at 32px 4px, var(--gw-line-strong) 4px, transparent 4px);
144
+ }
145
+ .gw-window__body { padding: 16px; }
146
+ .gw-window__body:has(> img:only-child) { padding: 0; }
147
+ .gw-window__body > img { width: 100%; }
148
+
149
+ /* icon: 20px inline stroke SVG (1.7 to 2px stroke, currentColor) */
150
+ .gw-icon { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 20px; height: 20px; }
151
+ .gw-icon svg { width: 20px; height: 20px; }
152
+
153
+ /* list with check marks */
154
+ .gw-list--check { display: grid; gap: 12px; padding: 0; list-style: none; }
155
+ .gw-list--check > li { position: relative; padding-left: 24px; }
156
+ .gw-list--check > li::before {
157
+ content: "";
158
+ position: absolute;
159
+ left: 0;
160
+ top: 6px;
161
+ top: calc((1lh - 16px) / 2);
162
+ width: 16px;
163
+ height: 16px;
164
+ background: var(--gw-accent);
165
+ -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;
166
+ 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;
167
+ }
168
+
169
+ /* table: the wrapper scrolls inside its own box on phones, the page never does */
170
+ .gw-table {
171
+ overflow-x: auto;
172
+ border: 1px solid var(--gw-line);
173
+ border-radius: var(--gw-radius-card);
174
+ background: var(--gw-surface);
175
+ -webkit-overflow-scrolling: touch;
176
+ }
177
+ .gw-table table { width: 100%; border-collapse: collapse; font-size: 14px; line-height: 1.5; }
178
+ .gw-table caption { padding: 16px; text-align: left; font-size: 13px; color: var(--gw-muted); }
179
+ .gw-table th, .gw-table td { padding: 12px 16px; border-bottom: 1px solid var(--gw-line); text-align: left; vertical-align: top; }
180
+ .gw-table thead th { background: var(--gw-bg-soft); color: var(--gw-ink); font-size: 13px; font-weight: 600; white-space: nowrap; }
181
+ .gw-table tbody th { font-weight: 500; }
182
+ .gw-table tbody tr:last-child > * { border-bottom: 0; }
183
+ .gw-table td { color: var(--gw-ink-2); font-variant-numeric: tabular-nums; }
184
+ .gw-table--compare table { min-width: 640px; }
185
+ .gw-table--compare td { text-align: center; }
186
+ .gw-table--compare thead th:not(:first-child) { text-align: center; }
187
+ .gw-table--compare tbody th { position: sticky; left: 0; z-index: 1; background: var(--gw-surface); min-width: 160px; }
188
+
189
+ /* FAQ: native details/summary, no script */
190
+ .gw-faq { display: grid; gap: 12px; }
191
+ .gw-faq__item {
192
+ padding: 0 24px;
193
+ border: 1px solid var(--gw-line);
194
+ border-radius: var(--gw-radius-card);
195
+ background: var(--gw-surface);
196
+ }
197
+ .gw-faq__item[open] { border-color: var(--gw-line-strong); }
198
+ .gw-faq__item > summary {
199
+ display: flex;
200
+ align-items: center;
201
+ justify-content: space-between;
202
+ gap: 16px;
203
+ min-height: 44px;
204
+ padding: 20px 0;
205
+ font-size: 18px;
206
+ font-weight: 600;
207
+ line-height: 1.4;
208
+ list-style: none;
209
+ cursor: pointer;
210
+ }
211
+ .gw-faq__item > summary::-webkit-details-marker { display: none; }
212
+ .gw-faq__item > summary::after {
213
+ content: "";
214
+ flex: none;
215
+ width: 16px;
216
+ height: 16px;
217
+ background:
218
+ linear-gradient(var(--gw-accent), var(--gw-accent)) center / 16px 2px no-repeat,
219
+ linear-gradient(var(--gw-accent), var(--gw-accent)) center / 2px 16px no-repeat;
220
+ transition: transform 0.2s var(--gw-ease);
221
+ }
222
+ .gw-faq__item[open] > summary::after { transform: rotate(45deg); }
223
+ .gw-faq__item > :not(summary) { margin: 0 0 20px; max-width: 68ch; color: var(--gw-muted); font-size: 16px; }
224
+
225
+ /* touch: every control is at least 44px tall, labels may wrap */
226
+ @media (max-width: 768px), (pointer: coarse) {
227
+ .gw-btn, .gw-btn--sm, .gw-btn--lg {
228
+ height: auto;
229
+ min-height: var(--gw-tap-min);
230
+ padding-block: 8px;
231
+ white-space: normal;
232
+ text-wrap: balance;
233
+ }
234
+ .gw-btn--lg { min-height: 48px; }
235
+ :is(a, button, label).gw-chip { min-height: var(--gw-tap-min); }
236
+ }
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
+ }
package/css/header.css ADDED
@@ -0,0 +1,231 @@
1
+ /* header: announcement, a flat 64px bar with one hairline, mega menu, mobile menu below 1080px.
2
+ Not a floating pill: no radius, no blur, no shadow on the bar. Nothing tucks under it. */
3
+ .gw-announce {
4
+ padding: 8px var(--gw-gutter);
5
+ border-bottom: 1px solid var(--gw-line);
6
+ background: var(--gw-bg-soft);
7
+ color: var(--gw-ink-2);
8
+ font-size: 13px;
9
+ line-height: 1.5;
10
+ text-align: center;
11
+ }
12
+ .gw-announce a { color: var(--gw-ink); font-weight: 600; text-decoration: underline; text-underline-offset: 4px; }
13
+
14
+ .gw-header {
15
+ position: sticky;
16
+ top: 0;
17
+ z-index: 50;
18
+ border-bottom: 1px solid var(--gw-line);
19
+ background: var(--gw-surface);
20
+ }
21
+ .gw-header.is-scrolled { border-bottom-color: var(--gw-line-strong); }
22
+ .gw-header__bar { display: flex; align-items: center; gap: 16px; height: var(--gw-header-height); }
23
+
24
+ .gw-brand {
25
+ display: inline-flex;
26
+ align-items: center;
27
+ gap: 8px;
28
+ flex: none;
29
+ min-height: 44px;
30
+ color: var(--gw-ink);
31
+ font-size: 18px;
32
+ font-weight: 700;
33
+ letter-spacing: -0.01em;
34
+ white-space: nowrap;
35
+ }
36
+ .gw-brand__mark { flex: none; width: 26px; height: 26px; }
37
+ .gw-brand__name { line-height: 1.2; }
38
+
39
+ /* desktop nav */
40
+ .gw-nav { display: none; }
41
+ .gw-nav__list { display: flex; align-items: center; gap: 4px; margin: 0; padding: 0; list-style: none; }
42
+ .gw-nav__trigger,
43
+ .gw-nav__link {
44
+ display: inline-flex;
45
+ align-items: center;
46
+ gap: 6px;
47
+ height: 40px;
48
+ padding: 0 12px;
49
+ border: 0;
50
+ border-radius: var(--gw-radius-action);
51
+ background: transparent;
52
+ color: var(--gw-ink-2);
53
+ font-size: 14px;
54
+ font-weight: 500;
55
+ line-height: 1.4;
56
+ white-space: nowrap;
57
+ cursor: pointer;
58
+ }
59
+ .gw-nav__trigger:hover,
60
+ .gw-nav__link:hover,
61
+ .gw-nav__item.is-open > .gw-nav__trigger { background: var(--gw-bg-soft); color: var(--gw-ink); }
62
+ /* the chevron: the site's own 24px path, drawn as a mask so it takes currentColor */
63
+ .gw-nav__trigger::after,
64
+ .gw-mobile-menu__group > summary::after {
65
+ content: "";
66
+ flex: none;
67
+ width: 12px;
68
+ height: 12px;
69
+ background: currentColor;
70
+ opacity: 0.6;
71
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px no-repeat;
72
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px no-repeat;
73
+ transition: transform 0.18s var(--gw-ease);
74
+ }
75
+ .gw-nav__trigger[aria-expanded="true"]::after,
76
+ .gw-mobile-menu__group[open] > summary::after { transform: rotate(180deg); }
77
+ .gw-nav__link[aria-current="page"] {
78
+ color: var(--gw-ink);
79
+ font-weight: 600;
80
+ text-decoration: underline;
81
+ text-decoration-color: var(--gw-accent);
82
+ text-decoration-thickness: 2px;
83
+ text-underline-offset: 8px;
84
+ }
85
+
86
+ /* mega menu: a floating card centred under the bar, as on gemboss.ai (Chris 24/10/2026): one hairline, radius 20,
87
+ the raised shadow (it is a popover), 32px inside. The bridge (::before) keeps the pointer inside while it crosses
88
+ the 8px gap from the trigger. */
89
+ .gw-mega {
90
+ position: absolute;
91
+ top: calc(100% + 8px);
92
+ left: 50%;
93
+ width: min(960px, calc(100vw - 40px));
94
+ max-height: calc(100vh - var(--gw-header-height) - 24px);
95
+ overflow-y: auto;
96
+ transform: translateX(-50%);
97
+ border: 1px solid var(--gw-line);
98
+ border-radius: 20px;
99
+ background: var(--gw-surface);
100
+ box-shadow: var(--gw-shadow-raised);
101
+ }
102
+ .gw-mega::before { content: ""; position: absolute; top: -12px; left: 0; right: 0; height: 12px; }
103
+ .gw-mega:not([hidden]) { animation: gw-mega-in 0.16s var(--gw-ease); }
104
+ @keyframes gw-mega-in { from { opacity: 0; transform: translateX(-50%) translateY(8px); } }
105
+ @media (prefers-reduced-motion: reduce) { .gw-mega:not([hidden]) { animation: none; } }
106
+ .gw-mega > .gw-wrap { width: auto; margin: 0; }
107
+ .gw-mega__grid { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 32px; padding: 32px; }
108
+ .gw-mega__col { display: flex; flex-direction: column; gap: 2px; }
109
+ .gw-mega__heading { padding: 0 8px 8px; color: var(--gw-muted); font-size: 12px; font-weight: 500; }
110
+ .gw-mega__link {
111
+ display: grid;
112
+ grid-template-columns: 32px minmax(0, 1fr);
113
+ align-items: start;
114
+ gap: 12px;
115
+ padding: 8px;
116
+ border-radius: var(--gw-radius-action);
117
+ color: var(--gw-ink);
118
+ }
119
+ .gw-mega__link:hover { background: var(--gw-bg-soft); }
120
+ .gw-mega__link b { display: block; font-size: 14px; font-weight: 500; line-height: 20px; }
121
+ .gw-mega__link i { display: block; margin-top: 2px; color: var(--gw-muted); font-size: 13px; font-style: normal; line-height: 18px; }
122
+ .gw-mega__link[aria-current="page"] b { color: var(--gw-accent-strong); font-weight: 600; }
123
+ .gw-mega__icon {
124
+ display: grid;
125
+ place-items: center;
126
+ width: 32px;
127
+ height: 32px;
128
+ border: 1px solid var(--gw-line);
129
+ border-radius: var(--gw-radius-action);
130
+ background: var(--gw-surface);
131
+ color: var(--gw-muted);
132
+ }
133
+ .gw-mega__icon svg { width: 16px; height: 16px; }
134
+ .gw-mega__link:hover .gw-mega__icon { border-color: var(--gw-accent-soft); background: var(--gw-accent-soft); color: var(--gw-accent-strong); }
135
+ /* the feature card is the Stage (the house illustration recipe, Chris 22/10/2026): a night ground lit with the accent
136
+ from two corners, white words. An illustration may light itself, so its glows are gradients, not chrome shadows. */
137
+ .gw-mega__feature {
138
+ display: flex;
139
+ flex-direction: column;
140
+ justify-content: flex-end;
141
+ gap: 4px;
142
+ min-height: 216px;
143
+ padding: 24px;
144
+ border-radius: var(--gw-radius-panel);
145
+ background:
146
+ radial-gradient(360px 260px at 12% 112%, color-mix(in srgb, var(--gw-accent) 78%, transparent), transparent 62%),
147
+ radial-gradient(300px 220px at 92% -14%, color-mix(in srgb, var(--gw-wash-violet) 44%, transparent), transparent 70%),
148
+ var(--gw-night);
149
+ color: var(--gw-on-night);
150
+ }
151
+ .gw-mega__feature b { font-size: 18px; font-weight: 650; line-height: 1.25; }
152
+ .gw-mega__feature i { color: var(--gw-on-night); font-size: 13px; font-style: normal; line-height: 1.5; opacity: 0.9; }
153
+ .gw-mega__feature .gw-link--arrow { margin-top: 16px; color: var(--gw-on-night); font-size: 13px; font-weight: 600; }
154
+
155
+ /* actions on the right */
156
+ .gw-header__actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
157
+ .gw-header__signin { display: none; }
158
+ .gw-header__actions > .gw-btn { display: none; }
159
+ .gw-theme-toggle,
160
+ .gw-menu-toggle {
161
+ display: inline-flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ flex: none;
165
+ width: 40px;
166
+ height: 40px;
167
+ padding: 0;
168
+ border: 0;
169
+ border-radius: var(--gw-radius-action);
170
+ background: transparent;
171
+ color: var(--gw-ink);
172
+ }
173
+ .gw-theme-toggle:hover,
174
+ .gw-menu-toggle:hover { background: var(--gw-bg-soft); }
175
+ .gw-theme-toggle svg,
176
+ .gw-menu-toggle svg { width: 20px; height: 20px; }
177
+ .gw-menu-toggle[aria-expanded="true"] .gw-menu-toggle__open,
178
+ .gw-menu-toggle:not([aria-expanded="true"]) .gw-menu-toggle__close { display: none; }
179
+
180
+ /* mobile menu: a full-height sheet under the bar; html.is-menu-open locks the page */
181
+ .gw-mobile-menu {
182
+ position: fixed;
183
+ top: calc(var(--gw-header-height) + 1px);
184
+ left: 0;
185
+ right: 0;
186
+ bottom: 0;
187
+ overflow-y: auto;
188
+ overscroll-behavior: contain;
189
+ padding-block: 8px 32px;
190
+ background: var(--gw-bg);
191
+ }
192
+ html.is-menu-open { overflow: hidden; }
193
+ html.is-menu-open .gw-header { position: fixed; top: 0; left: 0; right: 0; }
194
+ .gw-mobile-menu__group > summary,
195
+ .gw-mobile-menu__top {
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: space-between;
199
+ min-height: 48px;
200
+ padding-block: 8px;
201
+ border-bottom: 1px solid var(--gw-line);
202
+ color: var(--gw-ink);
203
+ font-size: 18px;
204
+ font-weight: 500;
205
+ list-style: none;
206
+ cursor: pointer;
207
+ }
208
+ .gw-mobile-menu__group > summary::-webkit-details-marker { display: none; }
209
+ .gw-mobile-menu__list { margin: 0; padding: 8px 0 16px; list-style: none; }
210
+ .gw-mobile-menu__link { display: flex; align-items: center; min-height: 44px; color: var(--gw-ink-2); font-size: 16px; }
211
+ .gw-mobile-menu__link[aria-current="page"],
212
+ .gw-mobile-menu__top[aria-current="page"] { color: var(--gw-accent-strong); font-weight: 600; }
213
+ .gw-mobile-menu__actions { display: grid; gap: 8px; margin-top: 24px; }
214
+
215
+ @media (min-width: 480px) {
216
+ .gw-header__actions > .gw-btn:not(.gw-btn--secondary) { display: inline-flex; }
217
+ }
218
+ @media (min-width: 768px) {
219
+ .gw-header__actions > .gw-btn--secondary { display: inline-flex; }
220
+ }
221
+ @media (min-width: 1080px) {
222
+ .gw-header__bar { gap: 32px; }
223
+ .gw-nav { display: block; }
224
+ .gw-header__signin { display: inline-flex; }
225
+ .gw-menu-toggle, .gw-mobile-menu { display: none !important; }
226
+ html.is-menu-open { overflow: visible; }
227
+ html.is-menu-open .gw-header { position: sticky; }
228
+ }
229
+ @media (max-width: 768px), (pointer: coarse) {
230
+ .gw-theme-toggle, .gw-menu-toggle { width: var(--gw-tap-min); height: var(--gw-tap-min); }
231
+ }
package/css/layout.css ADDED
@@ -0,0 +1,59 @@
1
+ /* layout: widths, section rhythm, grids, night band. Breakpoints 480 / 768 / 1080 (system.json). */
2
+ .gw-wrap,
3
+ .gw-wrap--narrow {
4
+ width: min(100% - 2 * var(--gw-gutter), var(--gw-wrap));
5
+ margin-inline: auto;
6
+ }
7
+ .gw-wrap--narrow { max-width: var(--gw-wrap-narrow); }
8
+
9
+ .gw-section { padding-block: var(--gw-space-section); }
10
+ .gw-section--soft { background: var(--gw-bg-soft); }
11
+ .gw-section--tight { padding-block: 40px; }
12
+ @media (min-width: 768px) { .gw-section--tight { padding-block: 64px; } }
13
+
14
+ /* label + h2 + one or two lines; the vertical flow of every text block in the kit */
15
+ .gw-section__head { max-width: 660px; }
16
+ .gw-section__head--center { margin-inline: auto; text-align: center; }
17
+ .gw-section__head--center .gw-lead { margin-inline: auto; }
18
+ .gw-section__head:not(:last-child) { margin-bottom: 40px; }
19
+ @media (min-width: 768px) { .gw-section__head:not(:last-child) { margin-bottom: 48px; } }
20
+
21
+ :where(.gw-section__head, .gw-hero > .gw-wrap, .gw-split > :not(.gw-window), .gw-cta, .gw-card, .gw-feature, .gw-plan) > * + * { margin-top: 16px; }
22
+ :where(.gw-section__head, .gw-hero > .gw-wrap, .gw-split > :not(.gw-window), .gw-cta, .gw-card, .gw-plan) > .gw-actions { margin-top: 32px; }
23
+ :where(.gw-section__head, .gw-hero > .gw-wrap, .gw-split > :not(.gw-window), .gw-cta, .gw-card, .gw-plan) > .gw-list--check { margin-top: 24px; }
24
+
25
+ /* a row of actions: one .gw-btn, then a secondary or a link */
26
+ .gw-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
27
+
28
+ /* grids: one column on phones */
29
+ .gw-grid,
30
+ .gw-grid--2,
31
+ .gw-grid--3,
32
+ .gw-grid--4 {
33
+ display: grid;
34
+ grid-template-columns: minmax(0, 1fr);
35
+ gap: 16px;
36
+ }
37
+ @media (min-width: 480px) {
38
+ .gw-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
39
+ }
40
+ @media (min-width: 768px) {
41
+ .gw-grid, .gw-grid--2, .gw-grid--3, .gw-grid--4 { gap: 24px; }
42
+ .gw-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
43
+ .gw-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
44
+ }
45
+ @media (min-width: 1080px) {
46
+ .gw-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
47
+ }
48
+
49
+ /* night band: the dark palette (tokens.css) on the night ground, lit from the top edge.
50
+ At most three on a page, never two in a row. */
51
+ .gw-band--night,
52
+ .gw-showcase {
53
+ position: relative;
54
+ isolation: isolate;
55
+ color: var(--gw-ink);
56
+ background:
57
+ radial-gradient(800px 360px at 50% 0%, color-mix(in srgb, var(--gw-accent) 22%, transparent), transparent 72%),
58
+ var(--gw-night);
59
+ }