@gemboss/website-ui 0.1.0 → 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.
- package/AGENTS.md +23 -6
- package/CHANGELOG.md +28 -0
- package/README.md +8 -7
- package/bin/lib/check.mjs +53 -0
- package/css/base.css +3 -3
- package/css/components.css +88 -0
- package/css/layout.css +1 -1
- package/css/prose.css +119 -0
- package/css/tokens.css +2 -0
- package/demo/index.html +83 -9
- package/docs/components.md +113 -5
- package/docs/markup-contract.md +36 -3
- package/docs/theming.md +3 -2
- package/js/website.js +39 -2
- package/package.json +1 -1
- package/skill/SKILL.md +2 -1
- package/system.json +3 -1
- package/templates/sections/hero.html +3 -1
- package/templates/sections/pricing.html +15 -3
- package/templates/sections/prose.html +36 -0
- package/templates/sections/showcase.html +3 -2
- package/templates/sections/split.html +3 -1
- package/website.css +215 -5
- package/website.min.css +1 -1
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>
|
package/docs/components.md
CHANGED
|
@@ -98,8 +98,28 @@ its label may wrap. Inside a night band the default button turns light by itself
|
|
|
98
98
|
</div>
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
Pressed (`:active`): the ink one step darker (the hover ink), and nothing moves; `--secondary` takes the soft tint and
|
|
102
|
+
`--ghost` the hairline tint.
|
|
103
|
+
|
|
104
|
+
**Loading.** While a `<button>` waits on the network (a form sent, an audit started), set `aria-busy="true"` on it and
|
|
105
|
+
remove it when the answer comes. The label stays in the markup, so the button keeps its width and its accessible name,
|
|
106
|
+
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
|
|
107
|
+
a still ellipsis. `website.js` ignores presses on a busy button, by mouse or keyboard, so nothing is sent twice. When
|
|
108
|
+
the wait ends, say what happened in words (a `.gw-note` or a `role="status"` line next to it).
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<button class="gw-btn" type="submit" aria-busy="true">Send the request</button>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
button.setAttribute('aria-busy', 'true'); // the request leaves
|
|
116
|
+
button.removeAttribute('aria-busy'); // the answer came
|
|
117
|
+
```
|
|
118
|
+
|
|
101
119
|
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
|
|
120
|
+
`.gw-btn` per view, the second is `--secondary` or a link); a `<div>` with a click handler instead of `<a>`/`<button>`;
|
|
121
|
+
a loading class, your own spinner or `disabled` on a waiting button (`check` reports `busy-button`: `disabled` throws
|
|
122
|
+
the keyboard focus to the top of the page); a "Loading…" label in place of the real one.
|
|
103
123
|
|
|
104
124
|
### Chips `.gw-chip`
|
|
105
125
|
|
|
@@ -112,6 +132,37 @@ hairline on accent-soft). As a `<button>`, `<a>` or `<label>` it is 44px tall on
|
|
|
112
132
|
|
|
113
133
|
Don't: use a chip as the page's action; square it; more than about six in a row.
|
|
114
134
|
|
|
135
|
+
### Segmented control `.gw-segmented`
|
|
136
|
+
|
|
137
|
+
One choice out of two to four, shown as one group: Monthly / Yearly on a pricing page, Run it yourself / We run it for
|
|
138
|
+
you. It is a radio group: one Tab stop, the arrow keys (and Home, End) move and choose, a click or Space chooses. The
|
|
139
|
+
look is the dashboard's (`@gemboss/ui` `.ui-seg`): one hairline group, radius 8, a hairline between options, the chosen
|
|
140
|
+
option on the accent tint, so it follows a brand's accent. 40px tall; on touch screens every option is at least 44px
|
|
141
|
+
and its label may wrap. `--block` stretches it to the row and shares the width equally.
|
|
142
|
+
|
|
143
|
+
What an option shows: any element with `data-gw-show="group:value"` (the group's `data-gw-segmented` name, the option's
|
|
144
|
+
`value`) is hidden unless that option is chosen. Put every version in the markup and `hidden` on all but the chosen
|
|
145
|
+
one's: without the script the page reads with the default choice, with it `website.js` swaps them. An element may
|
|
146
|
+
belong to several options (`data-gw-show="billing:monthly billing:quarterly"`). A choice also fires `gw:segmented` on
|
|
147
|
+
the group (it bubbles; `detail` is `{ name, value }`) for the page's own script, for example to move a "Recommended"
|
|
148
|
+
badge.
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<div class="gw-segmented" role="radiogroup" aria-label="Billing period" data-gw-segmented="billing">
|
|
152
|
+
<button type="button" role="radio" aria-checked="true" value="monthly">Monthly</button>
|
|
153
|
+
<button type="button" role="radio" aria-checked="false" value="yearly">Yearly –20%</button>
|
|
154
|
+
</div>
|
|
155
|
+
<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>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Labels: one to three words, as short in every language as in English (gemboss.ai: "Tháng / Năm –20%", "按月 / 按年
|
|
159
|
+
–20%"). The chosen label does not change weight, so nothing moves. A discount in a label is the one billing gives.
|
|
160
|
+
|
|
161
|
+
Don't: links dressed as a segmented control (to go to another page, use links); panels of one page that the reader
|
|
162
|
+
should be able to see side by side; more than four options (use chips); a choice that changes nothing visible; two
|
|
163
|
+
groups with the same name on one page; the old `role="group"` with `aria-pressed` buttons (`check` reports
|
|
164
|
+
`segmented-markup`).
|
|
165
|
+
|
|
115
166
|
### Badges `.gw-badge`
|
|
116
167
|
|
|
117
168
|
Small status, a pill, 12px. Variants: `--accent` (a test, the recommended plan), `--success` and `--warning` (state:
|
|
@@ -142,17 +193,27 @@ other than 12 (16 is for large panels: plans, quote).
|
|
|
142
193
|
|
|
143
194
|
### Window `.gw-window`
|
|
144
195
|
|
|
145
|
-
A framed screen: `__bar` (three dots and where the screen lives)
|
|
146
|
-
`<img>`)
|
|
196
|
+
A framed screen: `__bar` (three dots and where the screen lives), `__body` (16px padding, none around a single
|
|
197
|
+
`<img>`) and `__caption`. Use it around real product screenshots, or around a drawn mock-up built in the site's CSS with
|
|
198
|
+
the tokens.
|
|
199
|
+
|
|
200
|
+
`__caption` says what the numbers in the window are: 12px, muted, at the end of the window, the last child after
|
|
201
|
+
`__body` (a `<figcaption>` when the window is a `<figure>`). Under a drawn mock-up whose numbers are made up it says
|
|
202
|
+
**"Illustrative data."**, in exactly these words: vi "Dữ liệu minh hoạ.", zh "示意数据。". Add a second sentence only when
|
|
203
|
+
the mock-up shows a demo brand ("Vital Glow is a GemBoss demo brand."). Under real numbers it names their source
|
|
204
|
+
("Measured on a live GemBoss store, 10 Oct 2026."). `check` warns about a drawn window with numbers and no caption
|
|
205
|
+
(`illustrative-caption`).
|
|
147
206
|
|
|
148
207
|
```html
|
|
149
208
|
<div class="gw-window">
|
|
150
209
|
<div class="gw-window__bar">Funnels / Radiance Serum</div>
|
|
151
210
|
<div class="gw-window__body"><img src="/img/funnels.webp" alt="The funnel list with four steps" width="1200" height="750"></div>
|
|
211
|
+
<p class="gw-window__caption">Illustrative data. Vital Glow is a GemBoss demo brand.</p>
|
|
152
212
|
</div>
|
|
153
213
|
```
|
|
154
214
|
|
|
155
|
-
Don't: put interactive controls inside a mock-up; show made-up numbers without
|
|
215
|
+
Don't: put interactive controls inside a mock-up; show made-up numbers without the caption; write the caption in
|
|
216
|
+
other words ("Sample", "Demo numbers") or leave it in English on a translated page.
|
|
156
217
|
|
|
157
218
|
### Icon `.gw-icon`
|
|
158
219
|
|
|
@@ -204,7 +265,7 @@ Native `<details class="gw-faq__item">` with a `<summary>`, no script. The + tur
|
|
|
204
265
|
|
|
205
266
|
Don't: hide content a reader needs to decide (prices, terms) only inside a closed answer; more than eight questions.
|
|
206
267
|
|
|
207
|
-
### Not in
|
|
268
|
+
### Not in the kit yet
|
|
208
269
|
|
|
209
270
|
`.gw-tabs` does not exist yet. If a page needs tabs, build a stopgap (`kitgap-tabs`) and report the gap.
|
|
210
271
|
|
|
@@ -282,6 +343,48 @@ One snippet per section, with its limits in a comment, is in `templates/sections
|
|
|
282
343
|
Don't: a number without a source; logos or quotes you do not have permission for; a filled button on every plan
|
|
283
344
|
(only the featured one); a hero without a real first line of content above the fold on a 390px phone.
|
|
284
345
|
|
|
346
|
+
## Prose
|
|
347
|
+
|
|
348
|
+
| class | for |
|
|
349
|
+
|---|---|
|
|
350
|
+
| `.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 |
|
|
351
|
+
| `.gw-prose--sm` | the same at a 16px body, for docs and legal pages |
|
|
352
|
+
|
|
353
|
+
The HTML a CMS or a markdown renderer writes needs no classes. Inside `.gw-prose`:
|
|
354
|
+
|
|
355
|
+
| element | look |
|
|
356
|
+
|---|---|
|
|
357
|
+
| `h1` | the article title, at the section-headline size (`--gw-display-h2`) |
|
|
358
|
+
| `h2`, `h3`, `h4` | 30, 24, 20px (24, 20, 18px in `--sm`), weight 650; 48, 40, 32px of air above, 12px below |
|
|
359
|
+
| `p`, `ul`, `ol` | 20px between blocks (16px in `--sm`); bullets and numbers with muted markers, 8px between items |
|
|
360
|
+
| `blockquote` | a 3px rule on its left, muted words (the dashboard's reader quote) |
|
|
361
|
+
| `code` | a tint chip in `--gw-font-mono` (16px, 14px in `--sm`) that wraps on a phone; in a table it stays whole |
|
|
362
|
+
| `pre` | a soft box, 14px mono, that scrolls sideways inside itself |
|
|
363
|
+
| `table` | as a direct child: the `.gw-table` look, and it scrolls inside its own box when it is wider than the column |
|
|
364
|
+
| `img`, `figure`, `figcaption` | images radius 12; a caption 14px muted, 12px under its figure |
|
|
365
|
+
| `hr`, `a` | a hairline with 32px of air; links in the accent, underlined |
|
|
366
|
+
|
|
367
|
+
Kit components inside the prose keep their own look (`.gw-table`, `.gw-window`, `.gw-list--check`, `.gw-quote`,
|
|
368
|
+
`.gw-btn`). Prefer the `.gw-table` wrapper (`role="region"`, a name, `tabindex="0"`) when you write the markup: a
|
|
369
|
+
keyboard can scroll it.
|
|
370
|
+
|
|
371
|
+
```html
|
|
372
|
+
<section class="gw-section">
|
|
373
|
+
<div class="gw-wrap gw-wrap--narrow">
|
|
374
|
+
<article class="gw-prose">
|
|
375
|
+
<h1>Why the kit looks the way it does</h1>
|
|
376
|
+
<p>Each rule came from measuring real pages, not from taste alone.</p>
|
|
377
|
+
<h2>One language for the site and the product</h2>
|
|
378
|
+
<p>A visitor crosses from the website into the product in one click. …</p>
|
|
379
|
+
<ul><li>…</li></ul>
|
|
380
|
+
</article>
|
|
381
|
+
</div>
|
|
382
|
+
</section>
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Don't: marketing sections inside the prose (end the article, then the section); headings chosen for their size (keep
|
|
386
|
+
them in order); a page without `.gw-prose` that styles its articles in site CSS (that was the stopgap of gap #46).
|
|
387
|
+
|
|
285
388
|
## Motion
|
|
286
389
|
|
|
287
390
|
| class | for |
|
|
@@ -291,6 +394,11 @@ Don't: a number without a source; logos or quotes you do not have permission for
|
|
|
291
394
|
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
395
|
the first screen, and don't animate anything but `transform` and `opacity`.
|
|
293
396
|
|
|
397
|
+
## Additions to the markup contract in 0.2.0
|
|
398
|
+
|
|
399
|
+
The button states (`:active`, `[aria-busy="true"]`), `.gw-segmented` (+ `--block`) with its `data-gw-segmented` /
|
|
400
|
+
`data-gw-show` hooks, `.gw-window__caption` and `.gw-prose` (+ `--sm`). Two tokens: `--gw-font-mono` and `--gw-spin-ms`.
|
|
401
|
+
|
|
294
402
|
## Additions to the markup contract in 0.1.0
|
|
295
403
|
|
|
296
404
|
The contract names the classes the script relies on; these CSS-only helpers were added with it: `.gw-page`
|
package/docs/markup-contract.md
CHANGED
|
@@ -77,23 +77,56 @@ Script behaviour:
|
|
|
77
77
|
- **Current page**: the script marks `aria-current="page"` on `.gw-nav__link` / `.gw-mega__link` whose `href`
|
|
78
78
|
pathname equals `location.pathname` (when the markup did not already).
|
|
79
79
|
|
|
80
|
+
## Segmented control (0.2.0)
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<div class="gw-segmented" role="radiogroup" aria-label="Billing period" data-gw-segmented="billing">
|
|
84
|
+
<button type="button" role="radio" aria-checked="true" value="monthly">Monthly</button>
|
|
85
|
+
<button type="button" role="radio" aria-checked="false" value="yearly">Yearly –20%</button>
|
|
86
|
+
</div>
|
|
87
|
+
…
|
|
88
|
+
<span data-gw-show="billing:monthly">$49</span><span data-gw-show="billing:yearly" hidden>$39</span>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- The group: `.gw-segmented` (+ `--block`), `role="radiogroup"`, a name (`aria-label` or `aria-labelledby`) and
|
|
92
|
+
`data-gw-segmented="<name>"`, unique on the page. Two to four `<button type="button" role="radio">` children, each
|
|
93
|
+
with a `value` and `aria-checked`, exactly one `"true"`: the choice the page shows without the script.
|
|
94
|
+
- Content: any element with `data-gw-show="<name>:<value>"` (several tokens, space-separated, for several options) is
|
|
95
|
+
shown only while that option is chosen. The markup sets `hidden` on every version but the default's.
|
|
96
|
+
- Script behaviour: on load the checked option (or the first) is chosen again, which corrects `hidden` on the content
|
|
97
|
+
and sets the roving `tabindex` (`0` on the chosen option, `-1` on the others: one Tab stop). A click chooses;
|
|
98
|
+
ArrowRight / ArrowDown and ArrowLeft / ArrowUp move to the next and previous option, wrapping, Home and End to the
|
|
99
|
+
first and last, and the moved-to option is chosen and focused (the radio-group pattern). Each choice made by the
|
|
100
|
+
reader fires `gw:segmented` on the group (`bubbles: true`, `detail: { name, value }`); loading does not.
|
|
101
|
+
|
|
102
|
+
## Busy button (0.2.0)
|
|
103
|
+
|
|
104
|
+
- Markup: `aria-busy="true"` on a `<button class="gw-btn">` while it waits; the page's own script sets and removes it.
|
|
105
|
+
No `disabled` at the same time, no loading class, no spinner element: the CSS draws the ring (`::before`; `--sm`
|
|
106
|
+
keeps `::after` for its hit area).
|
|
107
|
+
- Script behaviour: one click listener on the document, in the capture phase, cancels a click on a
|
|
108
|
+
`.gw-btn[aria-busy="true"]` (or inside one) before the page's handlers see it: `preventDefault` and
|
|
109
|
+
`stopPropagation`. A form is not submitted twice, by mouse, Enter or Space, and the button keeps the focus.
|
|
110
|
+
|
|
80
111
|
## Components (CSS only)
|
|
81
112
|
|
|
82
113
|
| class | what |
|
|
83
114
|
|---|---|
|
|
84
|
-
| `.gw-btn` (+ `--secondary`, `--ghost`, `--on-night`, `--sm`, `--lg`, `--block`) | actions; rectangle 8px, ink fill by default |
|
|
115
|
+
| `.gw-btn` (+ `--secondary`, `--ghost`, `--on-night`, `--sm`, `--lg`, `--block`) | actions; rectangle 8px, ink fill by default; `:active` pressed, `[aria-busy="true"]` loading (script above) |
|
|
85
116
|
| `.gw-link` (+ `--arrow`) | inline text link in the accent; `--arrow` adds → that moves 2px on hover |
|
|
86
117
|
| `.gw-label` | the label above a heading: 13px muted, 8px accent square before it |
|
|
87
118
|
| `.gw-dim` | the muted first sentence inside a headline |
|
|
88
119
|
| `.gw-chip` (+ `.is-active`) | selectable token, pill |
|
|
120
|
+
| `.gw-segmented` (+ `--block`) | one choice out of two to four (script above) |
|
|
89
121
|
| `.gw-badge` (+ `--accent`, `--success`, `--warning`) | small status, pill |
|
|
90
122
|
| `.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 |
|
|
123
|
+
| `.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
124
|
| `.gw-icon` | 20px inline stroke SVG wrapper |
|
|
93
125
|
| `.gw-list--check` | list with check marks |
|
|
94
126
|
| `.gw-table` (+ `--compare`) | data / comparison table; scrolls inside its box on phones |
|
|
95
127
|
| `.gw-faq` (`<details class="gw-faq__item"><summary>`) | FAQ accordion with no script |
|
|
96
|
-
| `.gw-
|
|
128
|
+
| `.gw-prose` (+ `--sm`) | long-form reading: an article's own HTML, no classes inside |
|
|
129
|
+
| `.gw-tabs` (`role="tablist"`) | NOT in the kit yet (a reported gap would add it) |
|
|
97
130
|
|
|
98
131
|
## Sections
|
|
99
132
|
|
package/docs/theming.md
CHANGED
|
@@ -16,7 +16,7 @@ else.
|
|
|
16
16
|
|---|---|---|
|
|
17
17
|
| `--gw-accent-light` | the accent on light pages: links, focus ring, active item, label square, chart line | `#5661f5` |
|
|
18
18
|
| `--gw-accent-strong-light` | the accent on hover and as text on `--gw-accent-soft` | `#3a44de` |
|
|
19
|
-
| `--gw-accent-soft-light` | the accent's tint: active chip, accent badge, icon hover | `#eef0fe` |
|
|
19
|
+
| `--gw-accent-soft-light` | the accent's tint: active chip, chosen segmented option, accent badge, icon hover | `#eef0fe` |
|
|
20
20
|
| `--gw-on-accent-light` | text on a solid accent (rare; the kit never fills a button with it) | `#ffffff` |
|
|
21
21
|
| `--gw-accent-dark` | the accent in dark mode **and inside night bands** | `#7c84ff` |
|
|
22
22
|
| `--gw-accent-strong-dark` | its hover / strong form | `#a3a9ff` |
|
|
@@ -82,7 +82,8 @@ So:
|
|
|
82
82
|
|
|
83
83
|
1. **Accent text on white at least 4.5:1**: `--gw-accent-light` and `--gw-accent-strong-light` on `#ffffff`.
|
|
84
84
|
Check `--gw-accent-strong-light` on `--gw-bg-soft` (`#f2f3f5`) too, where links sit in soft sections.
|
|
85
|
-
2. `--gw-accent-strong-light` on `--gw-accent-soft-light`: at least 4.5:1 (the accent badge
|
|
85
|
+
2. `--gw-accent-strong-light` on `--gw-accent-soft-light`: at least 4.5:1 (the accent badge, the active chip and the
|
|
86
|
+
chosen option of a segmented control).
|
|
86
87
|
3. `--gw-accent-dark` and `--gw-accent-strong-dark` on `#0d0d12` (dark page) and on `#0c0e14` (night band): at
|
|
87
88
|
least 4.5:1.
|
|
88
89
|
4. `--gw-accent-strong-dark` on `--gw-accent-soft-dark`: at least 4.5:1.
|
package/js/website.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
(function () {
|
|
3
3
|
'use strict';
|
|
4
4
|
if (window.gwWebsite) return;
|
|
5
|
-
window.gwWebsite = { version: '0.
|
|
5
|
+
window.gwWebsite = { version: '0.2.0' };
|
|
6
6
|
var doc = document, root = doc.documentElement;
|
|
7
7
|
function mq(q) { return window.matchMedia ? matchMedia(q) : { matches: false }; }
|
|
8
8
|
function watch(m, fn) { if (m.addEventListener) m.addEventListener('change', fn); }
|
|
@@ -140,8 +140,45 @@
|
|
|
140
140
|
});
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
// Segmented control: a radio group, one Tab stop; arrows (Home, End) move and choose. What one option shows carries
|
|
144
|
+
// data-gw-show="group:value" and is hidden unless that option is chosen; without the script the markup's own choice shows.
|
|
145
|
+
function segmented() {
|
|
146
|
+
all('[data-gw-segmented]').forEach(function (g) {
|
|
147
|
+
var name = attr(g, 'data-gw-segmented', ''), opts = all('[role="radio"]', g);
|
|
148
|
+
if (!opts.length) return;
|
|
149
|
+
function pick(o, focus, said) {
|
|
150
|
+
var v = attr(o, 'value', '');
|
|
151
|
+
opts.forEach(function (x) { x.setAttribute('aria-checked', String(x === o)); x.setAttribute('tabindex', x === o ? '0' : '-1'); });
|
|
152
|
+
if (focus) o.focus();
|
|
153
|
+
all('[data-gw-show]').forEach(function (el) {
|
|
154
|
+
var mine = attr(el, 'data-gw-show', '').split(/\s+/).filter(function (t) { return t.split(':')[0] === name; });
|
|
155
|
+
if (mine.length) el.hidden = mine.indexOf(name + ':' + v) < 0;
|
|
156
|
+
});
|
|
157
|
+
if (said && window.CustomEvent) g.dispatchEvent(new CustomEvent('gw:segmented', { bubbles: true, detail: { name: name, value: v } }));
|
|
158
|
+
}
|
|
159
|
+
opts.forEach(function (o, i) {
|
|
160
|
+
on(o, 'click', function () { pick(o, false, true); });
|
|
161
|
+
on(o, 'keydown', function (e) {
|
|
162
|
+
var n = opts.length, k = { ArrowRight: i + 1, ArrowDown: i + 1, ArrowLeft: i - 1 + n, ArrowUp: i - 1 + n, Home: 0, End: n - 1 }[e.key];
|
|
163
|
+
if (k === undefined || e.altKey || e.ctrlKey || e.metaKey) return;
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
pick(opts[k % n], true, true);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
pick(opts.filter(function (o) { return attr(o, 'aria-checked', '') === 'true'; })[0] || opts[0]);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// A busy button (aria-busy="true") ignores presses, by mouse or keyboard: no double submit. It keeps its focus and name.
|
|
173
|
+
function busy() {
|
|
174
|
+
on(doc, 'click', function (e) {
|
|
175
|
+
var b = e.target.closest && e.target.closest('.gw-btn[aria-busy="true"]');
|
|
176
|
+
if (b) { e.preventDefault(); e.stopPropagation(); }
|
|
177
|
+
}, true);
|
|
178
|
+
}
|
|
179
|
+
|
|
143
180
|
function init() {
|
|
144
|
-
[mega, mobile, theme, reveal, header, current].forEach(function (f) {
|
|
181
|
+
[mega, mobile, theme, reveal, header, current, segmented, busy].forEach(function (f) {
|
|
145
182
|
try { f(); } catch (e) { console.error('[gw-website]', e); }
|
|
146
183
|
});
|
|
147
184
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemboss/website-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The GemBoss website kit: tokens, header, footer, sections and the rules (AGENTS.md) that GemCommerce public websites share, plus a CLI that checks a site, reports kit gaps and upgrades.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
package/skill/SKILL.md
CHANGED
|
@@ -22,7 +22,8 @@ the kit and its rules are how the page ends up looking designed. Compose from th
|
|
|
22
22
|
scales, classes in the site's own prefix. Never style a `.gw-*` class, never edit `node_modules`.
|
|
23
23
|
- Another brand: change only the accent family, wash hues, logo and favicon (`KIT/docs/theming.md`).
|
|
24
24
|
4. **Words**: real facts only. A missing price, number, name or date becomes a visible `[PLACEHOLDER]` and a
|
|
25
|
-
question to the person.
|
|
25
|
+
question to the person. Made-up numbers in a drawn mock-up get `<p class="gw-window__caption">Illustrative
|
|
26
|
+
data.</p>` in its window. No em dash in visible text, no "Learn more" as a whole link.
|
|
26
27
|
5. **Check**: `npx gemboss-website check` and fix every error (`--fix-list` gives a to-do list). Then look at the
|
|
27
28
|
page at 320px and 1366px. Only then say it is done.
|
|
28
29
|
|
package/system.json
CHANGED
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
},
|
|
80
80
|
"type": {
|
|
81
81
|
"family": "Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif",
|
|
82
|
+
"mono": "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
82
83
|
"weights": [400, 500, 600, 650, 700],
|
|
83
84
|
"scale": [11, 12, 13, 14, 16, 18, 20, 22, 24, 30],
|
|
84
85
|
"display_above": 30,
|
|
@@ -115,6 +116,7 @@
|
|
|
115
116
|
"properties": ["transform", "opacity"],
|
|
116
117
|
"ease": "cubic-bezier(.22,1,.36,1)",
|
|
117
118
|
"reveal_ms": 700,
|
|
118
|
-
"
|
|
119
|
+
"spin_ms": 700,
|
|
120
|
+
"$comment": "Animate transform and opacity only. Everything is still under prefers-reduced-motion (a busy button's ring turns once every spin_ms, as the dashboard's spinner, and stands still as an ellipsis)."
|
|
119
121
|
}
|
|
120
122
|
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
Add .gw-band--night for a dark hero (it counts as one of the page's three night bands).
|
|
5
5
|
Limits: a short label, a two-part headline (muted first sentence, ink second), at most two lines under it,
|
|
6
6
|
one .gw-btn and one secondary action. The optional window holds a real product screen (img with alt, width, height)
|
|
7
|
-
or a mock-up
|
|
7
|
+
or a drawn mock-up. Numbers in it that are not real get the caption "Illustrative data." (vi: "Dữ liệu minh hoạ.",
|
|
8
|
+
zh: "示意数据。"); real ones get their source there. Never tuck the hero under the header.
|
|
8
9
|
-->
|
|
9
10
|
<section class="gw-hero">
|
|
10
11
|
<div class="gw-hero__wash" aria-hidden="true"></div>
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
<div class="gw-window">
|
|
21
22
|
<div class="gw-window__bar">[Where this screen lives, e.g. the app section]</div>
|
|
22
23
|
<div class="gw-window__body"><img src="[/img/screen.webp]" alt="[What the screen shows]" width="[1200]" height="[750]"></div>
|
|
24
|
+
<p class="gw-window__caption">[Illustrative data. Or the source of the numbers. Delete when the screen shows no numbers.]</p>
|
|
23
25
|
</div>
|
|
24
26
|
</div>
|
|
25
27
|
</section>
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
Pricing: plan cards with the brand's real plan names and prices, exactly as billed. A missing price is [PRICE]
|
|
3
3
|
and a question to the person, never a guess. One plan may be .gw-plan--featured (an accent hairline, never a fill)
|
|
4
4
|
and only that one gets the filled .gw-btn; the others get .gw-btn--secondary.
|
|
5
|
+
Monthly / yearly: the .gw-segmented radio group, only when the brand really bills yearly for less, with the real
|
|
6
|
+
discount in its label. Every price is in the markup twice, data-gw-show="billing:monthly" and
|
|
7
|
+
data-gw-show="billing:yearly" (hidden): the page reads the monthly prices without the script, and website.js
|
|
8
|
+
swaps them on a choice. Under the price, say what is billed ("Billed $470.40 a year"). No yearly billing: delete
|
|
9
|
+
the control and every data-gw-show="billing:yearly" element.
|
|
5
10
|
Limits: two to four plans, four to six lines per plan, the same lines in the same order on every plan.
|
|
6
11
|
-->
|
|
7
12
|
<section class="gw-section">
|
|
@@ -10,12 +15,17 @@
|
|
|
10
15
|
<p class="gw-label">Pricing</p>
|
|
11
16
|
<h2>[Headline about how pricing works]</h2>
|
|
12
17
|
<p class="gw-lead">[One line: trial, billing, what every plan includes.]</p>
|
|
18
|
+
<div class="gw-segmented" role="radiogroup" aria-label="[Billing period]" data-gw-segmented="billing">
|
|
19
|
+
<button type="button" role="radio" aria-checked="true" value="monthly">[Monthly]</button>
|
|
20
|
+
<button type="button" role="radio" aria-checked="false" value="yearly">[Yearly –DISCOUNT%]</button>
|
|
21
|
+
</div>
|
|
13
22
|
</div>
|
|
14
23
|
<div class="gw-pricing">
|
|
15
24
|
<div class="gw-plan">
|
|
16
25
|
<h3>[Plan]</h3>
|
|
17
26
|
<p>[Who it is for, in one line]</p>
|
|
18
|
-
<p class="gw-plan__price">[PRICE] <small>/ month</small></p>
|
|
27
|
+
<p class="gw-plan__price"><span data-gw-show="billing:monthly">[PRICE]</span><span data-gw-show="billing:yearly" hidden>[PRICE A MONTH, BILLED YEARLY]</span> <small>/ month</small></p>
|
|
28
|
+
<p><span data-gw-show="billing:monthly">[Billed monthly]</span><span data-gw-show="billing:yearly" hidden>[Billed $X a year]</span></p>
|
|
19
29
|
<a class="gw-btn gw-btn--secondary gw-btn--block" href="[link]">[Action]</a>
|
|
20
30
|
<ul class="gw-list--check"><li>[What is included]</li><li>[What is included]</li></ul>
|
|
21
31
|
</div>
|
|
@@ -23,14 +33,16 @@
|
|
|
23
33
|
<span class="gw-badge gw-badge--accent">[Why this one, e.g. Recommended for …]</span>
|
|
24
34
|
<h3>[Plan]</h3>
|
|
25
35
|
<p>[Who it is for]</p>
|
|
26
|
-
<p class="gw-plan__price">[PRICE] <small>/ month</small></p>
|
|
36
|
+
<p class="gw-plan__price"><span data-gw-show="billing:monthly">[PRICE]</span><span data-gw-show="billing:yearly" hidden>[PRICE A MONTH, BILLED YEARLY]</span> <small>/ month</small></p>
|
|
37
|
+
<p><span data-gw-show="billing:monthly">[Billed monthly]</span><span data-gw-show="billing:yearly" hidden>[Billed $X a year]</span></p>
|
|
27
38
|
<a class="gw-btn gw-btn--block" href="[link]">[Action]</a>
|
|
28
39
|
<ul class="gw-list--check"><li>[What is included]</li><li>[What is included]</li></ul>
|
|
29
40
|
</div>
|
|
30
41
|
<div class="gw-plan">
|
|
31
42
|
<h3>[Plan]</h3>
|
|
32
43
|
<p>[Who it is for]</p>
|
|
33
|
-
<p class="gw-plan__price">[PRICE] <small>/ month</small></p>
|
|
44
|
+
<p class="gw-plan__price"><span data-gw-show="billing:monthly">[PRICE]</span><span data-gw-show="billing:yearly" hidden>[PRICE A MONTH, BILLED YEARLY]</span> <small>/ month</small></p>
|
|
45
|
+
<p><span data-gw-show="billing:monthly">[Billed monthly]</span><span data-gw-show="billing:yearly" hidden>[Billed $X a year]</span></p>
|
|
34
46
|
<a class="gw-btn gw-btn--secondary gw-btn--block" href="[link]">[Action]</a>
|
|
35
47
|
<ul class="gw-list--check"><li>[What is included]</li><li>[What is included]</li></ul>
|
|
36
48
|
</div>
|