@gemboss/website-ui 0.4.0 → 0.6.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 +43 -5
- package/CHANGELOG.md +117 -0
- package/README.md +9 -8
- package/bin/lib/check.mjs +77 -0
- package/css/article.css +237 -0
- package/css/components.css +23 -0
- package/css/footer.css +3 -0
- package/css/forms.css +154 -0
- package/css/prose.css +29 -16
- package/css/ratings.css +78 -0
- package/css/tokens.css +25 -0
- package/demo/article.html +359 -0
- package/demo/assets/article-hero.svg +1 -0
- package/demo/assets/avatar-gemboss.svg +1 -0
- package/demo/assets/avatar-sample.svg +1 -0
- package/demo/assets/post-headless.svg +1 -0
- package/demo/assets/post-motion.svg +1 -0
- package/demo/assets/post-output.svg +1 -0
- package/demo/assets/post-speed.svg +1 -0
- package/demo/assets/post-theme.svg +1 -0
- package/demo/assets/post-voice.svg +1 -0
- package/demo/blog.html +314 -0
- package/demo/forms.html +505 -0
- package/demo/index.html +2 -0
- package/docs/components.md +334 -12
- package/docs/markup-contract.md +123 -7
- package/docs/theming.md +4 -3
- package/js/website.js +68 -8
- package/js/website.mjs +80 -7
- package/package.json +1 -1
- package/skill/SKILL.md +8 -0
- package/system.json +12 -1
- package/templates/footer.html +15 -0
- package/templates/sections/article.html +93 -0
- package/templates/sections/form.html +42 -0
- package/templates/sections/post-list.html +65 -0
- package/templates/sections/testimonials.html +58 -0
- package/website.css +553 -17
- package/website.min.css +1 -1
package/docs/components.md
CHANGED
|
@@ -35,8 +35,8 @@ Don't remove the skip link, and don't leave out `id="main"`.
|
|
|
35
35
|
## Script
|
|
36
36
|
|
|
37
37
|
`js/website.js` (a `<script defer>`) or `js/website.mjs` (an `import`) wires every part that moves: the mega menu,
|
|
38
|
-
the mobile menu, the theme toggle, reveal, the header's scroll state, the current page, the segmented control
|
|
39
|
-
the
|
|
38
|
+
the mobile menu, the theme toggle, reveal, the header's scroll state, the current page, the segmented control, the
|
|
39
|
+
busy button, an article's contents list and the scroll box of its tables (0.5.0). Markup that arrives after the page loaded (a client-side route change, a builder block, a CMS
|
|
40
40
|
preview) is wired by calling `init` on it, as often as needed: an element is wired once.
|
|
41
41
|
|
|
42
42
|
```js
|
|
@@ -51,7 +51,7 @@ import { init } from '@gemboss/website-ui/website.mjs';
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
`destroy(root)` takes it back when a framework unmounts. The Next.js recipe, the `data-gw-ready` mark and the
|
|
54
|
-
events (`gw:mega`, `gw:theme`, `gw:segmented`) are in `docs/markup-contract.md`, "Script".
|
|
54
|
+
events (`gw:mega`, `gw:theme`, `gw:segmented`, `gw:toc`) are in `docs/markup-contract.md`, "Script".
|
|
55
55
|
|
|
56
56
|
Don't: wire a kit part with your own script (a click handler on a trigger, a class toggled by hand), or load two
|
|
57
57
|
copies of the script and expect both to work (the first owns the page).
|
|
@@ -116,8 +116,9 @@ Don't: more than three night bands on a page (the closing CTA does not count), o
|
|
|
116
116
|
|
|
117
117
|
40px tall, the action radius (8, 12 in `soft`), 14px/600, sentence case, the action colour as fill: ink, or the
|
|
118
118
|
brand's accent with `--gw-on-accent` words under `data-gw-action="accent"` (one colour on the whole site). Variants: `--secondary` (hairline, transparent), `--ghost`
|
|
119
|
-
(no frame), `--on-night` (light fill for a dark surface that is not a `.gw-band--night`), `--
|
|
120
|
-
|
|
119
|
+
(no frame), `--on-night` (light fill for a dark surface that is not a `.gw-band--night`), `--danger` (0.6.0: the
|
|
120
|
+
danger fill with `--gw-on-danger` words, for the action of a page that destroys something: delete a workspace, erase
|
|
121
|
+
my data), `--sm` (36px, with a 44px hit area), `--lg` (48px, 16px type), `--block` (full width). On touch screens every button is at least 44px tall and
|
|
121
122
|
its label may wrap. Inside a night band the default button turns light by itself.
|
|
122
123
|
|
|
123
124
|
```html
|
|
@@ -146,6 +147,9 @@ button.setAttribute('aria-busy', 'true'); // the request leaves
|
|
|
146
147
|
button.removeAttribute('aria-busy'); // the answer came
|
|
147
148
|
```
|
|
148
149
|
|
|
150
|
+
`--danger` is never a second primary beside `.gw-btn` and never a way to make an action louder: it marks what cannot
|
|
151
|
+
be undone, and the words around it say what will be lost.
|
|
152
|
+
|
|
149
153
|
Don't: a fill of your own (the accent comes only from the action profile, set on `<html>`), a gradient or a wash; a
|
|
150
154
|
raised or inset shadow; pill-shaped buttons; uppercase; two filled buttons side by side (one
|
|
151
155
|
`.gw-btn` per view, the second is `--secondary` or a link); a `<div>` with a click handler instead of `<a>`/`<button>`;
|
|
@@ -196,8 +200,9 @@ groups with the same name on one page; the old `role="group"` with `aria-pressed
|
|
|
196
200
|
|
|
197
201
|
### Badges `.gw-badge`
|
|
198
202
|
|
|
199
|
-
Small status, a pill, 12px. Variants: `--accent` (a test, the recommended plan), `--success
|
|
200
|
-
a coloured dot, ink words, because the state hues fail 4.5:1 as
|
|
203
|
+
Small status, a pill, 12px. Variants: `--accent` (a test, the recommended plan), `--success`, `--warning` and
|
|
204
|
+
`--danger` (0.6.0, an error: "Payment failed") (state: a coloured dot, ink words, because the state hues fail 4.5:1 as
|
|
205
|
+
small text on a tint).
|
|
201
206
|
|
|
202
207
|
```html
|
|
203
208
|
<span class="gw-badge gw-badge--success">Live</span>
|
|
@@ -219,8 +224,27 @@ Variants: `--soft` (on `--gw-bg-soft`), `--link` (the whole card is one `<a>`; t
|
|
|
219
224
|
</a>
|
|
220
225
|
```
|
|
221
226
|
|
|
227
|
+
**A picture on top (0.5.0)**: `.gw-card__media` as the card's first child, around one `<img>` (or `<video>`). It runs
|
|
228
|
+
edge to edge across the card's top, its corners the card's radius inside the hairline, and is an aspect-ratio box the
|
|
229
|
+
picture covers: 16:9 by default, `--wide` 1.91:1 (the 1200 × 630 share image a post usually has, so it fits uncut),
|
|
230
|
+
`--square` 1:1. One shape for every card of a row. The box shows the soft ground until the picture loads.
|
|
231
|
+
|
|
232
|
+
```html
|
|
233
|
+
<article class="gw-card">
|
|
234
|
+
<div class="gw-card__media"><img src="/img/case-study.webp" alt="" width="640" height="360" loading="lazy"></div>
|
|
235
|
+
<h3>…</h3>
|
|
236
|
+
<p>…</p>
|
|
237
|
+
</article>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Alt text: `alt=""` when the card's title already says what the picture is (a post's cover, a case study's photo); a
|
|
241
|
+
real description when the picture carries something the words do not (a chart, a before and after). Always `width`,
|
|
242
|
+
`height` and `loading="lazy"` (the box keeps its shape before the picture arrives). A list of posts is
|
|
243
|
+
`.gw-post-list` (below, "Article and blog").
|
|
244
|
+
|
|
222
245
|
Don't: put links or buttons inside a `--link` card (one card, one destination); a bigger shadow or a hover lift; a
|
|
223
|
-
radius of your own (cards take the card radius, plans and the quote the panel radius, both from the profile)
|
|
246
|
+
radius of your own (cards take the card radius, plans and the quote the panel radius, both from the profile); a
|
|
247
|
+
`.gw-card__media` anywhere but first in the card; pictures of different shapes in one row.
|
|
224
248
|
|
|
225
249
|
### Window `.gw-window`
|
|
226
250
|
|
|
@@ -337,8 +361,11 @@ hero pulled up under the header.
|
|
|
337
361
|
| `.gw-footer__brand` | the brand and one sentence, full width on phones |
|
|
338
362
|
| `.gw-footer__col`, `__heading` | a `<nav>` column: a sentence-case heading and a `<ul>` of links |
|
|
339
363
|
| `.gw-footer__bottom` | copyright, legal links, social links; alone (no grid above), it drops its top rule |
|
|
364
|
+
| `.gw-footer__newsletter` | 0.6.0, optional: a `<form>` in the brand block, 24px under its sentence: a `.gw-field` with its label, a `.gw-form-inline` row (the email field and a `--secondary` button) and the note |
|
|
340
365
|
|
|
341
|
-
Full markup: `templates/footer.html`.
|
|
366
|
+
Full markup: `templates/footer.html`. The newsletter note says how often you write and how to stop, and links to the
|
|
367
|
+
privacy policy. Don't: uppercase column headings; a newsletter form that stores nothing; a consent box ticked for the
|
|
368
|
+
reader; a filled primary button in the footer (the closing CTA above it is the page's action).
|
|
342
369
|
|
|
343
370
|
## Sections
|
|
344
371
|
|
|
@@ -393,13 +420,13 @@ The HTML a CMS or a markdown renderer writes needs no classes. Inside `.gw-prose
|
|
|
393
420
|
| `blockquote` | a 3px rule on its left, muted words (the dashboard's reader quote) |
|
|
394
421
|
| `code` | a tint chip in `--gw-font-mono` (16px, 14px in `--sm`) that wraps on a phone; in a table it stays whole |
|
|
395
422
|
| `pre` | a soft box, 14px mono, that scrolls sideways inside itself |
|
|
396
|
-
| `table` | as a direct child: the `.gw-table` look, and it scrolls inside its own box when it is wider than the column |
|
|
423
|
+
| `table` | as a direct child: the `.gw-table` look, and it scrolls inside its own box when it is wider than the column. `website.js` (0.5.0) puts it in a box of its own, `div.gw-prose__table`: a region named after its `<caption>` (else its first header cell) that a keyboard can focus and scroll, while the table stays a table for screen readers. Give it a `<caption>` |
|
|
397
424
|
| `img`, `figure`, `figcaption` | images take the card radius; a caption 14px muted, 12px under its figure |
|
|
398
425
|
| `hr`, `a` | a hairline with 32px of air; links in the accent, underlined |
|
|
399
426
|
|
|
400
427
|
Kit components inside the prose keep their own look (`.gw-table`, `.gw-window`, `.gw-list--check`, `.gw-quote`,
|
|
401
|
-
`.gw-btn`). Prefer the `.gw-table` wrapper (`role="region"`, a name, `tabindex="0"`) when
|
|
402
|
-
|
|
428
|
+
`.gw-btn`, `.gw-callout`, `.gw-faq`). Prefer the `.gw-table` wrapper (`role="region"`, a name, `tabindex="0"`) when
|
|
429
|
+
you write the markup; a bare table from the CMS gets the same from `website.js`.
|
|
403
430
|
|
|
404
431
|
```html
|
|
405
432
|
<section class="gw-section">
|
|
@@ -418,6 +445,284 @@ keyboard can scroll it.
|
|
|
418
445
|
Don't: marketing sections inside the prose (end the article, then the section); headings chosen for their size (keep
|
|
419
446
|
them in order); a page without `.gw-prose` that styles its articles in site CSS (that was the stopgap of gap #46).
|
|
420
447
|
|
|
448
|
+
## Article and blog (0.5.0)
|
|
449
|
+
|
|
450
|
+
A blog post is `templates/sections/article.html`: breadcrumbs, the title, the meta row, a hero image, the contents
|
|
451
|
+
list, the article's own HTML in `.gw-prose` with callouts where they help, the FAQ last. The blog index and "more from
|
|
452
|
+
the blog" are `templates/sections/post-list.html`. See both whole in `demo/article.html` and `demo/blog.html`.
|
|
453
|
+
|
|
454
|
+
| class | for |
|
|
455
|
+
|---|---|
|
|
456
|
+
| `.gw-article` (with `.gw-wrap`, on the `<article>`) | the page's layout: one column on phones; from 1080px the reading column (68ch) and the contents beside it |
|
|
457
|
+
| `.gw-article__head` | breadcrumbs, the `<h1>` (at the section-headline size) and the meta row, 16px apart |
|
|
458
|
+
| `.gw-article__hero` | a `<figure>` with the post's own picture, the card radius; not lazy, it is the first screen |
|
|
459
|
+
| `.gw-breadcrumbs` | the trail above the title: a `<nav>` with a name, an `<ol>`, the page itself last with `aria-current="page"` |
|
|
460
|
+
| `.gw-article-meta`, `__avatar` | author, category, date, read time: 14px muted, a dot between items |
|
|
461
|
+
| `.gw-toc` (`data-gw-toc`) | the contents: links to the article's h2s (and h3s), sticky beside it from 1080px, folded above it below |
|
|
462
|
+
| `.gw-callout` (+ `--note`, `--tip`, `--warning`), `__label` | a note, a tip or a warning inside the article, its kind in words |
|
|
463
|
+
| `.gw-post-list` (+ `--compact`) | a grid of post cards (one, two from 768px, three from 1080px); `--compact`: rows, no card, no picture |
|
|
464
|
+
| `.gw-post` (with `.gw-card`), `__title` | a post card: picture, category, title (its link covers the card), excerpt, meta row |
|
|
465
|
+
| `.gw-pagination`, `__status` | previous and next links with "Page 2 of 12" between them |
|
|
466
|
+
|
|
467
|
+
### Breadcrumbs `.gw-breadcrumbs`
|
|
468
|
+
|
|
469
|
+
Every level above the page, then the page itself, marked `aria-current="page"` (cut with an ellipsis on a phone:
|
|
470
|
+
the title under it says it in full). The chevrons between items are drawn by the CSS: never type ›, / or > in the
|
|
471
|
+
text, a screen reader would read them out.
|
|
472
|
+
|
|
473
|
+
```html
|
|
474
|
+
<nav class="gw-breadcrumbs" aria-label="Breadcrumb">
|
|
475
|
+
<ol>
|
|
476
|
+
<li><a href="/blog">Blog</a></li>
|
|
477
|
+
<li><a href="/blog/guides">Guides</a></li>
|
|
478
|
+
<li><a href="/blog/plan-a-product-page" aria-current="page">How to plan a product page</a></li>
|
|
479
|
+
</ol>
|
|
480
|
+
</nav>
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
The kit does not write structured data. A site that wants the trail in search results adds a BreadcrumbList with the
|
|
484
|
+
same items, each `item` the page's full URL:
|
|
485
|
+
|
|
486
|
+
```html
|
|
487
|
+
<script type="application/ld+json">
|
|
488
|
+
{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [
|
|
489
|
+
{"@type": "ListItem", "position": 1, "name": "Blog", "item": "https://example.com/blog"},
|
|
490
|
+
{"@type": "ListItem", "position": 2, "name": "Guides", "item": "https://example.com/blog/guides"},
|
|
491
|
+
{"@type": "ListItem", "position": 3, "name": "How to plan a product page", "item": "https://example.com/blog/plan-a-product-page"}
|
|
492
|
+
]}
|
|
493
|
+
</script>
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
`check` reports breadcrumbs without a named `<nav>`, without an `<ol>`, without `aria-current="page"` on the last item
|
|
497
|
+
(or with it on another one) and with a typed separator (`breadcrumbs-markup`).
|
|
498
|
+
|
|
499
|
+
### Meta row `.gw-article-meta`
|
|
500
|
+
|
|
501
|
+
Each item a `<span>` (a link sits inside its span). The date is a `<time datetime>`, written "Updated …" when the
|
|
502
|
+
post changed after it was published; the read time counts about 200 words a minute. The avatar is optional, 24px,
|
|
503
|
+
`alt=""` because the name is next to it. The row wraps on a phone, and a wrapped line never starts with a dot.
|
|
504
|
+
|
|
505
|
+
```html
|
|
506
|
+
<p class="gw-article-meta">
|
|
507
|
+
<span><img class="gw-article-meta__avatar" src="/img/team.webp" alt="" width="24" height="24">GemBoss Team</span>
|
|
508
|
+
<span><a href="/blog/guides">Guides</a></span>
|
|
509
|
+
<span>Updated <time datetime="2026-10-25">October 25, 2026</time></span>
|
|
510
|
+
<span>5 min read</span>
|
|
511
|
+
</p>
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Contents `.gw-toc`
|
|
515
|
+
|
|
516
|
+
One link per `<h2>` of the article (a nested `<ol>` for its `<h3>`s), with the heading's own words, to the heading's
|
|
517
|
+
id. The markup holds the list, so it works without the script and search engines see it. With `data-gw-toc`,
|
|
518
|
+
`website.js` marks the section in view (`aria-current="true"`: an accent bar, the weight does not change) and fires
|
|
519
|
+
`gw:toc`; it opens the `<details>` from 1080px, where the list sits beside the article and sticks under the header,
|
|
520
|
+
and below 1080px leaves it folded above the article and folds it again when a link is followed. A contents link
|
|
521
|
+
glides to its section, except for a visitor who asked for less motion.
|
|
522
|
+
|
|
523
|
+
```html
|
|
524
|
+
<nav class="gw-toc" aria-label="On this page" data-gw-toc>
|
|
525
|
+
<details>
|
|
526
|
+
<summary>On this page</summary>
|
|
527
|
+
<ol>
|
|
528
|
+
<li><a href="#start-from-the-questions">Start from the questions</a></li>
|
|
529
|
+
<li><a href="#first-screen">Answer the first question on the first screen</a>
|
|
530
|
+
<ol><li><a href="#write-the-title">Write the title for the shopper</a></li></ol>
|
|
531
|
+
</li>
|
|
532
|
+
<li><a href="#faq">Frequently asked questions</a></li>
|
|
533
|
+
</ol>
|
|
534
|
+
</details>
|
|
535
|
+
</nav>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
Put it in the `<article class="gw-wrap gw-article">` between the hero and the `.gw-prose`. `check` reports a link to
|
|
539
|
+
an id that is not on the page (`toc-target`). Don't: list h4s; a contents list on a page with fewer than three h2s;
|
|
540
|
+
headings written for the list rather than the reader.
|
|
541
|
+
|
|
542
|
+
### Callout `.gw-callout`
|
|
543
|
+
|
|
544
|
+
A note (`--note`, on the accent's tint), a tip (`--tip`, success's tint) or a warning (`--warning`, the warning tint),
|
|
545
|
+
inside or next to the prose. The kind is said in words: `__label` ("Note", "Tip", "Warning", or the same word in the
|
|
546
|
+
page's language), with an icon before it in the kind's hue. The tint only repeats it.
|
|
547
|
+
|
|
548
|
+
```html
|
|
549
|
+
<div class="gw-callout gw-callout--tip" role="note">
|
|
550
|
+
<p class="gw-callout__label">Tip</p>
|
|
551
|
+
<p>Ask someone who has never seen the product to find the return window while you watch.</p>
|
|
552
|
+
</div>
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Don't: a callout told by its colour alone (`check` warns: `callout-label`); a callout as a section band or an ad
|
|
556
|
+
("Try GemBoss free!"); more than one callout every few sections; a whole procedure inside one.
|
|
557
|
+
|
|
558
|
+
### Post list `.gw-post-list` and pagination `.gw-pagination`
|
|
559
|
+
|
|
560
|
+
A `<ul role="list">` of post cards. The card is a `.gw-card.gw-post`: a `.gw-card__media` picture (`alt=""`, lazy),
|
|
561
|
+
the category as a `.gw-label`, the title in a heading one level below the section's, one or two lines of excerpt and
|
|
562
|
+
a meta row. The title's link covers the whole card, so the card is one target and a screen reader hears the title
|
|
563
|
+
alone. Pagination is two links and the page number, no script: `rel="prev"` and `rel="next"`, and the link that does
|
|
564
|
+
not exist is left out.
|
|
565
|
+
|
|
566
|
+
```html
|
|
567
|
+
<ul class="gw-post-list" role="list">
|
|
568
|
+
<li>
|
|
569
|
+
<article class="gw-card gw-post">
|
|
570
|
+
<div class="gw-card__media"><img src="/img/post.webp" alt="" width="640" height="360" loading="lazy"></div>
|
|
571
|
+
<p class="gw-label">Themes</p>
|
|
572
|
+
<h3 class="gw-post__title"><a href="/blog/how-to-choose-a-theme">How to choose a Shopify theme that converts</a></h3>
|
|
573
|
+
<p>A checklist for choosing a theme: speed, mobile, features and fit.</p>
|
|
574
|
+
<p class="gw-article-meta"><span><time datetime="2026-08-08">Aug 8, 2026</time></span><span>8 min read</span></p>
|
|
575
|
+
</article>
|
|
576
|
+
</li>
|
|
577
|
+
</ul>
|
|
578
|
+
<nav class="gw-pagination" aria-label="Blog pages">
|
|
579
|
+
<a class="gw-btn gw-btn--secondary" href="/blog?page=1" rel="prev">Newer posts</a>
|
|
580
|
+
<p class="gw-pagination__status">Page 2 of 12</p>
|
|
581
|
+
<a class="gw-btn gw-btn--secondary" href="/blog?page=3" rel="next">Older posts</a>
|
|
582
|
+
</nav>
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
`--compact` is the same markup without the picture, drawn as rows with a hairline between them: a long archive, a
|
|
586
|
+
"more on this" list in a narrow column.
|
|
587
|
+
|
|
588
|
+
Don't: a second link or a button inside a post card; an excerpt longer than two lines; numbered page links (prev and
|
|
589
|
+
next only); "Read more" as a link.
|
|
590
|
+
|
|
591
|
+
### FAQ at the end of an article
|
|
592
|
+
|
|
593
|
+
The last `<h2>` of the prose, then a `.gw-faq`. A site that marks it up as FAQPage does so only when every answer is
|
|
594
|
+
visible on the page, with the same words:
|
|
595
|
+
|
|
596
|
+
```html
|
|
597
|
+
<script type="application/ld+json">
|
|
598
|
+
{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [
|
|
599
|
+
{"@type": "Question", "name": "How long should a product description be?",
|
|
600
|
+
"acceptedAnswer": {"@type": "Answer", "text": "Long enough to answer the questions on your list, and no longer."}}
|
|
601
|
+
]}
|
|
602
|
+
</script>
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
## Forms (0.6.0)
|
|
606
|
+
|
|
607
|
+
A public site asks as little as it can. One field beats a form (the home page's "What do you sell?", the audit's store
|
|
608
|
+
URL), and a form keeps two or three fields; a question with a good guess is a choice the reader confirms, with the
|
|
609
|
+
guess chosen. The look is the dashboard's field family (`@gemboss/ui` `gemboss-field`, `ui-select`, `ui-textarea`,
|
|
610
|
+
`ui-check`, `ui-help`, `ui-error`) at the website's sizes. See every state in `demo/forms.html`; a short form is
|
|
611
|
+
`templates/sections/form.html`, the footer newsletter `templates/footer.html`.
|
|
612
|
+
|
|
613
|
+
| class | for |
|
|
614
|
+
|---|---|
|
|
615
|
+
| `.gw-field` | one field: the label, the control, the error, the hint, 8px apart |
|
|
616
|
+
| `.gw-field__label` | the `<label for>` above the control (14px/600, ink); also the `<legend>` of a group |
|
|
617
|
+
| `.gw-field__error` | under the control: what to do, in words, after a 16px icon, in `--gw-danger` (14px/500); empty, it takes no room |
|
|
618
|
+
| `.gw-field__hint` | under the error: a short help line (14px, muted) |
|
|
619
|
+
| `.gw-input` | a line of text (`type` email, url, tel, text, search, password…): 40px tall, 44px on touch, 16px words, the action radius (8, 12 in `soft`), a 1px `--gw-line-control` border (3:1) on the surface |
|
|
620
|
+
| `.gw-select` | a choice from a list (`<select>`), the same box with a drawn caret |
|
|
621
|
+
| `.gw-textarea` | a few lines, the same box from 96px tall, resizes down the page |
|
|
622
|
+
| `.gw-choices` | a group of checkboxes or radios: a `<fieldset>`, its question in `<legend class="gw-field__label">`, one choice per row |
|
|
623
|
+
| `.gw-check`, `.gw-radio` | a `<label>` around one `<input type="checkbox">` or `type="radio"` and its words: the row is the target (32px, 44px on touch); chosen, the box takes the accent |
|
|
624
|
+
| `.gw-check__count` | a filter's count at the end of its row, a small neutral pill |
|
|
625
|
+
| `.gw-form-inline` | one field and its button on one line (a newsletter, "email me the guide"); below 480px, or in a narrow box, each takes a full line |
|
|
626
|
+
| `.gw-form` | a short form: fields 24px apart, the actions 32px below, 560px at most; on a `.gw-card` or inside one |
|
|
627
|
+
|
|
628
|
+
States: hover darkens the border; focus is the kit's accent ring (2px) with an ink border; read-only
|
|
629
|
+
(`readonly`) sits on the soft ground; disabled (`disabled`) is faded, always with its reason in the hint; invalid
|
|
630
|
+
(`aria-invalid="true"`) takes the danger border and tint, and its `.gw-field__error` says why. No success state: say
|
|
631
|
+
what happened after sending, in a `role="status"` line.
|
|
632
|
+
|
|
633
|
+
```html
|
|
634
|
+
<div class="gw-field">
|
|
635
|
+
<label class="gw-field__label" for="email">Work email</label>
|
|
636
|
+
<input class="gw-input" id="email" name="email" type="email" autocomplete="email" required
|
|
637
|
+
aria-invalid="true" aria-describedby="email-error email-hint">
|
|
638
|
+
<p class="gw-field__error" id="email-error">Enter your work email, like name@example.com.</p>
|
|
639
|
+
<p class="gw-field__hint" id="email-hint">We reply to this address.</p>
|
|
640
|
+
</div>
|
|
641
|
+
|
|
642
|
+
<fieldset class="gw-choices">
|
|
643
|
+
<legend class="gw-field__label">Industry</legend>
|
|
644
|
+
<label class="gw-check"><input type="checkbox" name="industry" value="beauty" checked><span>Beauty</span><span class="gw-check__count">96</span></label>
|
|
645
|
+
<label class="gw-check"><input type="checkbox" name="industry" value="home"><span>Home</span><span class="gw-check__count">41</span></label>
|
|
646
|
+
</fieldset>
|
|
647
|
+
|
|
648
|
+
<div class="gw-field">
|
|
649
|
+
<label class="gw-field__label" for="news-email">Product news by email</label>
|
|
650
|
+
<div class="gw-form-inline">
|
|
651
|
+
<input class="gw-input" id="news-email" name="email" type="email" autocomplete="email" required aria-describedby="news-email-error news-email-note">
|
|
652
|
+
<button class="gw-btn gw-btn--secondary" type="submit">Subscribe</button>
|
|
653
|
+
</div>
|
|
654
|
+
<p class="gw-field__error" id="news-email-error"></p>
|
|
655
|
+
<p class="gw-field__hint" id="news-email-note">Once a month. Unsubscribe in one click. <a href="/privacy">Privacy policy</a></p>
|
|
656
|
+
</div>
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
**Validation is the page's.** The kit ships no validator and no form script: the page (or its server) decides what is
|
|
660
|
+
wrong and writes the words. On send, set `aria-invalid="true"` on each field in error and its message in the
|
|
661
|
+
`.gw-field__error`, then move the focus to the first one: a screen reader reads its label and its error (through
|
|
662
|
+
`aria-describedby`, the error named first). Keep the empty error element in the markup, so the description is already
|
|
663
|
+
wired. Clear both when the field is right again, on the next send or as the reader fixes it. While the form is sent,
|
|
664
|
+
`aria-busy="true"` on its button; when the answer comes, a `role="status"` line (`.gw-note`) says what happened
|
|
665
|
+
("Sent. We'll reply to name@example.com."). Use `novalidate` on the `<form>` when the page writes its own messages, so
|
|
666
|
+
the browser's bubbles do not speak over them.
|
|
667
|
+
|
|
668
|
+
Words: the label names the answer ("Work email", not "Enter your email"); a hint only when the answer is not obvious;
|
|
669
|
+
an error says what to do, not what went wrong ("Enter the whole address, like name@example.com", not "Invalid
|
|
670
|
+
input"); optional fields say "(optional)" in their label, required ones need no mark.
|
|
671
|
+
|
|
672
|
+
Don't: a placeholder as the only label; a field across a wide screen (keep a form in a `.gw-form` or a narrow
|
|
673
|
+
column); a form split into steps to start something free; a consent box ticked in advance or required to get
|
|
674
|
+
something else; an error told by its colour alone; `disabled` on a field without its reason; a filled primary button
|
|
675
|
+
in a footer form.
|
|
676
|
+
|
|
677
|
+
## Ratings and testimonials (0.6.0)
|
|
678
|
+
|
|
679
|
+
Proof from people who are not the brand: the store's score and its reviews. **Every rating and every testimonial
|
|
680
|
+
links to the page it comes from** (the store's review page, the review itself) and copies only what that page shows:
|
|
681
|
+
the score and the count on the day you copy them, the reviewer's own words (cut a long one with an ellipsis, never
|
|
682
|
+
reword it), their name and store as they wrote them. No made-up review, no score you averaged yourself, no source you
|
|
683
|
+
cannot link. `check` warns about a badge or a card without a link (`rating-source`). See them in `demo/forms.html`;
|
|
684
|
+
the section is `templates/sections/testimonials.html`.
|
|
685
|
+
|
|
686
|
+
| class | for |
|
|
687
|
+
|---|---|
|
|
688
|
+
| `.gw-stars` | a value from 0 to 5 drawn as five 16px stars in ink, the last one cut where the value ends (4.5 shows half a star); `role="img"`, `aria-label="4.9 out of 5"`, `style="--gw-stars: 4.9"` |
|
|
689
|
+
| `.gw-testimonials` | the wall: a `<ul role="list">` of cards, one column on phones, two from 768px, three from 1080px; the cards of a row share its height (no masonry) |
|
|
690
|
+
| `.gw-testimonial` (with `.gw-card`, on a `<figure>`) | one review: stars, `__quote` (a `<blockquote>`), `__by` (a `<figcaption>`: the optional `__avatar`, the name in `<b>`, the store and the link to the review) |
|
|
691
|
+
| `.gw-rating-badge` (on an `<a href>`) | the store's score: `__icon` (the store's mark, or the generic bag), `__body` with `__score` (the number and the stars) and `__count` (the reviews and where) |
|
|
692
|
+
|
|
693
|
+
```html
|
|
694
|
+
<a class="gw-rating-badge" href="https://apps.shopify.com/your-app/reviews">
|
|
695
|
+
<span class="gw-rating-badge__icon" aria-hidden="true"><svg …></svg></span>
|
|
696
|
+
<span class="gw-rating-badge__body">
|
|
697
|
+
<span class="gw-rating-badge__score"><span aria-hidden="true">4.9</span><span class="gw-stars" role="img" aria-label="4.9 out of 5" style="--gw-stars: 4.9"></span></span>
|
|
698
|
+
<span class="gw-rating-badge__count">1,234 reviews on the Shopify App Store</span>
|
|
699
|
+
</span>
|
|
700
|
+
</a>
|
|
701
|
+
|
|
702
|
+
<ul class="gw-testimonials" role="list">
|
|
703
|
+
<li>
|
|
704
|
+
<figure class="gw-card gw-testimonial">
|
|
705
|
+
<span class="gw-stars" role="img" aria-label="5 out of 5" style="--gw-stars: 5"></span>
|
|
706
|
+
<blockquote class="gw-testimonial__quote"><p>The reviewer's exact words.</p></blockquote>
|
|
707
|
+
<figcaption class="gw-testimonial__by">
|
|
708
|
+
<img class="gw-testimonial__avatar" src="/img/reviewer.webp" alt="" width="40" height="40" loading="lazy">
|
|
709
|
+
<span><b>Name</b>Store · <a href="https://apps.shopify.com/your-app/reviews?…">Shopify App Store</a></span>
|
|
710
|
+
</figcaption>
|
|
711
|
+
</figure>
|
|
712
|
+
</li>
|
|
713
|
+
</ul>
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
The stars' value is written twice on purpose, once for the drawing (`--gw-stars`) and once in words (`aria-label`),
|
|
717
|
+
and `check` reports them when they disagree, when the value is not a number from 0 to 5, or when the name is missing
|
|
718
|
+
(`stars-markup`). A number shown beside the stars is `aria-hidden="true"`, so it is not read twice. In another language
|
|
719
|
+
the label is written for its reader ("4,9 trên 5"); the number stays the same. The badge's icon is the generic bag of
|
|
720
|
+
the template, or the store's own mark only where that store's brand rules allow it; the stars stay ink on every brand.
|
|
721
|
+
|
|
722
|
+
Don't: a rating or a quote without its link; a quote longer than three sentences (link to the rest); a star row with
|
|
723
|
+
no value in words; stars in a colour of your own; a "4.9" with no count and no source; a photo of the reviewer they did
|
|
724
|
+
not make public with the review; masonry or a carousel that moves by itself.
|
|
725
|
+
|
|
421
726
|
## Motion
|
|
422
727
|
|
|
423
728
|
| class | for |
|
|
@@ -427,6 +732,23 @@ them in order); a page without `.gw-prose` that styles its articles in site CSS
|
|
|
427
732
|
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
|
|
428
733
|
the first screen, and don't animate anything but `transform` and `opacity`.
|
|
429
734
|
|
|
735
|
+
## Additions to the markup contract in 0.6.0
|
|
736
|
+
|
|
737
|
+
Forms: `.gw-field` (`__label`, `__error`, `__hint`), `.gw-input`, `.gw-select`, `.gw-textarea`, `.gw-choices`, `.gw-check`
|
|
738
|
+
and `.gw-radio` (`.gw-check__count`), `.gw-form-inline`, `.gw-form`, the states `readonly`, `disabled` and
|
|
739
|
+
`aria-invalid="true"`, `.gw-footer__newsletter`, `.gw-btn--danger` and `.gw-badge--danger`. Ratings: `.gw-stars` with its
|
|
740
|
+
`--gw-stars` input, `.gw-testimonials`, `.gw-testimonial` (`__quote`, `__by`, `__avatar`), `.gw-rating-badge` (`__icon`,
|
|
741
|
+
`__body`, `__score`, `__count`). Tokens: `--gw-danger`, `--gw-danger-soft`, `--gw-on-danger`, `--gw-line-control`. No
|
|
742
|
+
script, no hook and no event: the page owns validation. No markup or class of 0.5.0 changes.
|
|
743
|
+
|
|
744
|
+
## Additions to the markup contract in 0.5.0
|
|
745
|
+
|
|
746
|
+
The blog parts: `.gw-breadcrumbs`, `.gw-article-meta` (`__avatar`), `.gw-article` (`__head`, `__hero`), `.gw-toc` with
|
|
747
|
+
its `data-gw-toc` hook and the `gw:toc` event (`{ id }`), `.gw-callout` (`--note`, `--tip`, `--warning`, `__label`),
|
|
748
|
+
`.gw-card__media` (`--wide`, `--square`), `.gw-post-list` (`--compact`), `.gw-post` (`__title`), `.gw-pagination`
|
|
749
|
+
(`__status`), and the script's `.gw-prose__table` box with the `.is-wrapped` state on a prose table. One token:
|
|
750
|
+
`--gw-warning-soft`. No markup or class of 0.4.0 changes.
|
|
751
|
+
|
|
430
752
|
## Additions to the markup contract in 0.4.0
|
|
431
753
|
|
|
432
754
|
The script: `init(root)` and `destroy(root)` (`window.gwWebsite`, or `js/website.mjs`), the `data-gw-ready` mark on
|
package/docs/markup-contract.md
CHANGED
|
@@ -27,7 +27,8 @@ BEM-ish: `.gw-block`, `.gw-block__part`, `.gw-block--variant`.
|
|
|
27
27
|
## Script (0.4.0)
|
|
28
28
|
|
|
29
29
|
One script wires every part below: the mega menu, the mobile menu, the theme toggle, reveal, the header's scroll
|
|
30
|
-
state, the current page, the segmented control
|
|
30
|
+
state, the current page, the segmented control, the busy button, an article's contents list and the scroll box of its
|
|
31
|
+
prose tables (0.5.0). It comes in two entries built from one source
|
|
31
32
|
(`js/website.mjs`; `scripts/build.mjs` writes `js/website.js` from it and `--check` fails when they differ):
|
|
32
33
|
|
|
33
34
|
| entry | file | how it starts |
|
|
@@ -42,9 +43,9 @@ state, the current page, the segmented control and the busy button. It comes in
|
|
|
42
43
|
to the page (Escape, the outside click, the busy-button guard, the system theme, the scroll) are installed once.
|
|
43
44
|
- **`destroy(root = document)`** takes back what `init` wired under `root`: its listeners and its marks. An open
|
|
44
45
|
mega menu or mobile menu is closed first, so an unmounted menu cannot leave the page locked.
|
|
45
|
-
- **`version`**: `'0.
|
|
46
|
+
- **`version`**: `'0.6.0'`. The classic script exposes the three as `window.gwWebsite`.
|
|
46
47
|
- **`data-gw-ready`**: on each wired element, the parts wired on it (`mega`, `menu`, `theme`, `reveal`, `header`,
|
|
47
|
-
`segmented`). It is a mark for people and tests, not a switch: markup copied from a live page, attribute and all,
|
|
48
|
+
`segmented`, `toc`, `table`). It is a mark for people and tests, not a switch: markup copied from a live page, attribute and all,
|
|
48
49
|
is still wired.
|
|
49
50
|
- **One copy per page.** The first copy that runs owns the page (`window.gwWebsite`); another one (the classic
|
|
50
51
|
script next to a bundle, two bundles) hands its calls to it, so nothing is wired twice.
|
|
@@ -93,9 +94,11 @@ The script tells the page's own code what changed. Every event is a `CustomEvent
|
|
|
93
94
|
| `gw:mega` | the trigger (`[data-gw-mega]`) | `{ id, open }`: the panel's `id`, `true` when it opened | a panel opens or closes: click, hover, Escape, a click outside, focus leaving, a link inside, `destroy` of an open one. Moving to another menu fires a close, then an open |
|
|
94
95
|
| `gw:theme` | `<html>` | `{ theme: 'light' \| 'dark' }`: the theme the page now shows | a press on a theme toggle; also a change of the system's scheme while the page follows it (no `data-theme`) |
|
|
95
96
|
| `gw:segmented` | the group (`[data-gw-segmented]`) | `{ name, value }`: the group's name, the chosen option's `value` | the reader chooses an option (click, arrows, Home, End) |
|
|
97
|
+
| `gw:toc` | the contents (`[data-gw-toc]`) | `{ id }`: the id of the heading whose section is now marked, `''` above the first one | the section in view changes: scrolling, a contents link, a jump. Also once after load when the page opens inside a section (a link to `#id`): the observer reports after `init`, never during it |
|
|
96
98
|
|
|
97
99
|
```js
|
|
98
100
|
document.addEventListener('gw:theme', function (e) { chart.setTheme(e.detail.theme); });
|
|
101
|
+
document.addEventListener('gw:toc', function (e) { analytics.track('section', e.detail.id); });
|
|
99
102
|
```
|
|
100
103
|
|
|
101
104
|
## Header
|
|
@@ -157,6 +160,107 @@ Script behaviour:
|
|
|
157
160
|
menu's links whose `href` pathname equals `location.pathname` (when the markup did not already). Each `init` takes
|
|
158
161
|
its own marks back first, so after a client-side navigation they follow the new path; a mark in the markup stays.
|
|
159
162
|
|
|
163
|
+
## Article: contents and prose tables (0.5.0)
|
|
164
|
+
|
|
165
|
+
```html
|
|
166
|
+
<article class="gw-wrap gw-article">
|
|
167
|
+
<header class="gw-article__head"> .gw-breadcrumbs, <h1>, .gw-article-meta </header>
|
|
168
|
+
<figure class="gw-article__hero"><img …></figure> <!-- optional -->
|
|
169
|
+
<nav class="gw-toc" aria-label="On this page" data-gw-toc>
|
|
170
|
+
<details> <!-- no open: the script opens it from 1080px -->
|
|
171
|
+
<summary>On this page</summary>
|
|
172
|
+
<ol><li><a href="#section-id">The section's heading</a><ol>…h3s…</ol></li> …</ol>
|
|
173
|
+
</details>
|
|
174
|
+
</nav>
|
|
175
|
+
<div class="gw-prose"> <h2 id="section-id">…</h2> … <table><caption>…</caption>…</table> … </div>
|
|
176
|
+
</article>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- **Contents** (`[data-gw-toc]`, a `.gw-toc`): the script reads its links whose `href` is `#id` of an element on the
|
|
180
|
+
page (percent-encoded ids are decoded; links to other pages and to no id are ignored). With none, the list is not
|
|
181
|
+
wired yet and a later `init` tries again.
|
|
182
|
+
- The section in view is the one whose heading is the last above the line 30% down the window. Its link gets
|
|
183
|
+
`aria-current="true"`; the previous link loses it. Above the first heading no link is marked. Each change fires
|
|
184
|
+
`gw:toc` on the nav.
|
|
185
|
+
- One `IntersectionObserver` per list, observing the linked headings, with
|
|
186
|
+
`rootMargin: '100000px 0px -70% 0px'`: its root runs from far above the window down to the line, so a heading
|
|
187
|
+
is "intersecting" exactly while it is above the line, and a jump across many sections is seen as well as a
|
|
188
|
+
scroll. Each report measures every heading again. Without `IntersectionObserver` nothing is marked; the links
|
|
189
|
+
still work.
|
|
190
|
+
- The `<details>` inside: from 1080px (`matchMedia('(min-width: 1080px)')`, the kit's desktop width, where the CSS makes it sticky) the
|
|
191
|
+
script sets `open`; below it, the markup's own state (folded unless the markup says `open`), again on every
|
|
192
|
+
change of width. Below 1080px a click on a link folds it, in the click, before the browser moves to the section.
|
|
193
|
+
- `destroy`: the observer disconnects, the mark goes, the `<details>` takes the markup's state back.
|
|
194
|
+
- **Prose tables**: every `<table>` that is a child of a `.gw-prose` (not one inside the author's own `.gw-table`, a
|
|
195
|
+
figure or another element) is moved into `<div class="gw-prose__table" role="region" tabindex="0" aria-label="…">`
|
|
196
|
+
at its place, and gets `.is-wrapped`.
|
|
197
|
+
- The name is the text of the table's `<caption>`, else of its first `<th>`, else of its first `<td>`, spaces
|
|
198
|
+
collapsed. A table with no text there is left as it is.
|
|
199
|
+
- The CSS keeps a wrapped table `display: table` (some Safari versions stop announcing a table whose display
|
|
200
|
+
changed) and gives the box the scroll, the hairline and the radius. A table without `.is-wrapped` (no script,
|
|
201
|
+
or before it runs) scrolls by itself with `display: block`, as in 0.2.0. Both look the same.
|
|
202
|
+
- Once per table: a second `init` finds it inside its box and leaves it. `destroy` puts the table back in the
|
|
203
|
+
prose, removes the box, and removes `.is-wrapped` unless the markup had it.
|
|
204
|
+
- A framework that renders the prose's table itself (JSX, not HTML from the CMS) should call `destroy(prose)`
|
|
205
|
+
before it re-renders that part, as for every element the script changed.
|
|
206
|
+
|
|
207
|
+
## Forms (0.6.0)
|
|
208
|
+
|
|
209
|
+
CSS only: the script does not touch a form. The page (or its server) owns validation and the words.
|
|
210
|
+
|
|
211
|
+
```html
|
|
212
|
+
<form class="gw-form" action="…" method="post" novalidate> <!-- or .gw-card.gw-form; novalidate when the page writes its own messages -->
|
|
213
|
+
<div class="gw-field">
|
|
214
|
+
<label class="gw-field__label" for="ID">Label</label>
|
|
215
|
+
<input class="gw-input" id="ID" … aria-describedby="ID-error ID-hint" aria-invalid?="true"> <!-- or select.gw-select, textarea.gw-textarea -->
|
|
216
|
+
<p class="gw-field__error" id="ID-error">What to do</p> <!-- empty until the page fills it; empty = no room -->
|
|
217
|
+
<p class="gw-field__hint" id="ID-hint">Help</p> <!-- optional -->
|
|
218
|
+
</div>
|
|
219
|
+
<fieldset class="gw-choices">
|
|
220
|
+
<legend class="gw-field__label">Question</legend>
|
|
221
|
+
<label class="gw-check"><input type="checkbox" …><span>Words</span><span class="gw-check__count">12</span></label> <!-- or .gw-radio with type="radio"; __count optional -->
|
|
222
|
+
</fieldset>
|
|
223
|
+
<div class="gw-field"> <!-- one field and its button -->
|
|
224
|
+
<label class="gw-field__label" for="ID2">Label</label>
|
|
225
|
+
<div class="gw-form-inline"><input class="gw-input" id="ID2" …><button class="gw-btn" type="submit">Action</button></div>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="gw-actions"><button class="gw-btn" type="submit">Send</button><p class="gw-note" role="status"></p></div>
|
|
228
|
+
</form>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- The error and the hint are children of `.gw-field` (or `.gw-choices`); `aria-describedby` names the error first.
|
|
232
|
+
- States are attributes, never classes: `aria-invalid="true"` (danger border and tint; on a checkbox or radio, a
|
|
233
|
+
danger box while unchecked), `readonly`, `disabled`. Hover and focus are the browser's.
|
|
234
|
+
- `.gw-check` / `.gw-radio` wrap exactly one `<input>` and its words, so the whole row is the target.
|
|
235
|
+
- `.gw-footer__newsletter` is a `<form>` in `.gw-footer__brand`, after the brand's sentence.
|
|
236
|
+
|
|
237
|
+
## Ratings (0.6.0)
|
|
238
|
+
|
|
239
|
+
```html
|
|
240
|
+
<span class="gw-stars" role="img" aria-label="N out of 5" style="--gw-stars: N"></span> <!-- N from 0 to 5, the same in both -->
|
|
241
|
+
|
|
242
|
+
<a class="gw-rating-badge" href="SOURCE">
|
|
243
|
+
<span class="gw-rating-badge__icon" aria-hidden="true"><svg|img></span>
|
|
244
|
+
<span class="gw-rating-badge__body">
|
|
245
|
+
<span class="gw-rating-badge__score"><span aria-hidden="true">N</span><span class="gw-stars" …></span></span>
|
|
246
|
+
<span class="gw-rating-badge__count">COUNT reviews on STORE</span>
|
|
247
|
+
</span>
|
|
248
|
+
</a>
|
|
249
|
+
|
|
250
|
+
<ul class="gw-testimonials" role="list">
|
|
251
|
+
<li><figure class="gw-card gw-testimonial">
|
|
252
|
+
<span class="gw-stars" …></span>
|
|
253
|
+
<blockquote class="gw-testimonial__quote"><p>Words</p></blockquote>
|
|
254
|
+
<figcaption class="gw-testimonial__by"><img class="gw-testimonial__avatar" alt="" …>?<span><b>Name</b>Store · <a href="SOURCE">Where</a></span></figcaption>
|
|
255
|
+
</figure></li>
|
|
256
|
+
</ul>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
- `--gw-stars` is the one `--gw-` variable a page writes, on the `.gw-stars` element itself: an input, not a token.
|
|
260
|
+
`check` accepts it there and reports a value outside 0 to 5, a missing `role="img"` or name, and a name that does not
|
|
261
|
+
carry the same number (`stars-markup`), and a `.gw-rating-badge` or `.gw-testimonial` without an `<a href>`
|
|
262
|
+
(`rating-source`, a warning).
|
|
263
|
+
|
|
160
264
|
## Brand profiles (0.3.0)
|
|
161
265
|
|
|
162
266
|
- `data-gw-action` and `data-gw-radius` sit on `<html>` only, with a name from `system.json` `profiles` (`ink` or
|
|
@@ -200,20 +304,31 @@ Script behaviour:
|
|
|
200
304
|
|
|
201
305
|
| class | what |
|
|
202
306
|
|---|---|
|
|
203
|
-
| `.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) |
|
|
307
|
+
| `.gw-btn` (+ `--secondary`, `--ghost`, `--on-night`, `--danger` (0.6.0), `--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) |
|
|
204
308
|
| `.gw-link` (+ `--arrow`) | inline text link in the accent; `--arrow` adds → that moves 2px on hover |
|
|
205
309
|
| `.gw-label` | the label above a heading: 13px muted, 8px accent square before it |
|
|
206
310
|
| `.gw-dim` | the muted first sentence inside a headline |
|
|
207
311
|
| `.gw-chip` (+ `.is-active`) | selectable token, pill |
|
|
208
312
|
| `.gw-segmented` (+ `--block`) | one choice out of two to four (script above) |
|
|
209
|
-
| `.gw-badge` (+ `--accent`, `--success`, `--warning`) | small status, pill |
|
|
313
|
+
| `.gw-badge` (+ `--accent`, `--success`, `--warning`, `--danger` (0.6.0)) | small status, pill |
|
|
314
|
+
| `.gw-field` (`__label`, `__error`, `__hint`), `.gw-input`, `.gw-select`, `.gw-textarea` | a field and its controls; states by attribute (forms above, 0.6.0) |
|
|
315
|
+
| `.gw-choices`, `.gw-check`, `.gw-radio` (`.gw-check__count`) | a group of checkboxes or radios, a row per choice (0.6.0) |
|
|
316
|
+
| `.gw-form-inline`, `.gw-form` | one field and its button on a line; a short form's stack (0.6.0) |
|
|
317
|
+
| `.gw-stars`, `.gw-rating-badge` (`__icon`, `__body`, `__score`, `__count`) | a 0 to 5 value; the store's score, a link to its reviews (ratings above, 0.6.0) |
|
|
210
318
|
| `.gw-card` (+ `--soft`, `--link`) | white box, 1px hairline, the card radius; `--link` makes the whole card an `<a>` |
|
|
319
|
+
| `.gw-card__media` (+ `--wide`, `--square`) | the card's picture, first child, edge to edge on top; 16:9, 1.91:1 or 1:1 (0.5.0) |
|
|
320
|
+
| `.gw-callout` (+ `--note`, `--tip`, `--warning`, `__label`) | a note, a tip or a warning in an article, `role="note"`, its kind in words (0.5.0) |
|
|
321
|
+
| `.gw-breadcrumbs` | `<nav aria-label>` + `<ol>`, the last item `aria-current="page"`; separators drawn by the CSS (0.5.0) |
|
|
322
|
+
| `.gw-article-meta` (+ `__avatar`) | author, category, `<time datetime>`, read time (0.5.0) |
|
|
323
|
+
| `.gw-article` (`__head`, `__hero`), `.gw-toc` | the article page and its contents (script above, 0.5.0) |
|
|
324
|
+
| `.gw-post-list` (+ `--compact`), `.gw-post` (`__title`) | post cards; the title's link covers the card (0.5.0) |
|
|
325
|
+
| `.gw-pagination` (`__status`) | previous / next links, `rel="prev"` / `rel="next"`, markup only (0.5.0) |
|
|
211
326
|
| `.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 |
|
|
212
327
|
| `.gw-icon` | 20px inline stroke SVG wrapper |
|
|
213
328
|
| `.gw-list--check` | list with check marks |
|
|
214
329
|
| `.gw-table` (+ `--compare`) | data / comparison table; scrolls inside its box on phones |
|
|
215
330
|
| `.gw-faq` (`<details class="gw-faq__item"><summary>`) | FAQ accordion with no script |
|
|
216
|
-
| `.gw-prose` (+ `--sm`) | long-form reading: an article's own HTML, no classes inside |
|
|
331
|
+
| `.gw-prose` (+ `--sm`) | long-form reading: an article's own HTML, no classes inside; its bare tables get a `.gw-prose__table` box from the script (0.5.0) |
|
|
217
332
|
| `.gw-tabs` (`role="tablist"`) | NOT in the kit yet (a reported gap would add it) |
|
|
218
333
|
|
|
219
334
|
## Sections
|
|
@@ -231,6 +346,7 @@ Script behaviour:
|
|
|
231
346
|
| `.gw-showcase` | night band with a large screen and two or three points |
|
|
232
347
|
| `.gw-steps` (`<ol>`) | numbered steps, how it works |
|
|
233
348
|
| `.gw-quote` | one real testimonial with name, role, company |
|
|
349
|
+
| `.gw-testimonials` (`.gw-card.gw-testimonial`: `__quote`, `__by`, `__avatar`) | a wall of short reviews, each linked to its source (0.6.0) |
|
|
234
350
|
| `.gw-pricing` (`.gw-plan`, `--featured`) | plan cards; the featured one has a hairline in the accent, not a fill |
|
|
235
351
|
| `.gw-cta` | closing call to action band |
|
|
236
|
-
| `.gw-footer` (`__grid`, `__col`, `__heading`, `__bottom`) | footer columns + bottom row |
|
|
352
|
+
| `.gw-footer` (`__grid`, `__col`, `__heading`, `__bottom`, `__newsletter`) | footer columns + bottom row; an optional newsletter form in the brand block (0.6.0) |
|
package/docs/theming.md
CHANGED
|
@@ -57,7 +57,7 @@ needs no markup, so a GemBoss page, and every page written for 0.2.0, stays exac
|
|
|
57
57
|
| knob | profiles | what changes |
|
|
58
58
|
|---|---|---|
|
|
59
59
|
| action colour, `data-gw-action` on `<html>` | `ink` (GemBoss, default), `accent` | `accent`: the primary `.gw-btn` is filled with `--gw-accent`, its words are `--gw-on-accent`, hover and pressed are `--gw-accent-strong`. In dark mode and night bands it takes the `-dark` family. Secondary, ghost and on-night buttons do not change. |
|
|
60
|
-
| radius, `data-gw-radius` on `<html>` | `tight` (GemBoss, default): 8 action, 12 card, 16 panel, 20 mega menu; `soft` (GemPages): 12, 20, 24, 24 | every button, control, card, window, table, FAQ, plan, quote and the mega menu. Chips and
|
|
60
|
+
| radius, `data-gw-radius` on `<html>` | `tight` (GemBoss, default): 8 action, 12 card, 16 panel, 20 mega menu; `soft` (GemPages): 12, 20, 24, 24 | every button, control (fields, lists, the rating badge's icon), card, window, table, FAQ, plan, quote and the mega menu. Chips, badges and a filter's count stay pills; a checkbox keeps its 4px. |
|
|
61
61
|
| header, a class on the `<header>` | flat (default), `.gw-header--float` | from 1080px the bar becomes an inset card: the panel radius of the profile (16 tight, 24 soft), the surface at 85% over a 10px blur, one hairline, the raised shadow, 16px from the top. Same bar, same mega menu, same script; below 1080px it is the flat bar, so the phone menu is unchanged. Solid for a visitor who asks for less transparency, and in a browser that cannot blur. |
|
|
62
62
|
|
|
63
63
|
Why the action colour is a mode and not a colour of its own: the button stays one colour on the whole site, and that
|
|
@@ -71,10 +71,11 @@ the panels; in `tight` that is the 16px the header was designed with.
|
|
|
71
71
|
## What you may not change
|
|
72
72
|
|
|
73
73
|
- **Neutrals**: `--gw-bg`, `--gw-bg-soft`, `--gw-surface`, `--gw-ink`, `--gw-ink-2`, `--gw-muted`, `--gw-line`,
|
|
74
|
-
`--gw-line-strong`, and the night colours. No cream or grey page, no brand-coloured ink.
|
|
74
|
+
`--gw-line-strong`, `--gw-line-control` (a form control's border, 3:1), and the night colours. No cream or grey page, no brand-coloured ink.
|
|
75
75
|
- **The action tokens**: `--gw-action`, `--gw-action-hover`, `--gw-on-action`. Choose the `accent` profile instead;
|
|
76
76
|
a colour set here is not checked and does not follow dark mode or night bands.
|
|
77
|
-
- **State**: `--gw-success`, `--gw-warning` and their tints. Green
|
|
77
|
+
- **State**: `--gw-success`, `--gw-warning`, `--gw-danger` and their tints (`--gw-on-danger` too). Green, amber and red
|
|
78
|
+
mean state on every GemCommerce site; the stars of a rating stay ink.
|
|
78
79
|
- **Scales and shapes**: type sizes and weights, spacing, the radius tokens (`--gw-radius-*`: choose a profile), shadows,
|
|
79
80
|
widths, the header height, the font family. No pill buttons, no bigger shadows, no other font.
|
|
80
81
|
- **Kit classes**: never restyle a `.gw-*` class. If the kit cannot do what your brand needs, that is a gap
|