@gemboss/website-ui 0.2.0 → 0.4.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 +52 -23
- package/CHANGELOG.md +63 -0
- package/README.md +45 -15
- package/bin/lib/check.mjs +76 -7
- package/bin/lib/contrast.mjs +67 -0
- package/css/components.css +8 -6
- package/css/header.css +37 -5
- package/css/prose.css +3 -2
- package/css/tokens.css +19 -0
- package/demo/brand-profiles.html +199 -0
- package/docs/components.md +56 -11
- package/docs/markup-contract.md +106 -18
- package/docs/philosophy.md +14 -9
- package/docs/theming.md +85 -29
- package/js/website.js +222 -168
- package/js/website.mjs +269 -0
- package/package.json +2 -1
- package/skill/SKILL.md +5 -1
- package/system.json +31 -7
- package/templates/header.html +3 -1
- package/templates/page.html +2 -0
- package/website.css +68 -14
- package/website.min.css +1 -1
package/docs/markup-contract.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
The full class list, including the CSS-only helpers added while building 0.1.0 (`.gw-h1`–`.gw-h4`, `.gw-lead`,
|
|
4
4
|
`.gw-note`, `.gw-actions`, `.gw-stats__source`, `.gw-plan__price`, the mobile menu parts…), is `docs/components.md`.
|
|
5
5
|
|
|
6
|
-
The CSS (`css/*.css`), the script (`js/website.
|
|
7
|
-
here first, then in all three.
|
|
6
|
+
The CSS (`css/*.css`), the script (`js/website.mjs`, and `js/website.js` built from it) and the templates
|
|
7
|
+
(`templates/`) agree on exactly this. Change it here first, then in all three.
|
|
8
8
|
|
|
9
9
|
Prefixes: kit classes `gw-`, kit CSS variables `--gw-`, state classes `is-` (set by the script), hooks `data-gw-*`.
|
|
10
10
|
BEM-ish: `.gw-block`, `.gw-block__part`, `.gw-block--variant`.
|
|
@@ -12,20 +12,96 @@ BEM-ish: `.gw-block`, `.gw-block__part`, `.gw-block--variant`.
|
|
|
12
12
|
## Page
|
|
13
13
|
|
|
14
14
|
```html
|
|
15
|
-
<html lang="en" data-theme?="light|dark"
|
|
15
|
+
<html lang="en" data-theme?="light|dark" data-gw-action?="accent" data-gw-radius?="soft">
|
|
16
|
+
<!-- data-theme absent = follow the system; the toggle sets it. data-gw-action, data-gw-radius: brand profiles (0.3.0),
|
|
17
|
+
absent = ink, tight -->
|
|
16
18
|
<body class="gw-page">
|
|
17
19
|
<a class="gw-skip" href="#main">Skip to content</a>
|
|
18
20
|
<div class="gw-announce" role="region" aria-label="Announcement">…<a href="…">…</a></div> <!-- optional -->
|
|
19
21
|
<header class="gw-header" data-gw-header> … </header>
|
|
20
22
|
<main id="main"> sections </main>
|
|
21
23
|
<footer class="gw-footer"> … </footer>
|
|
22
|
-
<script src="…/website.js" defer></script>
|
|
24
|
+
<script src="…/website.js" defer></script> <!-- or the ESM entry, see "Script" below -->
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Script (0.4.0)
|
|
28
|
+
|
|
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 and the busy button. It comes in two entries built from one source
|
|
31
|
+
(`js/website.mjs`; `scripts/build.mjs` writes `js/website.js` from it and `--check` fails when they differ):
|
|
32
|
+
|
|
33
|
+
| entry | file | how it starts |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| classic | `js/website.js` (`@gemboss/website-ui/website.js`) | `<script src="…/website.js" defer>`: wires the page on `DOMContentLoaded` by itself, then `window.gwWebsite` |
|
|
36
|
+
| ESM | `js/website.mjs` (`@gemboss/website-ui/website.mjs`) | `import { init, destroy, version } from '@gemboss/website-ui/website.mjs'`: importing does nothing (safe on a server); the app calls `init()` |
|
|
37
|
+
|
|
38
|
+
- **`init(root = document)`** wires the kit markup under `root`, `root` included. It is idempotent: an element is
|
|
39
|
+
wired once, so call it again after every client-side navigation or insert, and only the new elements are wired.
|
|
40
|
+
Called on a fragment, it wires that fragment and nothing else. It also lets go of elements removed from the page
|
|
41
|
+
since the last call, and moves the script's own `aria-current` mark to the new path. The listeners that belong
|
|
42
|
+
to the page (Escape, the outside click, the busy-button guard, the system theme, the scroll) are installed once.
|
|
43
|
+
- **`destroy(root = document)`** takes back what `init` wired under `root`: its listeners and its marks. An open
|
|
44
|
+
mega menu or mobile menu is closed first, so an unmounted menu cannot leave the page locked.
|
|
45
|
+
- **`version`**: `'0.4.0'`. The classic script exposes the three as `window.gwWebsite`.
|
|
46
|
+
- **`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
|
+
is still wired.
|
|
49
|
+
- **One copy per page.** The first copy that runs owns the page (`window.gwWebsite`); another one (the classic
|
|
50
|
+
script next to a bundle, two bundles) hands its calls to it, so nothing is wired twice.
|
|
51
|
+
- Never wire a kit part with the site's own script (a click handler on `.gw-nav__trigger`, a class toggled by hand):
|
|
52
|
+
insert the kit's markup, then call `init` on it.
|
|
53
|
+
|
|
54
|
+
### After inserting markup
|
|
55
|
+
|
|
56
|
+
A Next.js app (App Router): one client component in the root layout, an effect on each route change. Effects run
|
|
57
|
+
after hydration, so the server's markup is never changed before React reads it.
|
|
58
|
+
|
|
59
|
+
```jsx
|
|
60
|
+
// app/kit-script.jsx
|
|
61
|
+
'use client';
|
|
62
|
+
import { useEffect } from 'react';
|
|
63
|
+
import { usePathname } from 'next/navigation';
|
|
64
|
+
import { init } from '@gemboss/website-ui/website.mjs';
|
|
65
|
+
|
|
66
|
+
export default function KitScript() {
|
|
67
|
+
const pathname = usePathname();
|
|
68
|
+
useEffect(() => { init(); }, [pathname]);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
// app/layout.js: <body className="gw-page">{children}<KitScript /></body>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A component that mounts and unmounts kit markup on its own (a dialog, a part that streams in later):
|
|
75
|
+
`useEffect(() => { const el = ref.current; init(el); return () => destroy(el); }, [])`, on a wrapper with
|
|
76
|
+
`style={{ display: 'contents' }}` so it does not change the layout.
|
|
77
|
+
|
|
78
|
+
A page that inserts HTML (a GemPages block rendered after load, a CMS preview), with the classic script:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
var slot = document.getElementById('pricing-slot');
|
|
82
|
+
slot.innerHTML = html; // kit markup: a header, a segmented control, sections
|
|
83
|
+
window.gwWebsite.init(slot); // wires what is new in it; the rest of the page is left alone
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Events
|
|
87
|
+
|
|
88
|
+
The script tells the page's own code what changed. Every event is a `CustomEvent` that bubbles, so one listener on
|
|
89
|
+
`document` hears them all; none is fired by `init`.
|
|
90
|
+
|
|
91
|
+
| event | target | `detail` | fires when |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| `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
|
+
| `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
|
+
| `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) |
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
document.addEventListener('gw:theme', function (e) { chart.setTheme(e.detail.theme); });
|
|
23
99
|
```
|
|
24
100
|
|
|
25
101
|
## Header
|
|
26
102
|
|
|
27
103
|
```html
|
|
28
|
-
<header class="gw-header" data-gw-header>
|
|
104
|
+
<header class="gw-header gw-header--float?" data-gw-header> <!-- --float: a brand's header profile (0.3.0) -->
|
|
29
105
|
<div class="gw-wrap gw-header__bar">
|
|
30
106
|
<a class="gw-brand" href="/"><img class="gw-brand__mark" src="…" alt="" width="26" height="26"><span class="gw-brand__name">GemBoss</span></a>
|
|
31
107
|
<nav class="gw-nav" aria-label="Main">
|
|
@@ -59,23 +135,35 @@ BEM-ish: `.gw-block`, `.gw-block__part`, `.gw-block--variant`.
|
|
|
59
135
|
Script behaviour:
|
|
60
136
|
- **Mega menu**: click on a trigger toggles its panel (`hidden` removed, `aria-expanded="true"`, `.is-open` on the
|
|
61
137
|
`.gw-nav__item`). Hover opens on fine pointers after 80ms and closes 160ms after leaving item and panel. Only one
|
|
62
|
-
open at a time. `Escape` closes and returns focus to the trigger. A
|
|
138
|
+
open at a time on the page, across every header `init` wired. `Escape` closes and returns focus to the trigger. A
|
|
139
|
+
click outside closes. Arrow keys are not required. Each change fires `gw:mega`.
|
|
63
140
|
- **Mobile menu** (below 1080px the `.gw-nav` and sign-in hide, `.gw-menu-toggle` shows): the toggle flips `hidden`
|
|
64
|
-
on `#gw-mobile-menu
|
|
65
|
-
(Open menu / Close menu). `Escape` closes. Resizing above 1080px closes it.
|
|
141
|
+
on the menu its `aria-controls` names (`#gw-mobile-menu`), `aria-expanded`, `.is-menu-open` on `<html>` (the CSS
|
|
142
|
+
locks scroll), and its `aria-label` (Open menu / Close menu). `Escape` closes. Resizing above 1080px closes it.
|
|
66
143
|
- **Labels in other languages**: the script never writes English over a translated page. Give the toggles their
|
|
67
144
|
words with `data-gw-label-open` / `data-gw-label-close` (menu toggle) and `data-gw-label-dark` /
|
|
68
145
|
`data-gw-label-light` (theme toggle); without them it keeps the markup's own `aria-label`, then falls back to English.
|
|
69
146
|
- **Theme toggle**: cycles light ↔ dark by setting `data-theme` on `<html>`, stores the choice in `localStorage`
|
|
70
|
-
(`gw-theme`, every access in try/catch), updates `aria-label`. On load, a stored
|
|
71
|
-
one-line inline script in `<head>` that the template carries
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
147
|
+
(`gw-theme`, every access in try/catch), updates `aria-label` on every toggle, fires `gw:theme`. On load, a stored
|
|
148
|
+
choice is applied before paint by a one-line inline script in `<head>` that the template carries
|
|
149
|
+
(`templates/page.html`).
|
|
150
|
+
- **Reveal**: elements with `.gw-reveal` get `.is-in` when 15% visible (IntersectionObserver, once). Failsafe: 2.5s
|
|
151
|
+
after the `init` that found them, or at once if IntersectionObserver is missing, they get `.is-in`. Under
|
|
152
|
+
`prefers-reduced-motion` the CSS shows them without motion.
|
|
75
153
|
- **Header**: `.is-scrolled` on `[data-gw-header]` once the page scrolled more than 8px (the CSS may use it for the
|
|
76
|
-
hairline only; the bar does not change height or colour).
|
|
77
|
-
|
|
78
|
-
|
|
154
|
+
hairline only; the bar does not change height or colour). The floating header needs nothing more from the script:
|
|
155
|
+
it is the same element with one more class.
|
|
156
|
+
- **Current page**: the script marks `aria-current="page"` on `.gw-nav__link` / `.gw-mega__link` and the mobile
|
|
157
|
+
menu's links whose `href` pathname equals `location.pathname` (when the markup did not already). Each `init` takes
|
|
158
|
+
its own marks back first, so after a client-side navigation they follow the new path; a mark in the markup stays.
|
|
159
|
+
|
|
160
|
+
## Brand profiles (0.3.0)
|
|
161
|
+
|
|
162
|
+
- `data-gw-action` and `data-gw-radius` sit on `<html>` only, with a name from `system.json` `profiles` (`ink` or
|
|
163
|
+
`accent`, `tight` or `soft`); absent means the first. The CSS reads them (`css/tokens.css`), the script does not.
|
|
164
|
+
- `.gw-header--float` sits on the `<header class="gw-header">` itself, next to `gw-header`.
|
|
165
|
+
- `check` reports any other name or place (`profile-unknown`, `header-float-markup`) and, with `accent`, words on the
|
|
166
|
+
button below 4.5:1 (`action-contrast`).
|
|
79
167
|
|
|
80
168
|
## Segmented control (0.2.0)
|
|
81
169
|
|
|
@@ -112,14 +200,14 @@ Script behaviour:
|
|
|
112
200
|
|
|
113
201
|
| class | what |
|
|
114
202
|
|---|---|
|
|
115
|
-
| `.gw-btn` (+ `--secondary`, `--ghost`, `--on-night`, `--sm`, `--lg`, `--block`) | actions; rectangle
|
|
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) |
|
|
116
204
|
| `.gw-link` (+ `--arrow`) | inline text link in the accent; `--arrow` adds → that moves 2px on hover |
|
|
117
205
|
| `.gw-label` | the label above a heading: 13px muted, 8px accent square before it |
|
|
118
206
|
| `.gw-dim` | the muted first sentence inside a headline |
|
|
119
207
|
| `.gw-chip` (+ `.is-active`) | selectable token, pill |
|
|
120
208
|
| `.gw-segmented` (+ `--block`) | one choice out of two to four (script above) |
|
|
121
209
|
| `.gw-badge` (+ `--accent`, `--success`, `--warning`) | small status, pill |
|
|
122
|
-
| `.gw-card` (+ `--soft`, `--link`) | white box, 1px hairline, radius
|
|
210
|
+
| `.gw-card` (+ `--soft`, `--link`) | white box, 1px hairline, the card radius; `--link` makes the whole card an `<a>` |
|
|
123
211
|
| `.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 |
|
|
124
212
|
| `.gw-icon` | 20px inline stroke SVG wrapper |
|
|
125
213
|
| `.gw-list--check` | list with check marks |
|
package/docs/philosophy.md
CHANGED
|
@@ -7,26 +7,31 @@ spirit. Each rule came from measuring real pages, not from taste alone.
|
|
|
7
7
|
|
|
8
8
|
A visitor crosses from the website into the product in one click. If the two disagree (a cream site and a white app,
|
|
9
9
|
a purple button outside and a black one inside), the seam is exactly where trust is decided. So the website uses the
|
|
10
|
-
product's language: white canvas,
|
|
10
|
+
product's language: white canvas, the product's action colour, one accent, hairlines, almost no shadow.
|
|
11
11
|
|
|
12
|
-
## The thing you click is
|
|
12
|
+
## The thing you click is one colour: the product's
|
|
13
13
|
|
|
14
|
-
A page has one thing it wants you to do
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
A page has one thing it wants you to do, and the button that does it looks the same on every page. For GemBoss that
|
|
15
|
+
is near-black ink, as in its dashboard: the strongest mark on white, readable in every light. Since 0.3.0 a brand
|
|
16
|
+
whose product clicks in its accent (GemPages' product uses blue primary buttons) may do the same on its site, with
|
|
17
|
+
the `accent` action profile: the site and the product still speak one language. It is a mode, not a colour picked
|
|
18
|
+
per button; its words must reach 4.5:1 on it, and it never gains a shadow or a gradient. Everywhere else the accent
|
|
19
|
+
marks where you are and what is interactive in text: links, focus, the active item, the small square before a
|
|
20
|
+
label. It never colours the words of a heading or a whole section band: that makes a page louder, not clearer.
|
|
18
21
|
|
|
19
22
|
## Shapes carry meaning
|
|
20
23
|
|
|
21
24
|
Rectangles act, pills choose. When every button is a rectangle and every chip is a pill, people stop reading the
|
|
22
|
-
shape and read the words. A
|
|
23
|
-
the kind of drift that makes a page feel
|
|
25
|
+
shape and read the words. A site has one set of corners, a named profile (`tight` for GemBoss, `soft` for GemPages,
|
|
26
|
+
as in each product), so two cards side by side never differ by 2px, which is the kind of drift that makes a page feel
|
|
27
|
+
assembled rather than designed. Profiles, not free values: a brand can look like itself without inventing a radius.
|
|
24
28
|
|
|
25
29
|
## Separation by line and space, light only at rest
|
|
26
30
|
|
|
27
31
|
A shadow says "this floats above". Cards on a page do not float; they sit on it. A 1px hairline and the dashboard's
|
|
28
32
|
resting shadow (two faint pixels) separate a card and keep the page calm; nothing lifts on hover. Larger light is
|
|
29
|
-
spent where it means something: a popover such as the mega menu,
|
|
33
|
+
spent where it means something: a popover such as the mega menu, the floating header a brand may choose (it does
|
|
34
|
+
float), an illustration that lights itself.
|
|
30
35
|
|
|
31
36
|
## Scales instead of eyeballing
|
|
32
37
|
|
package/docs/theming.md
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
# Theming for a brand other than GemBoss
|
|
2
2
|
|
|
3
|
-
A GemCommerce brand (GemPages, Gemians…) uses the kit as it is and
|
|
4
|
-
wash hues**, **its logo** and **its favicon**. Everything else stays the kit's: the shapes, the scales, the ink
|
|
5
|
-
buttons, the white canvas, the type. That is what makes the sites look like one family, and it is why a site built
|
|
6
|
-
by someone with no design training still looks careful.
|
|
3
|
+
A GemCommerce brand (GemPages, Gemians…) uses the kit as it is and chooses six things, **and nothing else**:
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
1. **the accent variables** (eight, light and dark),
|
|
6
|
+
2. **the wash hues** (only if the brand has its own),
|
|
7
|
+
3. **the logo** (and the favicon),
|
|
8
|
+
4. **the action colour mode**: `data-gw-action="ink|accent"` on `<html>`,
|
|
9
|
+
5. **the radius profile**: `data-gw-radius="tight|soft"` on `<html>`,
|
|
10
|
+
6. **the header style**: the flat bar, or `.gw-header--float`.
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
The last three are **named profiles**, not values: a brand picks one of a short list, once for the whole site, and
|
|
13
|
+
every component follows. Everything else stays the kit's: the neutrals, the type, the spacing, the shadows, the
|
|
14
|
+
widths. That is what makes the sites look like one family, and why a site built by someone with no design training
|
|
15
|
+
still looks careful. `npx gemboss-website check` reports anything outside this set.
|
|
16
|
+
|
|
17
|
+
Working examples: `demo/theme-example.html` (the accent only) and `demo/brand-profiles.html` (a GemPages-style brand:
|
|
18
|
+
the accent, all three profiles and the floating header).
|
|
19
|
+
|
|
20
|
+
## 1 to 3. Accent, wash, logo
|
|
11
21
|
|
|
12
22
|
Put these in the site's own CSS (`site.css`, linked **after** `website.css`), in **one `:root {}` block**, and nowhere
|
|
13
23
|
else.
|
|
14
24
|
|
|
15
25
|
| variable | what it is | GemBoss value |
|
|
16
26
|
|---|---|---|
|
|
17
|
-
| `--gw-accent-light` | the accent on light pages: links, focus ring, active item, label square, chart line | `#5661f5` |
|
|
18
|
-
| `--gw-accent-strong-light` | the accent
|
|
27
|
+
| `--gw-accent-light` | the accent on light pages: links, focus ring, active item, label square, chart line; the primary button's fill under `data-gw-action="accent"` | `#5661f5` |
|
|
28
|
+
| `--gw-accent-strong-light` | accent text (links, the active item), and the accent button's hover and pressed fill | `#3a44de` |
|
|
19
29
|
| `--gw-accent-soft-light` | the accent's tint: active chip, chosen segmented option, accent badge, icon hover | `#eef0fe` |
|
|
20
|
-
| `--gw-on-accent-light` |
|
|
30
|
+
| `--gw-on-accent-light` | words on a solid accent: the accent button's label | `#ffffff` |
|
|
21
31
|
| `--gw-accent-dark` | the accent in dark mode **and inside night bands** | `#7c84ff` |
|
|
22
|
-
| `--gw-accent-strong-dark` | its
|
|
32
|
+
| `--gw-accent-strong-dark` | its text and hover form | `#a3a9ff` |
|
|
23
33
|
| `--gw-accent-soft-dark` | its tint | `#1f2140` |
|
|
24
|
-
| `--gw-on-accent-dark` |
|
|
34
|
+
| `--gw-on-accent-dark` | words on it | `#0d0d12` |
|
|
25
35
|
| `--gw-wash-pink`, `--gw-wash-violet`, `--gw-wash-sky`, `--gw-wash-mint` | the secondary hues of the hero wash and illustrations | `#ff6f5e`, `#7c8af7`, `#43b0ff`, `#1fcf9f` |
|
|
26
36
|
|
|
27
37
|
Why the `-light` / `-dark` names and not `--gw-accent` itself: the kit sets `--gw-accent` from these in four places
|
|
@@ -33,45 +43,86 @@ The logo and the favicon are files, not variables: the `<img class="gw-brand__ma
|
|
|
33
43
|
`alt=""` because the name is written next to it) and the `<span class="gw-brand__name">` in the header and footer
|
|
34
44
|
partials, and `<link rel="icon">` in the page head.
|
|
35
45
|
|
|
46
|
+
## 4 to 6. The three profiles
|
|
47
|
+
|
|
48
|
+
Chosen once, in the page skeleton (the layout or partial every page shares). The first of each is the default and
|
|
49
|
+
needs no markup, so a GemBoss page, and every page written for 0.2.0, stays exactly as it was.
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<html lang="en" data-gw-action="accent" data-gw-radius="soft">
|
|
53
|
+
…
|
|
54
|
+
<header class="gw-header gw-header--float" data-gw-header> …the same header markup… </header>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| knob | profiles | what changes |
|
|
58
|
+
|---|---|---|
|
|
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 badges stay pills. |
|
|
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
|
+
|
|
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
|
|
64
|
+
colour is a family the brand already checked for contrast. "Site and product speak one language": GemBoss's dashboard
|
|
65
|
+
clicks in ink, so gemboss.ai does; GemPages' product uses blue primary buttons, so a GemPages site may too. Still no
|
|
66
|
+
raised or inset shadow on a button, no gradient, no wash.
|
|
67
|
+
|
|
68
|
+
Why the header uses the panel radius: it is the largest piece of chrome on the page, so it follows the profile with
|
|
69
|
+
the panels; in `tight` that is the 16px the header was designed with.
|
|
70
|
+
|
|
36
71
|
## What you may not change
|
|
37
72
|
|
|
38
73
|
- **Neutrals**: `--gw-bg`, `--gw-bg-soft`, `--gw-surface`, `--gw-ink`, `--gw-ink-2`, `--gw-muted`, `--gw-line`,
|
|
39
74
|
`--gw-line-strong`, and the night colours. No cream or grey page, no brand-coloured ink.
|
|
40
|
-
- **The action**: `--gw-action`, `--gw-action-hover`, `--gw-on-action`.
|
|
41
|
-
|
|
75
|
+
- **The action tokens**: `--gw-action`, `--gw-action-hover`, `--gw-on-action`. Choose the `accent` profile instead;
|
|
76
|
+
a colour set here is not checked and does not follow dark mode or night bands.
|
|
42
77
|
- **State**: `--gw-success`, `--gw-warning` and their tints. Green and amber mean state on every GemCommerce site.
|
|
43
|
-
- **Scales and shapes**: type sizes and weights, spacing,
|
|
44
|
-
font family. No
|
|
78
|
+
- **Scales and shapes**: type sizes and weights, spacing, the radius tokens (`--gw-radius-*`: choose a profile), shadows,
|
|
79
|
+
widths, the header height, the font family. No pill buttons, no bigger shadows, no other font.
|
|
45
80
|
- **Kit classes**: never restyle a `.gw-*` class. If the kit cannot do what your brand needs, that is a gap
|
|
46
81
|
(`AGENTS.md` section 9), not a theme.
|
|
47
82
|
|
|
48
|
-
|
|
49
|
-
|
|
83
|
+
### Still forbidden with every profile
|
|
84
|
+
|
|
85
|
+
- **Gradient or accent-coloured words in a heading.** A heading is ink, with a muted first sentence (`.gw-dim`).
|
|
86
|
+
- **Accent-coloured section bands.** A section is white, `--gw-bg-soft` or a night band; the accent only lights a
|
|
87
|
+
night band's top edge.
|
|
88
|
+
- **All-caps labels.** Labels are sentence case, muted, led by the 8px accent square.
|
|
89
|
+
|
|
90
|
+
These make a page louder without making it clearer, and `check` reports the last one (`uppercase-label`) and any
|
|
91
|
+
button filled from site CSS (`accent-button`).
|
|
92
|
+
|
|
93
|
+
`check` also reports: colours outside the tokens, a redefined kit token (`token-redefined`, including the radius and
|
|
94
|
+
action tokens), overrides of `.gw-*` classes, an unknown profile or one set on another element than `<html>`
|
|
95
|
+
(`profile-unknown`), `.gw-header--float` anywhere but the header itself (`header-float-markup`), and, on a page with
|
|
96
|
+
`data-gw-action="accent"`, accent button words below 4.5:1 (`action-contrast`).
|
|
50
97
|
|
|
51
98
|
## A full example
|
|
52
99
|
|
|
53
100
|
```css
|
|
54
|
-
/* site.css, linked after website.css. The whole
|
|
101
|
+
/* site.css, linked after website.css. The whole CSS of a GemPages-style brand. */
|
|
55
102
|
:root {
|
|
56
|
-
/*
|
|
57
|
-
--gw-accent-light: #
|
|
58
|
-
|
|
59
|
-
|
|
103
|
+
/* light pages. #3c67ff fills the accent button: white words on it are 4.58:1 */
|
|
104
|
+
--gw-accent-light: #3c67ff;
|
|
105
|
+
/* EXAMPLE, not confirmed by the GemPages team: accent text needs a darker step, because #3c67ff is 4.13:1 on
|
|
106
|
+
--gw-bg-soft. #2f52d6 is 6.36:1 on white and 5.73:1 on --gw-bg-soft, and the button's hover. */
|
|
107
|
+
--gw-accent-strong-light: #2f52d6;
|
|
108
|
+
--gw-accent-soft-light: #eaefff;
|
|
60
109
|
--gw-on-accent-light: #ffffff;
|
|
61
|
-
/*
|
|
62
|
-
--gw-accent-dark: #
|
|
63
|
-
--gw-accent-strong-dark: #
|
|
110
|
+
/* dark mode and night bands: #7c9bff is 7.37:1 on #0d0d12, and #0d0d12 words on it are 7.37:1 */
|
|
111
|
+
--gw-accent-dark: #7c9bff;
|
|
112
|
+
--gw-accent-strong-dark: #a9beff;
|
|
64
113
|
--gw-accent-soft-dark: #16213d;
|
|
65
114
|
--gw-on-accent-dark: #0d0d12;
|
|
66
115
|
/* wash: only if the brand has its own secondary hues; otherwise leave the kit's */
|
|
67
|
-
--gw-wash-violet: #7b9cff;
|
|
68
116
|
}
|
|
69
117
|
```
|
|
70
118
|
|
|
71
119
|
```html
|
|
120
|
+
<html lang="en" data-gw-action="accent" data-gw-radius="soft">
|
|
72
121
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
73
122
|
…
|
|
74
|
-
<
|
|
123
|
+
<header class="gw-header gw-header--float" data-gw-header>
|
|
124
|
+
<div class="gw-wrap gw-header__bar">
|
|
125
|
+
<a class="gw-brand" href="/"><img class="gw-brand__mark" src="/logo-mark.svg" alt="" width="26" height="26"><span class="gw-brand__name">GemPages</span></a>
|
|
75
126
|
```
|
|
76
127
|
|
|
77
128
|
## Check the contrast before you ship
|
|
@@ -87,10 +138,15 @@ So:
|
|
|
87
138
|
3. `--gw-accent-dark` and `--gw-accent-strong-dark` on `#0d0d12` (dark page) and on `#0c0e14` (night band): at
|
|
88
139
|
least 4.5:1.
|
|
89
140
|
4. `--gw-accent-strong-dark` on `--gw-accent-soft-dark`: at least 4.5:1.
|
|
141
|
+
5. **With `data-gw-action="accent"`**: `--gw-on-accent-light` on `--gw-accent-light` and on
|
|
142
|
+
`--gw-accent-strong-light`, and `--gw-on-accent-dark` on `--gw-accent-dark` and on `--gw-accent-strong-dark`: at
|
|
143
|
+
least 4.5:1. `check` computes these four and fails the site below it (write the colours as `#rrggbb`); the kit's
|
|
144
|
+
build does the same for its own colours.
|
|
90
145
|
|
|
91
146
|
Any WCAG contrast checker works (paste the two hex values). A bright brand colour (yellow, light green, cyan) almost
|
|
92
147
|
never passes on white: darken it for `-light` and keep the bright one for `-dark`. If you cannot get 4.5:1 without
|
|
93
148
|
changing the brand colour beyond recognition, keep the kit's accent and use the brand colour only in the logo and
|
|
94
|
-
illustrations.
|
|
149
|
+
illustrations, and keep the ink button.
|
|
95
150
|
|
|
96
|
-
Then open the site in light and dark (the toggle in the header), at 320px and at 1366px, and look at a night band
|
|
151
|
+
Then open the site in light and dark (the toggle in the header), at 320px and at 1366px, and look at a night band and
|
|
152
|
+
at the header while the page scrolls under it.
|