@gemboss/website-ui 0.1.0 → 0.3.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.
package/demo/index.html CHANGED
@@ -22,7 +22,6 @@
22
22
  @media (min-width: 480px) { .demo-metrics b { font-size: 18px; } }
23
23
  .demo-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
24
24
  .demo-ask { margin-left: auto; max-width: 320px; padding: 8px 12px; border-radius: var(--gw-radius-card); background: var(--gw-bg-soft); color: var(--gw-ink); }
25
- .demo-illus { margin-top: 12px; color: var(--gw-muted); font-size: 12px; }
26
25
  .demo-mt { margin-top: 16px; }
27
26
  .demo-specimen + .demo-specimen { margin-top: 48px; }
28
27
  .demo-specimen > h3 { margin-bottom: 16px; font-size: 20px; }
@@ -170,8 +169,8 @@
170
169
  <div class="demo-row"><span><small>Product page</small><b>The Radiance Serum</b></span><span class="gw-badge gw-badge--success">Live</span></div>
171
170
  <div class="demo-row"><span><small>Offer</small><b>Build a box, save 25%</b></span><span class="gw-badge">Draft</span></div>
172
171
  </div>
173
- <p class="demo-illus">GemBoss-built demo pages for Vital Glow. Illustrative.</p>
174
172
  </div>
173
+ <p class="gw-window__caption">Illustrative data. Vital Glow is a GemBoss demo brand.</p>
175
174
  </div>
176
175
  </div>
177
176
  </section>
@@ -287,8 +286,8 @@
287
286
  <p><b>0.004</b>CLS</p>
288
287
  <p><b>0.05 s</b>TTFB</p>
289
288
  </div>
290
- <p class="demo-illus">Measured on a live GemBoss store: mobile, simulated 4G, median of 8 Lighthouse runs, 10 Oct 2026.</p>
291
289
  </div>
290
+ <p class="gw-window__caption">Measured on a live GemBoss store: mobile, simulated 4G, median of 8 Lighthouse runs, 10 Oct 2026.</p>
292
291
  </div>
293
292
  </div>
294
293
  </section>
@@ -315,8 +314,8 @@
315
314
  <div class="demo-row"><span><b>What dermatologists check first</b><small>Solution aware · ingredient readers</small></span><span class="gw-badge">Drafted</span></div>
316
315
  <div class="demo-row"><span><b>A gift that looks after her</b><small>Product aware · gift buyers</small></span><span class="gw-badge">Drafted</span></div>
317
316
  </div>
318
- <p class="demo-illus">GemBoss-built demo pages for Vital Glow. Illustrative.</p>
319
317
  </div>
318
+ <p class="gw-window__caption">Illustrative data. Vital Glow is a GemBoss demo brand.</p>
320
319
  </div>
321
320
  </div>
322
321
  </section>
@@ -339,8 +338,8 @@
339
338
  <li>Checked the render on desktop and phone</li>
340
339
  </ul>
341
340
  <div class="demo-head demo-mt"><span>The change waits on the canvas until you publish it.</span><span class="gw-badge gw-badge--accent">Draft</span></div>
342
- <p class="demo-illus">Vital Glow is a GemBoss demo store. Illustrative.</p>
343
341
  </div>
342
+ <p class="gw-window__caption">Illustrative data. Vital Glow is a GemBoss demo store.</p>
344
343
  </div>
345
344
  <div class="gw-features gw-grid--3">
346
345
  <div class="gw-feature"><h3>Ask in plain words</h3><p>"Add a trust row under the buy box" is enough.</p></div>
@@ -383,12 +382,17 @@
383
382
  <p class="gw-label">Pricing</p>
384
383
  <h2 id="pricing-h"><span class="gw-dim">Every angle gets its own funnel.</span> Pay for the traffic you send it.</h2>
385
384
  <p class="gw-lead">Build as many funnels as you like on every plan, and try it free for 14 days.</p>
385
+ <div class="gw-segmented" role="radiogroup" aria-label="Billing period" data-gw-segmented="billing">
386
+ <button type="button" role="radio" aria-checked="true" value="monthly">Monthly</button>
387
+ <button type="button" role="radio" aria-checked="false" value="yearly">Yearly –20%</button>
388
+ </div>
386
389
  </div>
387
390
  <div class="gw-pricing">
388
391
  <div class="gw-plan">
389
392
  <h3>Starter</h3>
390
393
  <p>Find your first winning angle.</p>
391
- <p class="gw-plan__price">$49 <small>/ month</small></p>
394
+ <p class="gw-plan__price"><span data-gw-show="billing:monthly">$49</span><span data-gw-show="billing:yearly" hidden>$39</span> <small>/ month</small></p>
395
+ <p><span data-gw-show="billing:monthly">Billed monthly through Shopify</span><span data-gw-show="billing:yearly" hidden>Billed $470.40 a year through Shopify</span></p>
392
396
  <a class="gw-btn gw-btn--secondary gw-btn--block" href="https://admin.gemboss.ai/signup">Start 14-day free trial</a>
393
397
  <ul class="gw-list--check">
394
398
  <li>10,000 funnel sessions a month</li>
@@ -401,7 +405,8 @@
401
405
  <span class="gw-badge gw-badge--accent">Recommended for $10k–50k</span>
402
406
  <h3>Growth</h3>
403
407
  <p>Test new angles every week.</p>
404
- <p class="gw-plan__price">$149 <small>/ month</small></p>
408
+ <p class="gw-plan__price"><span data-gw-show="billing:monthly">$149</span><span data-gw-show="billing:yearly" hidden>$119</span> <small>/ month</small></p>
409
+ <p><span data-gw-show="billing:monthly">Billed monthly through Shopify</span><span data-gw-show="billing:yearly" hidden>Billed $1,430.40 a year through Shopify</span></p>
405
410
  <a class="gw-btn gw-btn--block" href="https://admin.gemboss.ai/signup">Start 14-day free trial</a>
406
411
  <ul class="gw-list--check">
407
412
  <li>50,000 funnel sessions a month</li>
@@ -413,7 +418,8 @@
413
418
  <div class="gw-plan">
414
419
  <h3>Scale</h3>
415
420
  <p>Run many angles at once.</p>
416
- <p class="gw-plan__price">$399 <small>/ month</small></p>
421
+ <p class="gw-plan__price"><span data-gw-show="billing:monthly">$399</span><span data-gw-show="billing:yearly" hidden>$319</span> <small>/ month</small></p>
422
+ <p><span data-gw-show="billing:monthly">Billed monthly through Shopify</span><span data-gw-show="billing:yearly" hidden>Billed $3,830.40 a year through Shopify</span></p>
417
423
  <a class="gw-btn gw-btn--secondary gw-btn--block" href="https://admin.gemboss.ai/signup">Start 14-day free trial</a>
418
424
  <ul class="gw-list--check">
419
425
  <li>200,000 funnel sessions a month</li>
@@ -450,7 +456,7 @@
450
456
  <tr><th scope="col">Plan</th><th scope="col">Starter</th><th scope="col">Growth</th><th scope="col">Scale</th><th scope="col">Enterprise</th></tr>
451
457
  </thead>
452
458
  <tbody>
453
- <tr><th scope="row">Price a month</th><td>$49</td><td>$149</td><td>$399</td><td>from $999</td></tr>
459
+ <tr><th scope="row">Price a month</th><td><span data-gw-show="billing:monthly">$49</span><span data-gw-show="billing:yearly" hidden>$39</span></td><td><span data-gw-show="billing:monthly">$149</span><span data-gw-show="billing:yearly" hidden>$119</span></td><td><span data-gw-show="billing:monthly">$399</span><span data-gw-show="billing:yearly" hidden>$319</span></td><td>from $999</td></tr>
454
460
  <tr><th scope="row">Funnel sessions a month</th><td>10,000</td><td>50,000</td><td>200,000</td><td>Custom</td></tr>
455
461
  <tr><th scope="row">Funnels live at once</th><td>3</td><td>15</td><td>Unlimited</td><td>Unlimited</td></tr>
456
462
  <tr><th scope="row">A/B tests at once</th><td>2</td><td>5</td><td>20</td><td>Unlimited</td></tr>
@@ -480,6 +486,20 @@
480
486
  <a class="gw-btn gw-btn--sm" href="https://admin.gemboss.ai/signup">Start free</a>
481
487
  <a class="gw-btn gw-btn--lg" href="https://admin.gemboss.ai/signup">Start 14-day free trial</a>
482
488
  </div>
489
+ <div class="gw-actions demo-mt">
490
+ <button class="gw-btn" type="button" data-demo-busy>Send the request</button>
491
+ <button class="gw-btn gw-btn--secondary" type="button" data-demo-busy>Run the audit</button>
492
+ </div>
493
+ <p class="gw-note demo-mt">Press one: it waits two seconds, keeps its width and ignores a second press.</p>
494
+ </div>
495
+ <div class="demo-specimen">
496
+ <h3>Segmented control</h3>
497
+ <div class="gw-segmented" role="radiogroup" aria-label="How you want to work" data-gw-segmented="work">
498
+ <button type="button" role="radio" aria-checked="true" value="self">Run it yourself</button>
499
+ <button type="button" role="radio" aria-checked="false" value="managed">We run it for you</button>
500
+ </div>
501
+ <p class="demo-mt" data-gw-show="work:self">Self-serve plans from $49 a month. You build, test and promote the funnels.</p>
502
+ <p class="demo-mt" data-gw-show="work:managed" hidden>Managed from $690 a month. A dedicated operator builds and tests the funnels you approve.</p>
483
503
  </div>
484
504
  <div class="demo-specimen">
485
505
  <h3>Links</h3>
@@ -521,6 +541,51 @@
521
541
  </div>
522
542
  </section>
523
543
 
544
+ <!-- prose -->
545
+ <section class="gw-section" aria-labelledby="prose-h">
546
+ <div class="gw-wrap gw-wrap--narrow">
547
+ <div class="gw-section__head">
548
+ <p class="gw-label">Prose</p>
549
+ <h2 id="prose-h"><span class="gw-dim">Blog, docs and legal.</span> Plain HTML, easy to read.</h2>
550
+ <p class="gw-lead">A bare article inside .gw-prose: no class on its headings, lists, quote, code or table.</p>
551
+ </div>
552
+ <article class="gw-prose" aria-label="Why the kit looks the way it does">
553
+ <h2>One language for the site and the product</h2>
554
+ <p>A visitor crosses from the website into the product in one click. If the two disagree, a cream site and a white app, a purple button outside and a black one inside, the seam is exactly where trust is decided. So the website uses the product's language: <a href="https://gemboss.ai/">white canvas, ink actions, one accent</a>, hairlines, almost no shadow.</p>
555
+ <h3>Scales instead of eyeballing</h3>
556
+ <p>On gemboss.ai, before the scales, the four most common paddings were 9, 5, 12 and 15 pixels, and 1,291 of 2,076 spacing values belonged to no scale. The scales make "a bit more space" a choice between two real values:</p>
557
+ <ul>
558
+ <li>Spacing: 0 2 4 6 8 12 16 20 24 32 40 48 64 80 96 120 (px)</li>
559
+ <li>Type: 11 12 13 14 16 18 20 22 24 30, plus the two display sizes</li>
560
+ <li>Radii: 8 for actions, 12 for cards, 16 for panels, fully round for chips</li>
561
+ </ul>
562
+ <blockquote><p>A made-up number or a fake testimonial is found out, and then every other claim on the page is doubted.</p></blockquote>
563
+ <h3>Check before you hand it over</h3>
564
+ <p>Run <code>npx gemboss-website check</code> in the site; <code>--fix-list</code> prints the same findings as a to-do list. Then look at the page at 320px and 1366px.</p>
565
+ <figure>
566
+ <pre><code>npx gemboss-website check
567
+ npx gemboss-website check --fix-list</code></pre>
568
+ <figcaption>The check exits 1 on an error, so it can run in the site's CI too.</figcaption>
569
+ </figure>
570
+ <h3>What a brand changes</h3>
571
+ <table>
572
+ <thead><tr><th scope="col">Variable</th><th scope="col">What it is</th><th scope="col">GemBoss</th></tr></thead>
573
+ <tbody>
574
+ <tr><th scope="row"><code>--gw-accent-light</code></th><td>Links, focus ring, active item, label square</td><td><code>#5661f5</code></td></tr>
575
+ <tr><th scope="row"><code>--gw-accent-strong-light</code></th><td>Accent as text, and on its own tint</td><td><code>#3a44de</code></td></tr>
576
+ <tr><th scope="row"><code>--gw-accent-soft-light</code></th><td>The tint: active chip, chosen option</td><td><code>#eef0fe</code></td></tr>
577
+ <tr><th scope="row"><code>--gw-on-accent-light</code></th><td>Text on a solid accent</td><td><code>#ffffff</code></td></tr>
578
+ </tbody>
579
+ </table>
580
+ <ol>
581
+ <li>Set the eight accent variables once, in a <code>:root</code> block after the kit.</li>
582
+ <li>Check the contrast of the accent as text on white and on the soft ground.</li>
583
+ <li>Look at the site in light and dark, at 320px and 1366px.</li>
584
+ </ol>
585
+ </article>
586
+ </div>
587
+ </section>
588
+
524
589
  <!-- faq -->
525
590
  <section class="gw-section" aria-labelledby="faq-h">
526
591
  <div class="gw-wrap gw-wrap--narrow">
@@ -625,5 +690,14 @@
625
690
  </footer>
626
691
 
627
692
  <script src="../js/website.js" defer></script>
693
+ <script>
694
+ // demo only: what a site's own code does while a request is on its way
695
+ document.addEventListener('click', function (e) {
696
+ var b = e.target.closest('[data-demo-busy]');
697
+ if (!b || b.getAttribute('aria-busy') === 'true') return;
698
+ b.setAttribute('aria-busy', 'true');
699
+ setTimeout(function () { b.removeAttribute('aria-busy'); }, 2000);
700
+ });
701
+ </script>
628
702
  </body>
629
703
  </html>
@@ -8,6 +8,10 @@ Conventions: kit classes start with `gw-`, variants are `--name`, parts are `__n
8
8
  `is-*`. Never style a `.gw-*` class in your site's CSS: if the kit's version is wrong for you, that is a gap
9
9
  (`AGENTS.md` section 9).
10
10
 
11
+ Radii and the button fill follow the site's brand profiles (`docs/theming.md`): below, "radius 8 / 12 / 16" is the
12
+ default `tight` profile, and `soft` makes them 12 / 20 / 24; "ink fill" is the default `ink` action profile, and
13
+ `data-gw-action="accent"` makes it the accent.
14
+
11
15
  Spacing between the children of text blocks is automatic: inside `.gw-section__head`, the hero, a split column,
12
16
  `.gw-cta`, `.gw-card`, `.gw-feature` and `.gw-plan`, each child is 16px below the previous one, a `.gw-actions` row
13
17
  32px, a `.gw-list--check` 24px. Do not add margins to fix rhythm; reorder or remove content instead.
@@ -86,7 +90,8 @@ Don't: more than three night bands on a page (the closing CTA does not count), o
86
90
 
87
91
  ### Buttons `.gw-btn`
88
92
 
89
- 40px tall, radius 8, 14px/600, sentence case, ink fill. Variants: `--secondary` (hairline, transparent), `--ghost`
93
+ 40px tall, the action radius (8, 12 in `soft`), 14px/600, sentence case, the action colour as fill: ink, or the
94
+ brand's accent with `--gw-on-accent` words under `data-gw-action="accent"` (one colour on the whole site). Variants: `--secondary` (hairline, transparent), `--ghost`
90
95
  (no frame), `--on-night` (light fill for a dark surface that is not a `.gw-band--night`), `--sm` (36px, with a 44px
91
96
  hit area), `--lg` (48px, 16px type), `--block` (full width). On touch screens every button is at least 44px tall and
92
97
  its label may wrap. Inside a night band the default button turns light by itself.
@@ -98,8 +103,30 @@ its label may wrap. Inside a night band the default button turns light by itself
98
103
  </div>
99
104
  ```
100
105
 
101
- Don't: an accent, gradient or wash fill; pill-shaped buttons; uppercase; two filled buttons side by side (one
102
- `.gw-btn` per view, the second is `--secondary` or a link); a `<div>` with a click handler instead of `<a>`/`<button>`.
106
+ Pressed (`:active`): the fill one step stronger (the hover fill: the darker ink, or `--gw-accent-strong`), and
107
+ nothing moves; `--secondary` takes the soft tint and
108
+ `--ghost` the hairline tint.
109
+
110
+ **Loading.** While a `<button>` waits on the network (a form sent, an audit started), set `aria-busy="true"` on it and
111
+ remove it when the answer comes. The label stays in the markup, so the button keeps its width and its accessible name,
112
+ but it is not painted: a 16px ring in the button's own ink turns in its place, and under `prefers-reduced-motion` it is
113
+ a still ellipsis. `website.js` ignores presses on a busy button, by mouse or keyboard, so nothing is sent twice. When
114
+ the wait ends, say what happened in words (a `.gw-note` or a `role="status"` line next to it).
115
+
116
+ ```html
117
+ <button class="gw-btn" type="submit" aria-busy="true">Send the request</button>
118
+ ```
119
+
120
+ ```js
121
+ button.setAttribute('aria-busy', 'true'); // the request leaves
122
+ button.removeAttribute('aria-busy'); // the answer came
123
+ ```
124
+
125
+ Don't: a fill of your own (the accent comes only from the action profile, set on `<html>`), a gradient or a wash; a
126
+ raised or inset shadow; pill-shaped buttons; uppercase; two filled buttons side by side (one
127
+ `.gw-btn` per view, the second is `--secondary` or a link); a `<div>` with a click handler instead of `<a>`/`<button>`;
128
+ a loading class, your own spinner or `disabled` on a waiting button (`check` reports `busy-button`: `disabled` throws
129
+ the keyboard focus to the top of the page); a "Loading…" label in place of the real one.
103
130
 
104
131
  ### Chips `.gw-chip`
105
132
 
@@ -112,6 +139,37 @@ hairline on accent-soft). As a `<button>`, `<a>` or `<label>` it is 44px tall on
112
139
 
113
140
  Don't: use a chip as the page's action; square it; more than about six in a row.
114
141
 
142
+ ### Segmented control `.gw-segmented`
143
+
144
+ One choice out of two to four, shown as one group: Monthly / Yearly on a pricing page, Run it yourself / We run it for
145
+ you. It is a radio group: one Tab stop, the arrow keys (and Home, End) move and choose, a click or Space chooses. The
146
+ look is the dashboard's (`@gemboss/ui` `.ui-seg`): one hairline group, the action radius, a hairline between options, the chosen
147
+ option on the accent tint, so it follows a brand's accent. 40px tall; on touch screens every option is at least 44px
148
+ and its label may wrap. `--block` stretches it to the row and shares the width equally.
149
+
150
+ What an option shows: any element with `data-gw-show="group:value"` (the group's `data-gw-segmented` name, the option's
151
+ `value`) is hidden unless that option is chosen. Put every version in the markup and `hidden` on all but the chosen
152
+ one's: without the script the page reads with the default choice, with it `website.js` swaps them. An element may
153
+ belong to several options (`data-gw-show="billing:monthly billing:quarterly"`). A choice also fires `gw:segmented` on
154
+ the group (it bubbles; `detail` is `{ name, value }`) for the page's own script, for example to move a "Recommended"
155
+ badge.
156
+
157
+ ```html
158
+ <div class="gw-segmented" role="radiogroup" aria-label="Billing period" data-gw-segmented="billing">
159
+ <button type="button" role="radio" aria-checked="true" value="monthly">Monthly</button>
160
+ <button type="button" role="radio" aria-checked="false" value="yearly">Yearly –20%</button>
161
+ </div>
162
+ <p class="gw-plan__price"><span data-gw-show="billing:monthly">$49</span><span data-gw-show="billing:yearly" hidden>$39</span> <small>/ month</small></p>
163
+ ```
164
+
165
+ Labels: one to three words, as short in every language as in English (gemboss.ai: "Tháng / Năm –20%", "按月 / 按年
166
+ –20%"). The chosen label does not change weight, so nothing moves. A discount in a label is the one billing gives.
167
+
168
+ Don't: links dressed as a segmented control (to go to another page, use links); panels of one page that the reader
169
+ should be able to see side by side; more than four options (use chips); a choice that changes nothing visible; two
170
+ groups with the same name on one page; the old `role="group"` with `aria-pressed` buttons (`check` reports
171
+ `segmented-markup`).
172
+
115
173
  ### Badges `.gw-badge`
116
174
 
117
175
  Small status, a pill, 12px. Variants: `--accent` (a test, the recommended plan), `--success` and `--warning` (state:
@@ -125,7 +183,7 @@ Don't: make a badge clickable; write a sentence in it; use success or warning fo
125
183
 
126
184
  ### Cards `.gw-card`
127
185
 
128
- A white box with a 1px hairline, radius 12, the resting shadow `--gw-shadow` (as the dashboard's cards), 24px padding
186
+ A white box with a 1px hairline, the card radius (12, 20 in `soft`), the resting shadow `--gw-shadow` (as the dashboard's cards), 24px padding
129
187
  (32px from 768px). It sits: no hover lift. `--soft` is a tint and has no shadow.
130
188
  Variants: `--soft` (on `--gw-bg-soft`), `--link` (the whole card is one `<a>`; the border darkens on hover).
131
189
 
@@ -137,22 +195,32 @@ Variants: `--soft` (on `--gw-bg-soft`), `--link` (the whole card is one `<a>`; t
137
195
  </a>
138
196
  ```
139
197
 
140
- Don't: put links or buttons inside a `--link` card (one card, one destination); a bigger shadow or a hover lift; radius
141
- other than 12 (16 is for large panels: plans, quote).
198
+ Don't: put links or buttons inside a `--link` card (one card, one destination); a bigger shadow or a hover lift; a
199
+ radius of your own (cards take the card radius, plans and the quote the panel radius, both from the profile).
142
200
 
143
201
  ### Window `.gw-window`
144
202
 
145
- A framed screen: `__bar` (three dots and where the screen lives) and `__body` (16px padding, none around a single
146
- `<img>`). Use it around real product screenshots, or around a drawn mock-up built in the site's CSS with the tokens.
203
+ A framed screen: `__bar` (three dots and where the screen lives), `__body` (16px padding, none around a single
204
+ `<img>`) and `__caption`. Use it around real product screenshots, or around a drawn mock-up built in the site's CSS with
205
+ the tokens.
206
+
207
+ `__caption` says what the numbers in the window are: 12px, muted, at the end of the window, the last child after
208
+ `__body` (a `<figcaption>` when the window is a `<figure>`). Under a drawn mock-up whose numbers are made up it says
209
+ **"Illustrative data."**, in exactly these words: vi "Dữ liệu minh hoạ.", zh "示意数据。". Add a second sentence only when
210
+ the mock-up shows a demo brand ("Vital Glow is a GemBoss demo brand."). Under real numbers it names their source
211
+ ("Measured on a live GemBoss store, 10 Oct 2026."). `check` warns about a drawn window with numbers and no caption
212
+ (`illustrative-caption`).
147
213
 
148
214
  ```html
149
215
  <div class="gw-window">
150
216
  <div class="gw-window__bar">Funnels / Radiance Serum</div>
151
217
  <div class="gw-window__body"><img src="/img/funnels.webp" alt="The funnel list with four steps" width="1200" height="750"></div>
218
+ <p class="gw-window__caption">Illustrative data. Vital Glow is a GemBoss demo brand.</p>
152
219
  </div>
153
220
  ```
154
221
 
155
- Don't: put interactive controls inside a mock-up; show made-up numbers without "Illustrative" under them.
222
+ Don't: put interactive controls inside a mock-up; show made-up numbers without the caption; write the caption in
223
+ other words ("Sample", "Demo numbers") or leave it in English on a translated page.
156
224
 
157
225
  ### Icon `.gw-icon`
158
226
 
@@ -177,7 +245,7 @@ Don't: more than five or six items; full paragraphs as items.
177
245
 
178
246
  ### Table `.gw-table`
179
247
 
180
- A wrapper around a plain `<table>`: hairline, radius 12, and it scrolls inside its own box when the table is wider
248
+ A wrapper around a plain `<table>`: hairline, the card radius, and it scrolls inside its own box when the table is wider
181
249
  than the screen. `--compare`: centred values, a 640px minimum width and a sticky first column, for plan comparisons.
182
250
  Give the wrapper `role="region"`, an accessible name and `tabindex="0"` so keyboard users can scroll it.
183
251
 
@@ -204,7 +272,7 @@ Native `<details class="gw-faq__item">` with a `<summary>`, no script. The + tur
204
272
 
205
273
  Don't: hide content a reader needs to decide (prices, terms) only inside a closed answer; more than eight questions.
206
274
 
207
- ### Not in 0.1.0
275
+ ### Not in the kit yet
208
276
 
209
277
  `.gw-tabs` does not exist yet. If a page needs tabs, build a stopgap (`kitgap-tabs`) and report the gap.
210
278
 
@@ -214,10 +282,11 @@ Don't: hide content a reader needs to decide (prices, terms) only inside a close
214
282
  |---|---|
215
283
  | `.gw-announce` | optional one-sentence bar above the header, with one link |
216
284
  | `.gw-header` (`data-gw-header`) | the sticky, flat 64px bar with one hairline; `.is-scrolled` darkens the hairline |
285
+ | `.gw-header--float` | with `.gw-header`, a brand's header profile: from 1080px the same bar as an inset card (panel radius, the surface at 85% over a blur, one hairline, the raised shadow, 16px from the top); the flat bar below 1080px; solid under `prefers-reduced-transparency` |
217
286
  | `.gw-header__bar` | the row inside (`.gw-wrap gw-header__bar`) |
218
287
  | `.gw-brand`, `__mark`, `__name` | logo (26px) and name, linking home |
219
288
  | `.gw-nav`, `__list`, `__item`, `__trigger`, `__link` | the desktop nav (from 1080px); `__trigger` opens a mega panel, `__link` is a plain link; `aria-current="page"` underlines it in the accent |
220
- | `.gw-mega`, `__grid`, `__col`, `__heading`, `__link`, `__icon`, `__feature` | a floating card centred under the bar (radius 20, raised shadow, as on gemboss.ai): columns of links with an icon and one line each, and at most one feature card |
289
+ | `.gw-mega`, `__grid`, `__col`, `__heading`, `__link`, `__icon`, `__feature` | a floating card centred under the bar (the popover radius: 20, 24 in `soft`; raised shadow, as on gemboss.ai): columns of links with an icon and one line each, and at most one feature card |
221
290
  | `.gw-header__actions` | theme toggle, Sign in, Book a demo, Start free, menu button |
222
291
  | `.gw-header__signin` | with `.gw-nav__link`: hidden below 1080px (it moves into the mobile menu) |
223
292
  | `.gw-theme-toggle` | icon button (`data-gw-theme-toggle`), 40px, 44px on touch |
@@ -231,7 +300,8 @@ Don't: hide content a reader needs to decide (prices, terms) only inside a close
231
300
  Header buttons: the secondary one shows from 768px, the primary one from 480px; below that they live in the mobile
232
301
  menu. Full markup: `templates/header.html`.
233
302
 
234
- Don't: a floating, rounded or blurred header; more than five top-level items; a second filled button in the bar; a
303
+ Don't: a floating, rounded or blurred header of your own (the one floating header is `.gw-header--float`, chosen
304
+ for the whole site); more than five top-level items; a second filled button in the bar; a
235
305
  hero pulled up under the header.
236
306
 
237
307
  ## Footer
@@ -282,6 +352,48 @@ One snippet per section, with its limits in a comment, is in `templates/sections
282
352
  Don't: a number without a source; logos or quotes you do not have permission for; a filled button on every plan
283
353
  (only the featured one); a hero without a real first line of content above the fold on a 390px phone.
284
354
 
355
+ ## Prose
356
+
357
+ | class | for |
358
+ |---|---|
359
+ | `.gw-prose` | a page that is read rather than scanned (a blog post, a docs page, terms, privacy): the article's own HTML inside it, 18px body, 68ch measure |
360
+ | `.gw-prose--sm` | the same at a 16px body, for docs and legal pages |
361
+
362
+ The HTML a CMS or a markdown renderer writes needs no classes. Inside `.gw-prose`:
363
+
364
+ | element | look |
365
+ |---|---|
366
+ | `h1` | the article title, at the section-headline size (`--gw-display-h2`) |
367
+ | `h2`, `h3`, `h4` | 30, 24, 20px (24, 20, 18px in `--sm`), weight 650; 48, 40, 32px of air above, 12px below |
368
+ | `p`, `ul`, `ol` | 20px between blocks (16px in `--sm`); bullets and numbers with muted markers, 8px between items |
369
+ | `blockquote` | a 3px rule on its left, muted words (the dashboard's reader quote) |
370
+ | `code` | a tint chip in `--gw-font-mono` (16px, 14px in `--sm`) that wraps on a phone; in a table it stays whole |
371
+ | `pre` | a soft box, 14px mono, that scrolls sideways inside itself |
372
+ | `table` | as a direct child: the `.gw-table` look, and it scrolls inside its own box when it is wider than the column |
373
+ | `img`, `figure`, `figcaption` | images take the card radius; a caption 14px muted, 12px under its figure |
374
+ | `hr`, `a` | a hairline with 32px of air; links in the accent, underlined |
375
+
376
+ Kit components inside the prose keep their own look (`.gw-table`, `.gw-window`, `.gw-list--check`, `.gw-quote`,
377
+ `.gw-btn`). Prefer the `.gw-table` wrapper (`role="region"`, a name, `tabindex="0"`) when you write the markup: a
378
+ keyboard can scroll it.
379
+
380
+ ```html
381
+ <section class="gw-section">
382
+ <div class="gw-wrap gw-wrap--narrow">
383
+ <article class="gw-prose">
384
+ <h1>Why the kit looks the way it does</h1>
385
+ <p>Each rule came from measuring real pages, not from taste alone.</p>
386
+ <h2>One language for the site and the product</h2>
387
+ <p>A visitor crosses from the website into the product in one click. …</p>
388
+ <ul><li>…</li></ul>
389
+ </article>
390
+ </div>
391
+ </section>
392
+ ```
393
+
394
+ Don't: marketing sections inside the prose (end the article, then the section); headings chosen for their size (keep
395
+ them in order); a page without `.gw-prose` that styles its articles in site CSS (that was the stopgap of gap #46).
396
+
285
397
  ## Motion
286
398
 
287
399
  | class | for |
@@ -291,6 +403,17 @@ Don't: a number without a source; logos or quotes you do not have permission for
291
403
  Put it on a block (a grid, a list, a window), not on each card in a grid. Don't use it on the hero or anything in
292
404
  the first screen, and don't animate anything but `transform` and `opacity`.
293
405
 
406
+ ## Additions to the markup contract in 0.3.0
407
+
408
+ The brand profiles: `data-gw-action="ink|accent"` and `data-gw-radius="tight|soft"` on `<html>`, and
409
+ `.gw-header--float` on the header (`docs/theming.md`). Tokens: `--gw-radius-popover` (the mega menu), `--gw-radius-24`,
410
+ `--gw-header-gap`, `--gw-header-surface`, `--gw-header-blur`. No markup of 0.2.0 changes.
411
+
412
+ ## Additions to the markup contract in 0.2.0
413
+
414
+ The button states (`:active`, `[aria-busy="true"]`), `.gw-segmented` (+ `--block`) with its `data-gw-segmented` /
415
+ `data-gw-show` hooks, `.gw-window__caption` and `.gw-prose` (+ `--sm`). Two tokens: `--gw-font-mono` and `--gw-spin-ms`.
416
+
294
417
  ## Additions to the markup contract in 0.1.0
295
418
 
296
419
  The contract names the classes the script relies on; these CSS-only helpers were added with it: `.gw-page`
@@ -12,7 +12,9 @@ BEM-ish: `.gw-block`, `.gw-block__part`, `.gw-block--variant`.
12
12
  ## Page
13
13
 
14
14
  ```html
15
- <html lang="en" data-theme?="light|dark"> <!-- absent = follow the system; the toggle sets it -->
15
+ <html lang="en" data-theme?="light|dark" data-gw-action?="accent" data-gw-radius?="soft">
16
+ <!-- data-theme absent = follow the system; the toggle sets it. data-gw-action, data-gw-radius: brand profiles (0.3.0),
17
+ absent = ink, tight -->
16
18
  <body class="gw-page">
17
19
  <a class="gw-skip" href="#main">Skip to content</a>
18
20
  <div class="gw-announce" role="region" aria-label="Announcement">…<a href="…">…</a></div> <!-- optional -->
@@ -25,7 +27,7 @@ BEM-ish: `.gw-block`, `.gw-block__part`, `.gw-block--variant`.
25
27
  ## Header
26
28
 
27
29
  ```html
28
- <header class="gw-header" data-gw-header>
30
+ <header class="gw-header gw-header--float?" data-gw-header> <!-- --float: a brand's header profile (0.3.0) -->
29
31
  <div class="gw-wrap gw-header__bar">
30
32
  <a class="gw-brand" href="/"><img class="gw-brand__mark" src="…" alt="" width="26" height="26"><span class="gw-brand__name">GemBoss</span></a>
31
33
  <nav class="gw-nav" aria-label="Main">
@@ -73,27 +75,69 @@ Script behaviour:
73
75
  2.5s, or if IntersectionObserver is missing, all get `.is-in`. Under `prefers-reduced-motion` the CSS shows them
74
76
  without motion.
75
77
  - **Header**: `.is-scrolled` on `[data-gw-header]` once the page scrolled more than 8px (the CSS may use it for the
76
- hairline only; the bar does not change height or colour).
78
+ hairline only; the bar does not change height or colour). The floating header needs nothing more from the script:
79
+ it is the same element with one more class.
77
80
  - **Current page**: the script marks `aria-current="page"` on `.gw-nav__link` / `.gw-mega__link` whose `href`
78
81
  pathname equals `location.pathname` (when the markup did not already).
79
82
 
83
+ ## Brand profiles (0.3.0)
84
+
85
+ - `data-gw-action` and `data-gw-radius` sit on `<html>` only, with a name from `system.json` `profiles` (`ink` or
86
+ `accent`, `tight` or `soft`); absent means the first. The CSS reads them (`css/tokens.css`), the script does not.
87
+ - `.gw-header--float` sits on the `<header class="gw-header">` itself, next to `gw-header`.
88
+ - `check` reports any other name or place (`profile-unknown`, `header-float-markup`) and, with `accent`, words on the
89
+ button below 4.5:1 (`action-contrast`).
90
+
91
+ ## Segmented control (0.2.0)
92
+
93
+ ```html
94
+ <div class="gw-segmented" role="radiogroup" aria-label="Billing period" data-gw-segmented="billing">
95
+ <button type="button" role="radio" aria-checked="true" value="monthly">Monthly</button>
96
+ <button type="button" role="radio" aria-checked="false" value="yearly">Yearly –20%</button>
97
+ </div>
98
+ …
99
+ <span data-gw-show="billing:monthly">$49</span><span data-gw-show="billing:yearly" hidden>$39</span>
100
+ ```
101
+
102
+ - The group: `.gw-segmented` (+ `--block`), `role="radiogroup"`, a name (`aria-label` or `aria-labelledby`) and
103
+ `data-gw-segmented="<name>"`, unique on the page. Two to four `<button type="button" role="radio">` children, each
104
+ with a `value` and `aria-checked`, exactly one `"true"`: the choice the page shows without the script.
105
+ - Content: any element with `data-gw-show="<name>:<value>"` (several tokens, space-separated, for several options) is
106
+ shown only while that option is chosen. The markup sets `hidden` on every version but the default's.
107
+ - Script behaviour: on load the checked option (or the first) is chosen again, which corrects `hidden` on the content
108
+ and sets the roving `tabindex` (`0` on the chosen option, `-1` on the others: one Tab stop). A click chooses;
109
+ ArrowRight / ArrowDown and ArrowLeft / ArrowUp move to the next and previous option, wrapping, Home and End to the
110
+ first and last, and the moved-to option is chosen and focused (the radio-group pattern). Each choice made by the
111
+ reader fires `gw:segmented` on the group (`bubbles: true`, `detail: { name, value }`); loading does not.
112
+
113
+ ## Busy button (0.2.0)
114
+
115
+ - Markup: `aria-busy="true"` on a `<button class="gw-btn">` while it waits; the page's own script sets and removes it.
116
+ No `disabled` at the same time, no loading class, no spinner element: the CSS draws the ring (`::before`; `--sm`
117
+ keeps `::after` for its hit area).
118
+ - Script behaviour: one click listener on the document, in the capture phase, cancels a click on a
119
+ `.gw-btn[aria-busy="true"]` (or inside one) before the page's handlers see it: `preventDefault` and
120
+ `stopPropagation`. A form is not submitted twice, by mouse, Enter or Space, and the button keeps the focus.
121
+
80
122
  ## Components (CSS only)
81
123
 
82
124
  | class | what |
83
125
  |---|---|
84
- | `.gw-btn` (+ `--secondary`, `--ghost`, `--on-night`, `--sm`, `--lg`, `--block`) | actions; rectangle 8px, ink fill by default |
126
+ | `.gw-btn` (+ `--secondary`, `--ghost`, `--on-night`, `--sm`, `--lg`, `--block`) | actions; a rectangle with the action radius, the action colour as fill (ink, or the accent under `data-gw-action="accent"`); `:active` pressed, `[aria-busy="true"]` loading (script above) |
85
127
  | `.gw-link` (+ `--arrow`) | inline text link in the accent; `--arrow` adds → that moves 2px on hover |
86
128
  | `.gw-label` | the label above a heading: 13px muted, 8px accent square before it |
87
129
  | `.gw-dim` | the muted first sentence inside a headline |
88
130
  | `.gw-chip` (+ `.is-active`) | selectable token, pill |
131
+ | `.gw-segmented` (+ `--block`) | one choice out of two to four (script above) |
89
132
  | `.gw-badge` (+ `--accent`, `--success`, `--warning`) | small status, pill |
90
- | `.gw-card` (+ `--soft`, `--link`) | white box, 1px hairline, radius 12; `--link` makes the whole card an `<a>` |
91
- | `.gw-window` (+ `__bar`, `__body`) | a framed screenshot/mock-up, like a browser or app window |
133
+ | `.gw-card` (+ `--soft`, `--link`) | white box, 1px hairline, the card radius; `--link` makes the whole card an `<a>` |
134
+ | `.gw-window` (+ `__bar`, `__body`, `__caption`) | a framed screenshot/mock-up, like a browser or app window; `__caption` (last child) says what its numbers are: "Illustrative data." or their source |
92
135
  | `.gw-icon` | 20px inline stroke SVG wrapper |
93
136
  | `.gw-list--check` | list with check marks |
94
137
  | `.gw-table` (+ `--compare`) | data / comparison table; scrolls inside its box on phones |
95
138
  | `.gw-faq` (`<details class="gw-faq__item"><summary>`) | FAQ accordion with no script |
96
- | `.gw-tabs` (`role="tablist"`) | NOT in 0.1.0 (a reported gap would add it) |
139
+ | `.gw-prose` (+ `--sm`) | long-form reading: an article's own HTML, no classes inside |
140
+ | `.gw-tabs` (`role="tablist"`) | NOT in the kit yet (a reported gap would add it) |
97
141
 
98
142
  ## Sections
99
143
 
@@ -7,26 +7,31 @@ spirit. Each rule came from measuring real pages, not from taste alone.
7
7
 
8
8
  A visitor crosses from the website into the product in one click. If the two disagree (a cream site and a white app,
9
9
  a purple button outside and a black one inside), the seam is exactly where trust is decided. So the website uses the
10
- product's language: white canvas, ink actions, one accent, hairlines, almost no shadow.
10
+ product's language: white canvas, the product's action colour, one accent, hairlines, almost no shadow.
11
11
 
12
- ## The thing you click is ink
12
+ ## The thing you click is one colour: the product's
13
13
 
14
- A page has one thing it wants you to do. Near-black is the strongest mark on white and stays readable in every
15
- light; the accent colour is louder but weaker as an instruction, and a page full of accent-coloured buttons has no
16
- hierarchy left. The accent marks where you are and what is interactive in text: links, focus, the active item, the
17
- small square before a label.
14
+ A page has one thing it wants you to do, and the button that does it looks the same on every page. For GemBoss that
15
+ is near-black ink, as in its dashboard: the strongest mark on white, readable in every light. Since 0.3.0 a brand
16
+ whose product clicks in its accent (GemPages' product uses blue primary buttons) may do the same on its site, with
17
+ the `accent` action profile: the site and the product still speak one language. It is a mode, not a colour picked
18
+ per button; its words must reach 4.5:1 on it, and it never gains a shadow or a gradient. Everywhere else the accent
19
+ marks where you are and what is interactive in text: links, focus, the active item, the small square before a
20
+ label. It never colours the words of a heading or a whole section band: that makes a page louder, not clearer.
18
21
 
19
22
  ## Shapes carry meaning
20
23
 
21
24
  Rectangles act, pills choose. When every button is a rectangle and every chip is a pill, people stop reading the
22
- shape and read the words. A radius scale with seven steps means two cards side by side never differ by 2px, which is
23
- the kind of drift that makes a page feel assembled rather than designed.
25
+ shape and read the words. A site has one set of corners, a named profile (`tight` for GemBoss, `soft` for GemPages,
26
+ as in each product), so two cards side by side never differ by 2px, which is the kind of drift that makes a page feel
27
+ assembled rather than designed. Profiles, not free values: a brand can look like itself without inventing a radius.
24
28
 
25
29
  ## Separation by line and space, light only at rest
26
30
 
27
31
  A shadow says "this floats above". Cards on a page do not float; they sit on it. A 1px hairline and the dashboard's
28
32
  resting shadow (two faint pixels) separate a card and keep the page calm; nothing lifts on hover. Larger light is
29
- spent where it means something: a popover such as the mega menu, an illustration that lights itself.
33
+ spent where it means something: a popover such as the mega menu, the floating header a brand may choose (it does
34
+ float), an illustration that lights itself.
30
35
 
31
36
  ## Scales instead of eyeballing
32
37