@gemboss/website-ui 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +67 -25
- package/CHANGELOG.md +60 -0
- package/README.md +21 -12
- package/bin/lib/check.mjs +129 -7
- package/bin/lib/contrast.mjs +67 -0
- package/css/base.css +3 -3
- package/css/components.css +93 -3
- package/css/header.css +37 -5
- package/css/layout.css +1 -1
- package/css/prose.css +120 -0
- package/css/tokens.css +21 -0
- package/demo/brand-profiles.html +199 -0
- package/demo/index.html +83 -9
- package/docs/components.md +136 -13
- package/docs/markup-contract.md +51 -7
- package/docs/philosophy.md +14 -9
- package/docs/theming.md +88 -31
- package/js/website.js +39 -2
- package/package.json +1 -1
- package/skill/SKILL.md +5 -2
- package/system.json +34 -8
- package/templates/header.html +3 -1
- package/templates/page.html +2 -0
- 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 +277 -13
- package/website.min.css +1 -1
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
|
|
19
|
-
| `--gw-accent-soft-light` | the accent's tint: active chip, accent badge, icon hover | `#eef0fe` |
|
|
20
|
-
| `--gw-on-accent-light` |
|
|
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` |
|
|
29
|
+
| `--gw-accent-soft-light` | the accent's tint: active chip, chosen segmented option, accent badge, icon hover | `#eef0fe` |
|
|
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
|
|
@@ -82,14 +133,20 @@ So:
|
|
|
82
133
|
|
|
83
134
|
1. **Accent text on white at least 4.5:1**: `--gw-accent-light` and `--gw-accent-strong-light` on `#ffffff`.
|
|
84
135
|
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
|
|
136
|
+
2. `--gw-accent-strong-light` on `--gw-accent-soft-light`: at least 4.5:1 (the accent badge, the active chip and the
|
|
137
|
+
chosen option of a segmented control).
|
|
86
138
|
3. `--gw-accent-dark` and `--gw-accent-strong-dark` on `#0d0d12` (dark page) and on `#0c0e14` (night band): at
|
|
87
139
|
least 4.5:1.
|
|
88
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.
|
|
89
145
|
|
|
90
146
|
Any WCAG contrast checker works (paste the two hex values). A bright brand colour (yellow, light green, cyan) almost
|
|
91
147
|
never passes on white: darken it for `-light` and keep the bright one for `-dark`. If you cannot get 4.5:1 without
|
|
92
148
|
changing the brand colour beyond recognition, keep the kit's accent and use the brand colour only in the logo and
|
|
93
|
-
illustrations.
|
|
149
|
+
illustrations, and keep the ink button.
|
|
94
150
|
|
|
95
|
-
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.
|
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.3.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.3.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
|
@@ -20,9 +20,12 @@ the kit and its rules are how the page ends up looking designed. Compose from th
|
|
|
20
20
|
- Changed page: keep its kit classes; change words, order and choice of sections.
|
|
21
21
|
- Site CSS only for what the kit cannot know (an illustration, a product mock-up), with the kit's tokens and
|
|
22
22
|
scales, classes in the site's own prefix. Never style a `.gw-*` class, never edit `node_modules`.
|
|
23
|
-
- Another brand:
|
|
23
|
+
- Another brand: choose only the accent family, wash hues, logo and favicon, and the three named profiles
|
|
24
|
+
(`data-gw-action` and `data-gw-radius` on `<html>`, `.gw-header--float`), never free values
|
|
25
|
+
(`KIT/docs/theming.md`).
|
|
24
26
|
4. **Words**: real facts only. A missing price, number, name or date becomes a visible `[PLACEHOLDER]` and a
|
|
25
|
-
question to the person.
|
|
27
|
+
question to the person. Made-up numbers in a drawn mock-up get `<p class="gw-window__caption">Illustrative
|
|
28
|
+
data.</p>` in its window. No em dash in visible text, no "Learn more" as a whole link.
|
|
26
29
|
5. **Check**: `npx gemboss-website check` and fix every error (`--fix-list` gives a to-do list). Then look at the
|
|
27
30
|
page at 320px and 1366px. Only then say it is done.
|
|
28
31
|
|
package/system.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "The GemBoss website system in one file. css/tokens.css, the docs and `gemboss-website check` all read these values: change a value here, never in one of them alone. Values measured on gemboss.ai and the GemBoss dashboard (DESIGN-LANGUAGE.md of gemboss-www, 15/10/2026).",
|
|
3
|
-
"version":
|
|
3
|
+
"version": 2,
|
|
4
4
|
"color": {
|
|
5
5
|
"light": {
|
|
6
6
|
"bg": "#ffffff",
|
|
@@ -58,18 +58,16 @@
|
|
|
58
58
|
"mint": "#1fcf9f"
|
|
59
59
|
},
|
|
60
60
|
"roles": {
|
|
61
|
-
"action": "The thing you click
|
|
62
|
-
"accent": "Links, focus rings, active state, a chart line, the 8px label square. Never a
|
|
61
|
+
"action": "The thing you click: ink, or the brand's accent when the site chose the accent action profile (profiles.action).",
|
|
62
|
+
"accent": "Links, focus rings, active state, a chart line, the 8px label square; the primary button only through the accent action profile. Never body text, never words in a heading, never a section band.",
|
|
63
63
|
"success/warning": "State only.",
|
|
64
64
|
"wash": "The hero wash and illustrations only."
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"radius": {
|
|
68
|
-
"scale": [0, 4, 6, 8, 12, 16, 20, 999],
|
|
69
|
-
"action": 8,
|
|
68
|
+
"scale": [0, 4, 6, 8, 12, 16, 20, 24, 999],
|
|
70
69
|
"chip": 999,
|
|
71
|
-
"
|
|
72
|
-
"panel": 16
|
|
70
|
+
"$comment": "Chips stay pills in every profile. The radii of actions, cards, panels and popovers come from the site's radius profile (profiles.radius): components read --gw-radius-action|card|panel|popover, never a number."
|
|
73
71
|
},
|
|
74
72
|
"shadow": {
|
|
75
73
|
"rest": "0 1px 2px rgba(31,30,29,.04), 0 2px 6px rgba(31,30,29,.06)",
|
|
@@ -79,6 +77,7 @@
|
|
|
79
77
|
},
|
|
80
78
|
"type": {
|
|
81
79
|
"family": "Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif",
|
|
80
|
+
"mono": "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
82
81
|
"weights": [400, 500, 600, 650, 700],
|
|
83
82
|
"scale": [11, 12, 13, 14, 16, 18, 20, 22, 24, 30],
|
|
84
83
|
"display_above": 30,
|
|
@@ -111,10 +110,37 @@
|
|
|
111
110
|
"button_height": 40,
|
|
112
111
|
"tap_target_min": 44
|
|
113
112
|
},
|
|
113
|
+
"profiles": {
|
|
114
|
+
"$comment": "A brand's three knobs (Chris 25/10/2026, issue #61): each one a named profile, chosen once per site, never free values. The first option is the default and needs no markup. A brand chooses these, its accent family, its wash hues and its logo, and nothing else (docs/theming.md); check reports an unknown name.",
|
|
115
|
+
"action": {
|
|
116
|
+
"$comment": "What fills the primary .gw-btn: data-gw-action on <html>. A mapping of roles, not a colour: accent fills the button with the brand's own accent family. The build (with the kit's colours) and check (with the brand's) require on-action to reach 4.5:1 on action and on action-hover, in light and in dark.",
|
|
117
|
+
"attribute": "data-gw-action",
|
|
118
|
+
"options": {
|
|
119
|
+
"ink": { "$comment": "GemBoss, as its dashboard: the thing you click is ink", "action": "action", "action-hover": "action-hover", "on-action": "on-action" },
|
|
120
|
+
"accent": { "$comment": "a brand whose product's primary buttons are its accent (GemPages)", "action": "accent", "action-hover": "accent-strong", "on-action": "on-accent" }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"radius": {
|
|
124
|
+
"$comment": "The shapes: data-gw-radius on <html>. popover is the mega menu, the one floating card under the bar.",
|
|
125
|
+
"attribute": "data-gw-radius",
|
|
126
|
+
"options": {
|
|
127
|
+
"tight": { "$comment": "GemBoss, as its dashboard", "action": 8, "card": 12, "panel": 16, "popover": 20 },
|
|
128
|
+
"soft": { "$comment": "GemPages (gempages.net measured 25/10/2026: buttons 12, cards 16 to 24, panels 24)", "action": 12, "card": 20, "panel": 24, "popover": 24 }
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"header": {
|
|
132
|
+
"$comment": "The header's shape: a class next to gw-header on the <header>. float is an inset card from 1080px (the panel radius of the site's profile, the surface at `surface` percent over a `blur` px blur, one hairline, the raised shadow, `gap` px from the top); below 1080px, and so for the phone menu, it is the flat bar. Solid under prefers-reduced-transparency and where the browser cannot blur.",
|
|
133
|
+
"options": {
|
|
134
|
+
"flat": { "$comment": "a flat 64px bar with one hairline", "class": "gw-header" },
|
|
135
|
+
"float": { "class": "gw-header--float", "gap": 16, "surface": 85, "blur": 10 }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
114
139
|
"motion": {
|
|
115
140
|
"properties": ["transform", "opacity"],
|
|
116
141
|
"ease": "cubic-bezier(.22,1,.36,1)",
|
|
117
142
|
"reveal_ms": 700,
|
|
118
|
-
"
|
|
143
|
+
"spin_ms": 700,
|
|
144
|
+
"$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
145
|
}
|
|
120
146
|
}
|
package/templates/header.html
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
Header: one per site, the same on every page. Keep it in one partial or component, never copied per page.
|
|
3
|
-
A flat 64px bar with one hairline.
|
|
3
|
+
A flat 64px bar with one hairline. A brand that chose the floating header adds one class to the <header>:
|
|
4
|
+
class="gw-header gw-header--float" (an inset card from 1080px, docs/theming.md); nothing else changes.
|
|
5
|
+
Below 1080px the nav and Sign in hide and the menu button opens #gw-mobile-menu,
|
|
4
6
|
which repeats the same links. website.js runs the mega menus, the mobile menu and the theme toggle.
|
|
5
7
|
Limits: at most five top-level items, one .gw-btn (the primary action), one .gw-btn--secondary. Each mega panel:
|
|
6
8
|
two or three columns of four or five links, at most one feature card. The announcement bar is optional: one sentence.
|
package/templates/page.html
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
then link the site's own CSS after the kit. Fill the header and footer once, in a shared partial.
|
|
6
6
|
One <h1> per page (in the hero), sections in the order the page needs, then delete every unused part.
|
|
7
7
|
Replace every [bracketed] placeholder. No lorem ipsum, no invented facts.
|
|
8
|
+
A brand other than GemBoss may add its profiles to <html>, once, here: data-gw-action="accent" (the primary button
|
|
9
|
+
takes the accent) and data-gw-radius="soft" (rounder corners). See docs/theming.md.
|
|
8
10
|
-->
|
|
9
11
|
<html lang="en">
|
|
10
12
|
<head>
|
|
@@ -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>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Prose: a page that is read rather than scanned (a blog post, a docs page, terms, privacy). Put the article's own
|
|
3
|
+
HTML inside .gw-prose as it comes from the CMS or the markdown renderer: no classes needed on its headings,
|
|
4
|
+
paragraphs, lists, quotes, code, tables or images. Add .gw-prose--sm (16px) for docs and legal pages.
|
|
5
|
+
The page's <h1> is the article title: in a .gw-hero--compact above, or as the first child of .gw-prose.
|
|
6
|
+
Limits: one article per page; headings in order (h2, then h3) and never used for size alone; every image with
|
|
7
|
+
alt, width and height; a wide table in the .gw-table wrapper (a bare table scrolls inside its own box too).
|
|
8
|
+
-->
|
|
9
|
+
<section class="gw-section">
|
|
10
|
+
<div class="gw-wrap gw-wrap--narrow">
|
|
11
|
+
<article class="gw-prose">
|
|
12
|
+
<h1>[Article title]</h1>
|
|
13
|
+
<p>[The first paragraph: what the reader will know or be able to do by the end.]</p>
|
|
14
|
+
<h2>[A section of the article]</h2>
|
|
15
|
+
<p>[Paragraphs of two to five sentences. Links go where they say: <a href="[link]">[the setup guide]</a>.]</p>
|
|
16
|
+
<ul>
|
|
17
|
+
<li>[A point, in one or two lines]</li>
|
|
18
|
+
<li>[A point]</li>
|
|
19
|
+
</ul>
|
|
20
|
+
<h3>[A step or a sub-topic]</h3>
|
|
21
|
+
<p>[Inline code for a command or a name the reader types: <code>[npx gemboss-website check]</code>.]</p>
|
|
22
|
+
<pre><code>[a block of code or a config the reader copies]</code></pre>
|
|
23
|
+
<blockquote><p>[A quotation, with its source in the text around it.]</p></blockquote>
|
|
24
|
+
<figure>
|
|
25
|
+
<img src="[/img/article-screen.webp]" alt="[What the picture shows]" width="[1200]" height="[750]" loading="lazy">
|
|
26
|
+
<figcaption>[What to notice in the picture, or where it comes from.]</figcaption>
|
|
27
|
+
</figure>
|
|
28
|
+
<div class="gw-table" role="region" aria-label="[What the table compares]" tabindex="0">
|
|
29
|
+
<table>
|
|
30
|
+
<thead><tr><th scope="col">[Column]</th><th scope="col">[Column]</th></tr></thead>
|
|
31
|
+
<tbody><tr><th scope="row">[Row]</th><td>[Value]</td></tr></tbody>
|
|
32
|
+
</table>
|
|
33
|
+
</div>
|
|
34
|
+
</article>
|
|
35
|
+
</div>
|
|
36
|
+
</section>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
Showcase: a night band with one large product screen and two or three points under it. Use for the one feature
|
|
3
3
|
the page is about. It counts as a night band: at most three per page, never two in a row.
|
|
4
|
-
Limits: centred head (label, h2, two lines), one window (a real screen or
|
|
5
|
-
two or three points (.gw-features in .gw-grid--3 or --2), at most one action pair.
|
|
4
|
+
Limits: centred head (label, h2, two lines), one window (a real screen, or a mock-up whose made-up numbers carry
|
|
5
|
+
the caption "Illustrative data."), two or three points (.gw-features in .gw-grid--3 or --2), at most one action pair.
|
|
6
6
|
-->
|
|
7
7
|
<section class="gw-section gw-band--night gw-showcase">
|
|
8
8
|
<div class="gw-wrap">
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
<div class="gw-window">
|
|
15
15
|
<div class="gw-window__bar">[Where this screen lives]</div>
|
|
16
16
|
<div class="gw-window__body"><img src="[/img/screen.webp]" alt="[What the screen shows]" width="[1600]" height="[1000]" loading="lazy"></div>
|
|
17
|
+
<p class="gw-window__caption">[Illustrative data. Or the source of the numbers. Delete when the screen shows no numbers.]</p>
|
|
17
18
|
</div>
|
|
18
19
|
<div class="gw-features gw-grid--3">
|
|
19
20
|
<div class="gw-feature"><h3>[Point]</h3><p>[One line.]</p></div>
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
Split: one idea told in words beside a figure (a real screen, or a drawn mock-up of the product).
|
|
3
3
|
.gw-split--reverse puts the figure on the left on wide screens; on phones the words always come first.
|
|
4
4
|
Alternate --reverse between consecutive splits. Limits: label, h2, at most two lines, a check list of three to
|
|
5
|
-
five items, one action (a link or one button). The figure is a .gw-window or a <figure> with alt text
|
|
5
|
+
five items, one action (a link or one button). The figure is a .gw-window or a <figure> with alt text; numbers in
|
|
6
|
+
a window that are not real get the caption "Illustrative data.", real ones their source.
|
|
6
7
|
-->
|
|
7
8
|
<section class="gw-section">
|
|
8
9
|
<div class="gw-wrap gw-split">
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
<div class="gw-window">
|
|
21
22
|
<div class="gw-window__bar">[Where this screen lives]</div>
|
|
22
23
|
<div class="gw-window__body"><img src="[/img/screen.webp]" alt="[What the screen shows]" width="[1200]" height="[900]" loading="lazy"></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>
|