@gemboss/website-ui 0.11.0 → 0.13.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 +14 -2
- package/CHANGELOG.md +77 -0
- package/README.md +6 -3
- package/bin/gemboss-website.mjs +13 -1
- package/bin/lib/build.mjs +101 -0
- package/bin/lib/check.mjs +50 -5
- package/bin/lib/init.mjs +93 -3
- package/css/article.css +1 -1
- package/css/base.css +23 -18
- package/css/components.css +29 -9
- package/css/forms.css +1 -1
- package/css/header.css +52 -5
- package/css/layout.css +7 -3
- package/css/ratings.css +0 -1
- package/css/sections.css +3 -4
- package/css/tokens.css +11 -0
- package/demo/header.html +1 -1
- package/docs/components.md +39 -14
- package/docs/markup-contract.md +10 -6
- package/docs/theming.md +39 -12
- package/js/website.js +33 -9
- package/js/website.mjs +37 -10
- package/package.json +1 -1
- package/system.json +28 -4
- package/templates/header.html +3 -2
- package/templates/page.html +2 -2
- package/templates/sections/form.html +2 -0
- package/templates/sections/hero.html +2 -1
- package/templates/sections/split.html +1 -1
- package/templates/site/app/[[...path]]/route.js +23 -0
- package/templates/site/assets/kit-gaps.css +3 -0
- package/templates/site/assets/site.css +16 -0
- package/templates/site/gitignore +4 -0
- package/templates/site/lib/pages.mjs +41 -0
- package/templates/site/next.config.mjs +15 -0
- package/templates/site/stage-public.mjs +49 -0
- package/templates/site/vercel.json +5 -0
- package/website.css +128 -43
- package/website.min.css +1 -1
package/css/components.css
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
/* actions are rectangles: 40px, the action radius of the site's profile (8, 12 in soft), 14px/600, sentence case.
|
|
4
4
|
The fill is the action colour: ink, or the brand's accent under data-gw-action="accent" (tokens.css). Never a
|
|
5
|
-
gradient
|
|
5
|
+
gradient or a wash hue. On hover (0.12.0, as gemboss.ai) the filled button rises 1px onto the rest shadow; the framed
|
|
6
|
+
and frameless ones only change their frame or tint. Phones: see "touch" below. */
|
|
6
7
|
.gw-btn {
|
|
7
8
|
position: relative;
|
|
8
9
|
display: inline-flex;
|
|
9
10
|
align-items: center;
|
|
10
|
-
justify-content: center;
|
|
11
11
|
gap: 8px;
|
|
12
12
|
max-width: 100%;
|
|
13
13
|
height: var(--gw-button-height);
|
|
@@ -16,26 +16,34 @@
|
|
|
16
16
|
border-radius: var(--gw-radius-action);
|
|
17
17
|
background: var(--gw-action);
|
|
18
18
|
color: var(--gw-on-action);
|
|
19
|
+
font-family: var(--gw-font-action);
|
|
19
20
|
font-size: 14px;
|
|
20
21
|
font-weight: 600;
|
|
21
|
-
line-height: 1.4;
|
|
22
22
|
letter-spacing: 0;
|
|
23
23
|
text-align: center;
|
|
24
24
|
white-space: nowrap;
|
|
25
25
|
cursor: pointer;
|
|
26
|
+
transition: background 0.15s ease, border-color 0.15s ease;
|
|
26
27
|
}
|
|
27
|
-
.gw-btn:hover {
|
|
28
|
+
.gw-btn:hover { transform: translateY(-1px); box-shadow: var(--gw-shadow); }
|
|
28
29
|
.gw-btn svg { flex: none; width: 16px; height: 16px; }
|
|
29
30
|
.gw-btn--secondary { background: transparent; border-color: var(--gw-line-strong); color: var(--gw-ink); }
|
|
30
31
|
.gw-btn--secondary:hover { background: transparent; border-color: var(--gw-ink); }
|
|
31
32
|
.gw-btn--ghost { background: transparent; color: var(--gw-ink); }
|
|
32
33
|
.gw-btn--ghost:hover { background: var(--gw-bg-soft); }
|
|
34
|
+
.gw-btn:is(.gw-btn--secondary, .gw-btn--ghost):hover { transform: none; box-shadow: none; }
|
|
35
|
+
/* an arrow after the label (→), nudged 3px on hover */
|
|
36
|
+
.gw-btn__arrow { letter-spacing: 0; transition: transform 0.18s ease; }
|
|
37
|
+
.gw-btn:hover .gw-btn__arrow { transform: translateX(3px); }
|
|
38
|
+
|
|
33
39
|
.gw-btn--on-night { background: var(--gw-on-night); color: var(--gw-night); }
|
|
34
40
|
.gw-btn--on-night:hover { background: var(--gw-on-night); opacity: 0.88; }
|
|
35
41
|
.gw-btn--sm { height: 36px; padding: 0 16px; }
|
|
36
42
|
.gw-btn--sm::after { content: ""; position: absolute; top: -6px; right: 0; bottom: -6px; left: 0; }
|
|
37
43
|
.gw-btn--lg { height: 48px; padding: 0 24px; font-size: 16px; }
|
|
38
|
-
.
|
|
44
|
+
/* a button wider than its label keeps the label at its start (as gemboss.ai), unless it is made wide on purpose: full
|
|
45
|
+
width (--block), a phone's row of actions, an inline form */
|
|
46
|
+
.gw-btn--block { display: flex; width: 100%; justify-content: center; }
|
|
39
47
|
.gw-btn:disabled, .gw-btn[aria-disabled="true"] { opacity: 0.48; cursor: not-allowed; pointer-events: none; }
|
|
40
48
|
/* pressed: the ink one step darker (the hover ink, as the dashboard's primary), nothing moves; the frameless variants
|
|
41
49
|
take one more step of tint */
|
|
@@ -258,6 +266,13 @@
|
|
|
258
266
|
}
|
|
259
267
|
.gw-window__body:has(> img:only-child) + .gw-window__caption { padding-top: 12px; }
|
|
260
268
|
|
|
269
|
+
/* figure (0.13.0, Gemians): a photo and its caption where a section shows one outside the prose (beside words in a
|
|
270
|
+
split, under a hero's actions): the picture at the card radius, the caption 14px, muted, 12px under it, as a figure
|
|
271
|
+
in .gw-prose. A screen of the product goes in .gw-window instead. */
|
|
272
|
+
.gw-figure > :is(img, video, picture) { display: block; width: 100%; height: auto; border-radius: var(--gw-radius-card); }
|
|
273
|
+
.gw-figure > picture > img { display: block; width: 100%; height: auto; border-radius: inherit; }
|
|
274
|
+
.gw-figure > figcaption { margin-top: 12px; color: var(--gw-muted); font-size: 14px; line-height: 1.5; }
|
|
275
|
+
|
|
261
276
|
/* icon: 20px inline stroke SVG (1.7 to 2px stroke, currentColor) */
|
|
262
277
|
.gw-icon { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 20px; height: 20px; }
|
|
263
278
|
.gw-icon svg { width: 20px; height: 20px; }
|
|
@@ -334,16 +349,21 @@
|
|
|
334
349
|
.gw-faq__item[open] > summary::after { transform: rotate(45deg); }
|
|
335
350
|
.gw-faq__item > :not(summary) { margin: 0 0 20px; max-width: 68ch; color: var(--gw-muted); font-size: 16px; }
|
|
336
351
|
|
|
337
|
-
/* touch: every control is at least 44px tall,
|
|
338
|
-
|
|
352
|
+
/* touch: every control is at least 44px tall. A button up to 768px (0.12.0, as gemboss.ai: by the width, not the
|
|
353
|
+
pointer), its label wrapping (and tighter) from 480px down in the page's <main>, so a long one never pushes the page
|
|
354
|
+
sideways (a header or an overlay outside <main> keeps its one line) */
|
|
355
|
+
@media (max-width: 768px) {
|
|
339
356
|
.gw-btn, .gw-btn--sm, .gw-btn--lg {
|
|
340
357
|
height: auto;
|
|
341
358
|
min-height: var(--gw-tap-min);
|
|
342
359
|
padding-block: 8px;
|
|
343
|
-
white-space: normal;
|
|
344
|
-
text-wrap: balance;
|
|
345
360
|
}
|
|
346
361
|
.gw-btn--lg { min-height: 48px; }
|
|
362
|
+
}
|
|
363
|
+
@media (max-width: 480px) {
|
|
364
|
+
:where(main) .gw-btn { white-space: normal; line-height: 1.35; }
|
|
365
|
+
}
|
|
366
|
+
@media (max-width: 768px), (pointer: coarse) {
|
|
347
367
|
:is(a, button, label).gw-chip { min-height: var(--gw-tap-min); }
|
|
348
368
|
.gw-segmented > button { height: auto; min-height: var(--gw-tap-min); padding-block: 8px; white-space: normal; text-wrap: balance; }
|
|
349
369
|
}
|
package/css/forms.css
CHANGED
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
room; below 480px, and in a box too narrow for both, the field and the button each take a full line. */
|
|
135
135
|
.gw-form-inline { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
136
136
|
.gw-form-inline > :not(.gw-btn) { flex: 999 1 160px; min-width: 0; }
|
|
137
|
-
.gw-form-inline > .gw-btn { flex: 1 0 auto; }
|
|
137
|
+
.gw-form-inline > .gw-btn { flex: 1 0 auto; justify-content: center; }
|
|
138
138
|
@media (max-width: 479px) {
|
|
139
139
|
.gw-form-inline > :not(.gw-btn) { flex-basis: 100%; }
|
|
140
140
|
}
|
package/css/header.css
CHANGED
|
@@ -1,16 +1,50 @@
|
|
|
1
1
|
/* header: announcement, a flat 64px bar with one hairline, mega menu, mobile menu up to 1080px.
|
|
2
2
|
Not a floating pill: no radius, no blur, no shadow on the bar. Nothing tucks under it. A brand may choose the one
|
|
3
3
|
other shape, .gw-header--float (an inset card from 1081px, at the end of this file). */
|
|
4
|
+
/* the announcement bar (0.12.0, gemboss.ai's): one sentence on its own ground over the header, the same in both themes
|
|
5
|
+
(system.json color.announce). A small pill may lead it (.gw-announce__tag); its link is light, underlined by a hairline
|
|
6
|
+
that brightens on hover. From 520px down, 12px words and a slimmer bar. */
|
|
4
7
|
.gw-announce {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
position: relative;
|
|
9
|
+
z-index: 60;
|
|
10
|
+
padding: 12px 16px;
|
|
11
|
+
border-bottom: 1px solid var(--gw-announce-line);
|
|
12
|
+
background: var(--gw-announce-bg);
|
|
13
|
+
color: var(--gw-announce-ink);
|
|
9
14
|
font-size: 13px;
|
|
15
|
+
font-weight: 500;
|
|
10
16
|
line-height: 1.5;
|
|
17
|
+
letter-spacing: 0.005em;
|
|
11
18
|
text-align: center;
|
|
12
19
|
}
|
|
13
|
-
.
|
|
20
|
+
/* 2.5px above and below, as gemboss.ai draws the pill (21.5px tall): the one half pixel in the kit */
|
|
21
|
+
.gw-announce__tag {
|
|
22
|
+
display: inline-block;
|
|
23
|
+
margin-right: 12px;
|
|
24
|
+
padding: 2.5px 8px;
|
|
25
|
+
border-radius: var(--gw-radius-chip);
|
|
26
|
+
background: var(--gw-announce-tag);
|
|
27
|
+
color: var(--gw-announce-tag-ink);
|
|
28
|
+
font-size: 11px;
|
|
29
|
+
font-weight: 700;
|
|
30
|
+
letter-spacing: 0.09em;
|
|
31
|
+
vertical-align: 1px;
|
|
32
|
+
}
|
|
33
|
+
.gw-announce a {
|
|
34
|
+
margin-left: 8px;
|
|
35
|
+
padding-bottom: 2px;
|
|
36
|
+
border-bottom: 1px solid var(--gw-announce-link-line);
|
|
37
|
+
color: var(--gw-announce-link);
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
text-decoration: none;
|
|
40
|
+
white-space: nowrap;
|
|
41
|
+
transition: border-color 0.18s;
|
|
42
|
+
}
|
|
43
|
+
.gw-announce a:hover { border-bottom-color: var(--gw-announce-link); }
|
|
44
|
+
@media (max-width: 520px) {
|
|
45
|
+
.gw-announce { padding: 8px 12px; font-size: 12px; }
|
|
46
|
+
.gw-announce__tag { margin-right: 6px; }
|
|
47
|
+
}
|
|
14
48
|
|
|
15
49
|
.gw-header {
|
|
16
50
|
position: sticky;
|
|
@@ -669,3 +703,16 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
|
|
|
669
703
|
:root:not([data-theme="light"]) .gw-theme-toggle__sun { display: block; }
|
|
670
704
|
:root:not([data-theme="light"]) .gw-theme-toggle__moon { display: none; }
|
|
671
705
|
}
|
|
706
|
+
|
|
707
|
+
/* ---- 0.13.0: the brand's logo as one drawing (Gemians): the mark and the wordmark in one file, in place of the mark and
|
|
708
|
+
the name, 26px tall, its width the file's. Its alt is the brand's name. A logo drawn for dark grounds only (a white
|
|
709
|
+
wordmark) comes as a second file, .gw-brand__logo--dark, right after the first: it shows in dark (the system's or the
|
|
710
|
+
toggle's) and the first one hides, the toggle icons' rule. One file that reads on both: no --dark. */
|
|
711
|
+
.gw-brand__logo { display: block; flex: none; width: auto; height: 26px; }
|
|
712
|
+
.gw-brand__logo--dark { display: none; }
|
|
713
|
+
:root[data-theme="dark"] .gw-brand__logo--dark { display: block; }
|
|
714
|
+
:root[data-theme="dark"] .gw-brand__logo:has(+ .gw-brand__logo--dark) { display: none; }
|
|
715
|
+
@media (prefers-color-scheme: dark) {
|
|
716
|
+
:root:not([data-theme="light"]) .gw-brand__logo--dark { display: block; }
|
|
717
|
+
:root:not([data-theme="light"]) .gw-brand__logo:has(+ .gw-brand__logo--dark) { display: none; }
|
|
718
|
+
}
|
package/css/layout.css
CHANGED
|
@@ -12,15 +12,19 @@
|
|
|
12
12
|
@media (min-width: 768px) { .gw-section--tight { padding-block: 64px; } }
|
|
13
13
|
|
|
14
14
|
/* label + h2 + one or two lines; the vertical flow of every text block in the kit */
|
|
15
|
-
.gw-section__head { max-width:
|
|
15
|
+
.gw-section__head { max-width: 760px; }
|
|
16
16
|
.gw-section__head--center { margin-inline: auto; text-align: center; }
|
|
17
17
|
.gw-section__head--center .gw-lead { margin-inline: auto; }
|
|
18
|
-
.
|
|
19
|
-
|
|
18
|
+
/* what follows a head starts 40px under it, 48px from 768px (0.12.0): a top margin on the next thing, at no
|
|
19
|
+
specificity, so a block that brings its own (a FAQ's 40px, say) keeps it */
|
|
20
|
+
:where(.gw-section__head) + * { margin-top: 40px; }
|
|
21
|
+
@media (min-width: 768px) { :where(.gw-section__head) + * { margin-top: 48px; } }
|
|
20
22
|
|
|
21
23
|
:where(.gw-section__head, .gw-hero > .gw-wrap, .gw-split > :not(.gw-window), .gw-cta, .gw-card, .gw-feature, .gw-plan) > * + * { margin-top: 16px; }
|
|
22
24
|
:where(.gw-section__head, .gw-hero > .gw-wrap, .gw-split > :not(.gw-window), .gw-cta, .gw-card, .gw-plan) > :is(.gw-actions, .gw-segmented) { margin-top: 32px; }
|
|
23
25
|
:where(.gw-section__head, .gw-hero > .gw-wrap, .gw-split > :not(.gw-window), .gw-cta, .gw-card, .gw-plan) > .gw-list--check { margin-top: 24px; }
|
|
26
|
+
/* a lead under a heading: 20px (0.12.0, as gemboss.ai); in a hero it keeps the 16px of the flow */
|
|
27
|
+
:where(.gw-section__head, .gw-split > :not(.gw-window), .gw-cta) > :where(* + .gw-lead) { margin-top: 20px; }
|
|
24
28
|
|
|
25
29
|
/* a row of actions: one .gw-btn, then a secondary or a link */
|
|
26
30
|
.gw-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
|
package/css/ratings.css
CHANGED
package/css/sections.css
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
radial-gradient(420px 320px at 52% 118%, color-mix(in srgb, var(--gw-wash-mint) 10%, transparent), transparent 62%);
|
|
14
14
|
}
|
|
15
15
|
.gw-hero h1 .gw-dim { display: block; }
|
|
16
|
-
.gw-hero > .gw-wrap > .gw-window { margin-top: 48px; }
|
|
16
|
+
.gw-hero > .gw-wrap > :is(.gw-window, .gw-figure) { margin-top: 48px; }
|
|
17
17
|
.gw-hero--center { text-align: center; }
|
|
18
18
|
.gw-hero--center > .gw-wrap > * { margin-inline: auto; }
|
|
19
19
|
.gw-hero--center .gw-actions { justify-content: center; }
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
.gw-hero > .gw-wrap > .gw-window { margin-top: 64px; }
|
|
27
27
|
}
|
|
28
28
|
@media (max-width: 479px) {
|
|
29
|
-
:is(.gw-hero, .gw-cta) .gw-actions > .gw-btn { flex: 1 1 100%; }
|
|
29
|
+
:is(.gw-hero, .gw-cta) .gw-actions > .gw-btn { flex: 1 1 100%; justify-content: center; }
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/* logos: a proof strip of real logos or wordmarks */
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
.gw-logos img { width: auto; height: 28px; }
|
|
47
47
|
|
|
48
48
|
/* stats: three or four facts, each with its source */
|
|
49
|
-
.gw-stats { display: grid; grid-template-columns: minmax(0, 1fr); gap: 24px;
|
|
49
|
+
.gw-stats { display: grid; grid-template-columns: minmax(0, 1fr); gap: 24px; padding: 0; list-style: none; }
|
|
50
50
|
.gw-stats__value { font-size: var(--gw-display-h2); font-weight: 600; letter-spacing: -0.03em; line-height: 1.1; font-variant-numeric: tabular-nums; }
|
|
51
51
|
.gw-stats__label { margin-top: 8px; max-width: 28ch; color: var(--gw-muted); font-size: 14px; }
|
|
52
52
|
.gw-stats__source { margin-top: 8px; color: var(--gw-muted); font-size: 12px; }
|
|
@@ -79,7 +79,6 @@
|
|
|
79
79
|
display: grid;
|
|
80
80
|
grid-template-columns: minmax(0, 1fr);
|
|
81
81
|
gap: 32px 24px;
|
|
82
|
-
margin: 0;
|
|
83
82
|
padding: 0;
|
|
84
83
|
list-style: none;
|
|
85
84
|
counter-reset: gw-step;
|
package/css/tokens.css
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
color-scheme: light;
|
|
4
4
|
--gw-font: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
5
5
|
--gw-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
6
|
+
--gw-font-action: Inter;
|
|
6
7
|
--gw-text-11: 11px;
|
|
7
8
|
--gw-text-12: 12px;
|
|
8
9
|
--gw-text-13: 13px;
|
|
@@ -18,6 +19,8 @@
|
|
|
18
19
|
--gw-display-wordmark: clamp(40px, 9vw, 128px);
|
|
19
20
|
--gw-display-weight: 600;
|
|
20
21
|
--gw-display-tracking: -0.03em;
|
|
22
|
+
--gw-display-h1-leading: 1.1;
|
|
23
|
+
--gw-display-h2-leading: 1.06;
|
|
21
24
|
--gw-heading-weight: 650;
|
|
22
25
|
--gw-heading-tracking: -0.022em;
|
|
23
26
|
--gw-heading-leading: 1.08;
|
|
@@ -70,6 +73,7 @@
|
|
|
70
73
|
--gw-tap-min: 44px;
|
|
71
74
|
--gw-ease: cubic-bezier(.22,1,.36,1);
|
|
72
75
|
--gw-reveal-ms: 700ms;
|
|
76
|
+
--gw-reveal-distance: 20px;
|
|
73
77
|
--gw-spin-ms: 700ms;
|
|
74
78
|
--gw-spot-radius: 260px;
|
|
75
79
|
--gw-spot-radius-action: 120px;
|
|
@@ -88,6 +92,13 @@
|
|
|
88
92
|
--gw-stage-bottom: #0d0f2e;
|
|
89
93
|
--gw-stage-dot: rgba(255, 255, 255, 0.1);
|
|
90
94
|
--gw-stage-ink: #ffffff;
|
|
95
|
+
--gw-announce-bg: #171a38;
|
|
96
|
+
--gw-announce-ink: #b9beda;
|
|
97
|
+
--gw-announce-line: rgba(255, 255, 255, 0.06);
|
|
98
|
+
--gw-announce-tag: rgba(122, 132, 255, 0.2);
|
|
99
|
+
--gw-announce-tag-ink: #a7aeff;
|
|
100
|
+
--gw-announce-link: #ffffff;
|
|
101
|
+
--gw-announce-link-line: rgba(255, 255, 255, 0.28);
|
|
91
102
|
/* accent primitives: the only colours a brand overrides (docs/theming.md) */
|
|
92
103
|
--gw-accent-light: #5661f5;
|
|
93
104
|
--gw-accent-strong-light: #3a44de;
|
package/demo/header.html
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
<body class="gw-page">
|
|
45
45
|
<a class="gw-skip" href="#main">Skip to content</a>
|
|
46
46
|
|
|
47
|
-
<div class="gw-announce" role="region" aria-label="Announcement">The GemBoss community is live. <a href="https://community.gemboss.ai">Join founders building brand-true stores
|
|
47
|
+
<div class="gw-announce" role="region" aria-label="Announcement"><span class="gw-announce__tag">New</span> The GemBoss community is live. <a href="https://community.gemboss.ai">Join founders building brand-true stores →</a></div>
|
|
48
48
|
<header class="gw-header" data-gw-header>
|
|
49
49
|
<div class="gw-header__bar">
|
|
50
50
|
<a class="gw-brand" href="https://gemboss.ai/" data-gw-show="brand:gemboss"><svg class="gw-brand__mark" viewBox="0 0 100 116" aria-hidden="true"><polygon points="26,14 74,14 66,50 34,50" style="fill:var(--gw-accent)"/><path d="M26,14 74,14 96,50 50,110 4,50Z" fill="none" stroke="currentColor" stroke-width="9" stroke-linejoin="round"/><path d="M4,50H96M26,14 34,50M74,14 66,50M34,50 50,110M66,50 50,110" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round"/></svg><span class="gw-brand__name">GemBoss</span></a><a class="gw-brand" href="header.html#gempages" data-gw-show="brand:gempages" hidden><svg class="gw-brand__mark" viewBox="0 0 26 26" aria-hidden="true"><rect width="26" height="26" rx="7" style="fill:var(--gw-accent)"/><path d="M8 9h10M8 13h10M8 17h6" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round"/></svg><span class="gw-brand__name">Example brand</span></a>
|
package/docs/components.md
CHANGED
|
@@ -13,15 +13,16 @@ default `tight` profile, and `soft` makes them 12 / 20 / 24; "ink fill" is the d
|
|
|
13
13
|
`data-gw-action="accent"` makes it the accent.
|
|
14
14
|
|
|
15
15
|
Spacing between the children of text blocks is automatic: inside `.gw-section__head`, the hero, a split column,
|
|
16
|
-
`.gw-cta`, `.gw-card`, `.gw-feature` and `.gw-plan`, each child is 16px below the previous one, a `.gw-
|
|
17
|
-
32px, a `.gw-list--check` 24px. Do not add margins to fix rhythm;
|
|
16
|
+
`.gw-cta`, `.gw-card`, `.gw-feature` and `.gw-plan`, each child is 16px below the previous one, a `.gw-lead` under a
|
|
17
|
+
heading 20px (16px in the hero), a `.gw-actions` row 32px, a `.gw-list--check` 24px. Do not add margins to fix rhythm;
|
|
18
|
+
reorder or remove content instead.
|
|
18
19
|
|
|
19
20
|
## Page
|
|
20
21
|
|
|
21
22
|
| class | for |
|
|
22
23
|
|---|---|
|
|
23
24
|
| `.gw-page` | on `<body>`: the footer stays at the bottom of a short page |
|
|
24
|
-
| `.gw-skip` | the first element in `<body>`: "Skip to content", visible only on keyboard focus |
|
|
25
|
+
| `.gw-skip` | the first element in `<body>`: "Skip to content", visible only on keyboard focus (an accent tab from the top left corner) |
|
|
25
26
|
|
|
26
27
|
```html
|
|
27
28
|
<body class="gw-page">
|
|
@@ -60,11 +61,11 @@ copies of the script and expect both to work (the first owns the page).
|
|
|
60
61
|
|
|
61
62
|
| class | for |
|
|
62
63
|
|---|---|
|
|
63
|
-
| `h1`, `h2` (and `.gw-h1`, `.gw-h2`) | display sizes: h1 `clamp(40px, 4.6vw, 68px)
|
|
64
|
-
| `h3`, `h4` (and `.gw-h3`, `.gw-h4`) |
|
|
64
|
+
| `h1`, `h2` (and `.gw-h1`, `.gw-h2`) | display sizes: h1 `clamp(40px, 4.6vw, 68px)` at line height 1.1, h2 `clamp(32px, 3.4vw, 52px)` at 1.06, weight 600, tracking −0.03em |
|
|
65
|
+
| `h3`, `h4` (and `.gw-h3`, `.gw-h4`) | 20px / 600 (tracking −0.02em, line height 1.2, every h3 on gemboss.ai) and 20px / 600 |
|
|
65
66
|
| `.gw-dim` | the muted first sentence of the house headline shape |
|
|
66
|
-
| `.gw-label` | the label above a heading: 13px, muted, led by the 8px
|
|
67
|
-
| `.gw-lead` | the one or two lines under a heading (18px, 20px in the hero, muted, max
|
|
67
|
+
| `.gw-label` | the label above a heading: 13px, muted, led by the 8px square in ink; `--gw-label-mark` paints the square (a product's own colour): `style="--gw-label-mark: var(--gw-accent)"` on the label or around it |
|
|
68
|
+
| `.gw-lead` | the one or two lines under a heading (18px, 20px in the hero, muted, max 58ch) |
|
|
68
69
|
| `.gw-note` | a short reassurance under actions (14px, muted) |
|
|
69
70
|
| `.gw-link` | an inline link in the accent, underlined |
|
|
70
71
|
| `.gw-link--arrow` | a standalone "go there" link with a → that moves 2px on hover |
|
|
@@ -92,7 +93,7 @@ Don't: uppercase or letter-spaced labels; a label longer than a few words; the a
|
|
|
92
93
|
| `.gw-section` | a section's vertical rhythm (`--gw-space-section`) |
|
|
93
94
|
| `.gw-section--soft` | on `--gw-bg-soft`, to separate a section without a line |
|
|
94
95
|
| `.gw-section--tight` | less padding (40px, 64px from 768px), for proof strips and follow-on tables |
|
|
95
|
-
| `.gw-section__head` | label + h2 + lead, max
|
|
96
|
+
| `.gw-section__head` | label + h2 + lead, max 760px; the content after it starts 40px below (48px from 768px), unless it brings its own top margin |
|
|
96
97
|
| `.gw-section__head--center` | the same, centred |
|
|
97
98
|
| `.gw-grid`, `.gw-grid--2`, `.gw-grid--3`, `.gw-grid--4` | equal columns: one on phones; 2 and 3 from 768px; 4 is 2 from 480px and 4 from 1080px |
|
|
98
99
|
| `.gw-actions` | a row of actions that wraps; on phones the hero and CTA buttons go full width |
|
|
@@ -118,8 +119,11 @@ Don't: more than three night bands on a page (the closing CTA does not count), o
|
|
|
118
119
|
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
120
|
(no frame), `--on-night` (light fill for a dark surface that is not a `.gw-band--night`), `--danger` (0.6.0: the
|
|
120
121
|
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
|
|
122
|
-
|
|
122
|
+
my data), `--sm` (36px, with a 44px hit area), `--lg` (48px, 16px type), `--block` (full width). On hover (0.12.0, as
|
|
123
|
+
gemboss.ai) the filled button rises 1px onto the rest shadow, a framed one darkens its frame; an arrow after the label
|
|
124
|
+
(`<span class="gw-btn__arrow">→</span>`) moves 3px. Up to 768px every button is at least 44px tall; up to 480px a label
|
|
125
|
+
in `<main>` may wrap. The face is `--gw-font-action` (the brand face alone, `system.json` `type.button.family`). Inside a
|
|
126
|
+
night band the default button turns light by itself.
|
|
123
127
|
|
|
124
128
|
```html
|
|
125
129
|
<div class="gw-actions">
|
|
@@ -270,6 +274,24 @@ the mock-up shows a demo brand ("Vital Glow is a GemBoss demo brand."). Under re
|
|
|
270
274
|
Don't: put interactive controls inside a mock-up; show made-up numbers without the caption; write the caption in
|
|
271
275
|
other words ("Sample", "Demo numbers") or leave it in English on a translated page.
|
|
272
276
|
|
|
277
|
+
### Figure `.gw-figure`
|
|
278
|
+
|
|
279
|
+
0.13.0 (Gemians): a photo and its caption outside the prose, where a section shows one: beside the words of a split,
|
|
280
|
+
under a hero's actions (48px below, as a window). The picture takes the card radius; the `<figcaption>` is 14px,
|
|
281
|
+
muted, 12px under it, the look of a figure inside `.gw-prose`. A screen of the product goes in `.gw-window`; a photo
|
|
282
|
+
of people, a place or a product goes here.
|
|
283
|
+
|
|
284
|
+
```html
|
|
285
|
+
<figure class="gw-figure">
|
|
286
|
+
<img src="/img/team-2025.webp" alt="The team together in the office, 2025" width="1000" height="471" loading="lazy">
|
|
287
|
+
<figcaption>The GemCommerce team, 2025.</figcaption>
|
|
288
|
+
</figure>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Don't: a photo of people without their permission, or of another brand without its; a caption that repeats the alt
|
|
292
|
+
word for word (the alt says what is in the picture, the caption says what it is); a frame, a shadow or a radius of
|
|
293
|
+
your own.
|
|
294
|
+
|
|
273
295
|
### Icon `.gw-icon`
|
|
274
296
|
|
|
275
297
|
A 20px box for an inline stroke SVG (1.7–2px stroke, `currentColor`, `aria-hidden="true"`). Inside `.gw-feature` it
|
|
@@ -332,11 +354,12 @@ level. A brand changes what it fills in and the tokens (colours, the Stage, the
|
|
|
332
354
|
|
|
333
355
|
| class | for |
|
|
334
356
|
|---|---|
|
|
335
|
-
| `.gw-announce` | optional one-sentence bar above the header, with one link |
|
|
357
|
+
| `.gw-announce` | optional one-sentence bar above the header, with one link, on its own ground in both themes (`color.announce`); `.gw-announce__tag` a small pill before the sentence ("New") |
|
|
336
358
|
| `.gw-header` (`data-gw-header`) | the sticky, flat 64px bar with one hairline; `.is-scrolled` darkens the hairline |
|
|
337
359
|
| `.gw-header--float` | with `.gw-header`, a brand's header profile: from 1081px the same bar as an inset card (panel radius, the surface at 85% over a blur, one hairline, the raised shadow, 16px from the top); the flat bar up to 1080px; solid under `prefers-reduced-transparency` |
|
|
338
360
|
| `.gw-header__bar` | the row inside: the page's wrap + 12px (1132px on GemBoss), 24px inside (6px up to 640px), 32px between its parts |
|
|
339
361
|
| `.gw-brand`, `__mark`, `__name` | logo (34px, its corners clipped at 8px) and name, linking home; a 44px row up to 768px |
|
|
362
|
+
| `.gw-brand__logo`, `--dark` | 0.13.0: a brand's logo drawn as one file (mark and wordmark), 26px tall, in place of `__mark` and `__name`, `alt` the brand's name; `--dark` a second file shown in dark only, right after the first (docs/theming.md) |
|
|
340
363
|
| `.gw-nav`, `__list`, `__item`, `__trigger`, `__chev`, `__link` | the desktop nav (from 1081px), 14px weight 500, 0.005em tighter; `__trigger` is a section's link that opens its mega panel (its 12px `__chev` turns while open), `__link` a plain link; the current page (`aria-current="page"`) or section (`aria-current` on a trigger) in the accent's strong ink, weight 600 |
|
|
341
364
|
| `.gw-mega` (`--gw-mega-w`, `--gw-mega-pv`, `--gw-mega-cols`), `__body`, `__grid`, `__col`, `__heading`, `__link`, `__aside`, `__card`, `__art`, `__card-text`, `__go`, `__foot`, `__foot-start`, `__foot-end`, `__cta`, `__new` | 0.11.0, gemboss.ai's mega menu: a panel on one surface that morphs between panels (the popover radius, raised shadow), a caret at the open name, a pill under the hovered name, a highlight under the hovered row; columns of rows (a name and one line), the pictures beside them (`__aside`: the panel's own `__card`, a row's preview from its `<template>`), each picture on the Stage (`__art`: the deep iris ground, `--gw-stage-*`); an optional strip under it. The page behind dims and blurs (`.gw-scrim`). Script: `docs/markup-contract.md` |
|
|
342
365
|
| `.gw-header__actions` | Sign in, a second text action, Start free, the menu button (4px apart; the button 8px further) |
|
|
@@ -838,10 +861,12 @@ not make public with the review; masonry or a carousel that moves by itself.
|
|
|
838
861
|
|
|
839
862
|
| class | for |
|
|
840
863
|
|---|---|
|
|
841
|
-
| `.gw-reveal` | fades and rises
|
|
864
|
+
| `.gw-reveal` | fades and rises 20px into place once 12% of it is in view (the script adds `.is-in`); each one after the first among its siblings waits 70ms more (at most 380ms); shown anyway after 1.6s, and still under `prefers-reduced-motion` |
|
|
865
|
+
| `[data-gw-count]` | inside a `.gw-reveal`: its number counts up from 0 when the reveal shows (`<span data-gw-count>$1,200+</span>`) |
|
|
842
866
|
|
|
843
|
-
Put it on a block (a grid, a list, a window),
|
|
844
|
-
the first screen, and don't animate anything but `transform` and `opacity`.
|
|
867
|
+
Put it on a block (a grid, a list, a window), or on each card of one grid to cascade them. Don't use it on the hero
|
|
868
|
+
or anything in the first screen, and don't animate anything but `transform` and `opacity`. A count is for a number
|
|
869
|
+
that is a measure (a total, a percentage), never for a step's ordinal ("01").
|
|
845
870
|
|
|
846
871
|
### Spotlight `data-gw-spot` (0.9.0)
|
|
847
872
|
|
package/docs/markup-contract.md
CHANGED
|
@@ -44,7 +44,7 @@ the spotlight (0.9.0). It comes in two entries built from one source
|
|
|
44
44
|
to the page (Escape, the outside click, the busy-button guard, the system theme, the scroll) are installed once.
|
|
45
45
|
- **`destroy(root = document)`** takes back what `init` wired under `root`: its listeners and its marks. An open
|
|
46
46
|
mega menu or mobile menu is closed first, so an unmounted menu cannot leave the page locked.
|
|
47
|
-
- **`version`**: `'0.
|
|
47
|
+
- **`version`**: `'0.12.0'`. The classic script exposes the three as `window.gwWebsite`.
|
|
48
48
|
- **`data-gw-ready`**: on each wired element, the parts wired on it (`mega`, on the `.gw-header` since 0.11.0, `menu`, `lang`, `theme`, `reveal`,
|
|
49
49
|
`header`, `segmented`, `toc`, `table`, `fold`, `spot`). A drill-down menu is wired with its toggle (`menu`). It is a mark for people and tests, not a switch: markup copied from a live page, attribute and all,
|
|
50
50
|
is still wired.
|
|
@@ -170,9 +170,13 @@ Script behaviour:
|
|
|
170
170
|
(`gw-theme`, every access in try/catch), updates `aria-label` on every toggle, fires `gw:theme`. On load, a stored
|
|
171
171
|
choice is applied before paint by a one-line inline script in `<head>` that the template carries
|
|
172
172
|
(`templates/page.html`).
|
|
173
|
-
- **Reveal
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
- **Reveal** (0.12.0, as gemboss.ai): elements with `.gw-reveal` get `.is-in` once 12% of them is in view, the bottom
|
|
174
|
+
6% of the view not counting (IntersectionObserver, once). Each `.gw-reveal` after the first among its `.gw-reveal`
|
|
175
|
+
siblings waits 70ms more (at most 380ms), so a grid of them cascades. A number in a `[data-gw-count]` element inside
|
|
176
|
+
counts up from 0 to itself over 1.1s when its reveal shows (its first words: `$1,200+` keeps the sign, the grouping
|
|
177
|
+
and the decimals). Failsafe: 1.6s after the `init` that found them, or at once if IntersectionObserver is missing,
|
|
178
|
+
they get `.is-in`. Under `prefers-reduced-motion` the CSS shows them without motion, at once, and nothing counts.
|
|
179
|
+
The values are `system.json` `motion.reveal`.
|
|
176
180
|
- **Header**: `.is-scrolled` on `[data-gw-header]` once the page scrolled more than 8px (the CSS may use it for the
|
|
177
181
|
hairline only; the bar does not change height or colour). The floating header needs nothing more from the script:
|
|
178
182
|
it is the same element with one more class.
|
|
@@ -521,7 +525,7 @@ CSS only: the script does not touch a form. The page (or its server) owns valida
|
|
|
521
525
|
|---|---|
|
|
522
526
|
| `.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) |
|
|
523
527
|
| `.gw-link` (+ `--arrow`) | inline text link in the accent; `--arrow` adds → that moves 2px on hover |
|
|
524
|
-
| `.gw-label` | the label above a heading: 13px muted, 8px
|
|
528
|
+
| `.gw-label` | the label above a heading: 13px muted, the 8px square before it in ink; `--gw-label-mark` (on it or around it) paints the square |
|
|
525
529
|
| `.gw-dim` | the muted first sentence inside a headline |
|
|
526
530
|
| `.gw-chip` (+ `.is-active`) | selectable token, pill |
|
|
527
531
|
| `.gw-segmented` (+ `--block`) | one choice out of two to four (script above) |
|
|
@@ -553,7 +557,7 @@ CSS only: the script does not touch a form. The page (or its server) owns valida
|
|
|
553
557
|
|---|---|
|
|
554
558
|
| `.gw-section` (+ `--soft`, `--tight`) | vertical rhythm; `--soft` on `--gw-bg-soft` |
|
|
555
559
|
| `.gw-band--night` | a dark band (max three per page plus the closing CTA, never adjacent) |
|
|
556
|
-
| `.gw-section__head` (+ `--center`) | label + h2 + one or two lines, max
|
|
560
|
+
| `.gw-section__head` (+ `--center`) | label + h2 + one or two lines, max 760px |
|
|
557
561
|
| `.gw-hero` (+ `__wash`, `--center`, `--compact`) | first screen; `__wash` is the soft colour wash behind it |
|
|
558
562
|
| `.gw-logos` | proof strip of customer or partner logos (real ones only) |
|
|
559
563
|
| `.gw-stats` (`__item`, `__value`, `__label`) | 3–4 facts in a row, each with its source |
|
package/docs/theming.md
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
A GemCommerce brand (GemPages, Gemians…) uses the kit as it is and chooses six things, **and nothing else**:
|
|
4
4
|
|
|
5
5
|
1. **the accent variables** (eight, light and dark),
|
|
6
|
-
2. **the wash hues** (only if the brand has its own),
|
|
7
|
-
|
|
6
|
+
2. **the wash hues** (only if the brand has its own), and the colours of its **announcement bar** and **Stage** (0.13.0,
|
|
7
|
+
only if it uses them and wants its own),
|
|
8
|
+
3. **the logo** (and the favicon): a mark beside the name, or one drawing of both (0.13.0),
|
|
8
9
|
4. **the action colour mode**: `data-gw-action="ink|accent"` on `<html>`,
|
|
9
10
|
5. **the radius profile**: `data-gw-radius="tight|soft"` on `<html>`,
|
|
10
11
|
6. **the header style**: the flat bar, or `.gw-header--float`.
|
|
@@ -46,9 +47,30 @@ brand's page lights in the brand's own colour, in light, in dark and in night ba
|
|
|
46
47
|
takes the corners of the element it lights, so both radius profiles hold. A brand does not choose its colour, its
|
|
47
48
|
strength or its size (they are tokens), and never a second colour.
|
|
48
49
|
|
|
49
|
-
The logo and the favicon are files, not variables
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
The logo and the favicon are files, not variables, in the header and footer partials and `<link rel="icon">` in the
|
|
51
|
+
page head. The logo is one of two shapes:
|
|
52
|
+
|
|
53
|
+
- **a mark beside the name** (GemBoss): `<img class="gw-brand__mark">` (34×34, SVG preferred, `alt=""` because the name
|
|
54
|
+
is written next to it) and `<span class="gw-brand__name">`;
|
|
55
|
+
- **one drawing of both** (0.13.0, Gemians: the mark and the wordmark in one file): `<img class="gw-brand__logo">`, 26px
|
|
56
|
+
tall, its width the file's, its `alt` the brand's name. A logo that only works on a dark ground (a white wordmark)
|
|
57
|
+
comes with a second file in ink for the light theme, or the other way round: the file for light grounds first, then
|
|
58
|
+
the file for dark grounds as `class="gw-brand__logo gw-brand__logo--dark"`. The kit shows one per theme (the system's
|
|
59
|
+
setting or the toggle). Keep the brand's own drawing: recolour the wordmark, never redraw it in the kit's type.
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<a class="gw-brand" href="/"><img class="gw-brand__logo" src="/assets/img/logo.png" alt="Gemians" width="150" height="26"><img class="gw-brand__logo gw-brand__logo--dark" src="/assets/img/logo-on-dark.png" alt="Gemians" width="150" height="26"></a>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`check` reports a logo without its name in `alt`, without `width` and `height`, or a dark file without the light one
|
|
66
|
+
right before it (`brand-markup`).
|
|
67
|
+
|
|
68
|
+
**The announcement bar and the Stage** (0.13.0). The bar over the header (`--gw-announce-*`, `system.json`
|
|
69
|
+
`color.announce`) and the Stage the mega menu's pictures sit on (`--gw-stage-*`, `color.stage`) are GemBoss's iris by
|
|
70
|
+
default. A brand that shows them may set its own in the same `:root` block: the bar's `bg`, `ink`, `line`, `tag`,
|
|
71
|
+
`tag-ink`, `link`, `link-line`; the Stage's `glow`, `glow-2`, `top`, `bottom`, `dot`, `ink`. Both stay the same in
|
|
72
|
+
light and dark. `check` computes the bar's words and link on its ground and the Stage's words on both ends of its
|
|
73
|
+
gradient, and fails below 4.5:1 (`brand-contrast`).
|
|
52
74
|
|
|
53
75
|
The footer (0.7.0, `templates/footer.html`) is the same for every brand; a brand fills it. Its family row lists the
|
|
54
76
|
brand's siblings (a GemPages site: GemBoss, GemX, GemUpsells), its Ask AI row asks about the brand, and its wordmark
|
|
@@ -98,7 +120,7 @@ the panels; in `tight` that is the 16px the header was designed with.
|
|
|
98
120
|
- **Gradient or accent-coloured words in a heading.** A heading is ink, with a muted first sentence (`.gw-dim`).
|
|
99
121
|
- **Accent-coloured section bands.** A section is white, `--gw-bg-soft` or a night band; the accent only lights a
|
|
100
122
|
night band's top edge.
|
|
101
|
-
- **All-caps labels.** Labels are sentence case, muted, led by the 8px
|
|
123
|
+
- **All-caps labels.** Labels are sentence case, muted, led by the 8px square (ink, or the colour `--gw-label-mark` gives it).
|
|
102
124
|
- **A light on a heading, or in more than one colour.** The spotlight lights things you click, in the accent only.
|
|
103
125
|
|
|
104
126
|
These make a page louder without making it clearer, and `check` reports the last one (`uppercase-label`) and any
|
|
@@ -106,8 +128,9 @@ button filled from site CSS (`accent-button`).
|
|
|
106
128
|
|
|
107
129
|
`check` also reports: colours outside the tokens, a redefined kit token (`token-redefined`, including the radius and
|
|
108
130
|
action tokens), overrides of `.gw-*` classes, an unknown profile or one set on another element than `<html>`
|
|
109
|
-
(`profile-unknown`), `.gw-header--float` anywhere but the header itself (`header-float-markup`),
|
|
110
|
-
`data-gw-action="accent"`, accent button words below 4.5:1 (`action-contrast`)
|
|
131
|
+
(`profile-unknown`), `.gw-header--float` anywhere but the header itself (`header-float-markup`), on a page with
|
|
132
|
+
`data-gw-action="accent"`, accent button words below 4.5:1 (`action-contrast`), a brand's own announcement bar or
|
|
133
|
+
Stage with words below 4.5:1 (`brand-contrast`), and a one-file logo without its markup (`brand-markup`).
|
|
111
134
|
|
|
112
135
|
## A full example
|
|
113
136
|
|
|
@@ -136,7 +159,7 @@ action tokens), overrides of `.gw-*` classes, an unknown profile or one set on a
|
|
|
136
159
|
…
|
|
137
160
|
<header class="gw-header gw-header--float" data-gw-header>
|
|
138
161
|
<div class="gw-wrap gw-header__bar">
|
|
139
|
-
<a class="gw-brand" href="/"><img class="gw-brand__mark" src="/logo-mark.svg" alt="" width="
|
|
162
|
+
<a class="gw-brand" href="/"><img class="gw-brand__mark" src="/logo-mark.svg" alt="" width="34" height="34"><span class="gw-brand__name">GemPages</span></a>
|
|
140
163
|
```
|
|
141
164
|
|
|
142
165
|
## Check the contrast before you ship
|
|
@@ -145,8 +168,10 @@ The accent family is read as text: the kit draws links, step numbers, active men
|
|
|
145
168
|
`--gw-accent-strong`, and the label square, focus ring, check marks and the featured plan's hairline in `--gw-accent`.
|
|
146
169
|
So:
|
|
147
170
|
|
|
148
|
-
1. **Accent text on white at least 4.5:1**: `--gw-accent-light` and `--gw-
|
|
149
|
-
|
|
171
|
+
1. **Accent text on white at least 4.5:1**: `--gw-accent-strong-light` on `#ffffff` and on `--gw-bg-soft`
|
|
172
|
+
(`#f2f3f5`), where links sit in soft sections. The plain `--gw-accent-light` is never text (icons, marks, the focus
|
|
173
|
+
ring, the featured plan's hairline): **3:1** on `#ffffff` is enough (WCAG 1.4.11), so a gold or a light green can
|
|
174
|
+
stay bright there (0.13.0; this line asked 4.5:1 of it before, which no gold passes).
|
|
150
175
|
2. `--gw-accent-strong-light` on `--gw-accent-soft-light`: at least 4.5:1 (the accent badge, the active chip and the
|
|
151
176
|
chosen option of a segmented control).
|
|
152
177
|
3. `--gw-accent-dark` and `--gw-accent-strong-dark` on `#0d0d12` (dark page) and on `#0c0e14` (night band): at
|
|
@@ -158,7 +183,9 @@ So:
|
|
|
158
183
|
build does the same for its own colours.
|
|
159
184
|
|
|
160
185
|
Any WCAG contrast checker works (paste the two hex values). A bright brand colour (yellow, light green, cyan) almost
|
|
161
|
-
never passes on white: darken it for `-light
|
|
186
|
+
never passes as text on white: darken it for `-strong-light`, keep it near the brand for `-light` (3:1) and bright
|
|
187
|
+
for `-dark`. A colour that needs dark words on it (gold, yellow) cannot be the button's fill and its text colour at
|
|
188
|
+
once: keep the ink button (Gemians does). If you cannot get 4.5:1 without
|
|
162
189
|
changing the brand colour beyond recognition, keep the kit's accent and use the brand colour only in the logo and
|
|
163
190
|
illustrations, and keep the ink button.
|
|
164
191
|
|
package/js/website.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/*! @gemboss/website-ui 0.
|
|
1
|
+
/*! @gemboss/website-ui 0.13.0 website.js | GENERATED by scripts/build.mjs from js/website.mjs (the source, with its comments): edit that file | spec: docs/markup-contract.md | load with defer */
|
|
2
2
|
(function () {
|
|
3
3
|
'use strict';
|
|
4
4
|
if (window.gwWebsite) return;
|
|
5
5
|
|
|
6
|
-
var version = '0.
|
|
6
|
+
var version = '0.13.0';
|
|
7
7
|
var api = { version: version, init: init, destroy: destroy };
|
|
8
8
|
var doc, root, mine, fine, sys, wide, desk, mid, live = [], queued = false;
|
|
9
9
|
var still, lights;
|
|
@@ -483,17 +483,41 @@ function theme(ctx) {
|
|
|
483
483
|
relabel();
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
+
var REVEAL = { threshold: 0.12, margin: '0px 0px -6% 0px', stagger: 70, staggerMax: 380, failsafe: 1600, count: 1100 };
|
|
487
|
+
function countUp(el) {
|
|
488
|
+
var n = el.firstChild, m = n && n.nodeType === 3 && /^(\s*\D*?)(\d[\d,]*(?:\.\d+)?)(.*)$/.exec(n.nodeValue), t0;
|
|
489
|
+
if (!m) return;
|
|
490
|
+
var num = m[2].replace(/,/g, ''), dec = (num.split('.')[1] || '').length;
|
|
491
|
+
function fmt(v) { v = v.toFixed(dec); return m[1] + (num !== m[2] ? Number(v).toLocaleString('en-US') : v) + m[3]; }
|
|
492
|
+
n.nodeValue = fmt(0);
|
|
493
|
+
requestAnimationFrame(function step(ts) {
|
|
494
|
+
var p = Math.min((ts - (t0 = t0 || ts)) / REVEAL.count, 1);
|
|
495
|
+
n.nodeValue = fmt(p < 1 ? num * (1 - Math.pow(1 - p, 3)) : +num);
|
|
496
|
+
if (p < 1) requestAnimationFrame(step);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
486
499
|
function reveal(ctx) {
|
|
487
|
-
var els = [], io =
|
|
488
|
-
each(ctx, '.gw-reveal', 'reveal', function (el) {
|
|
489
|
-
|
|
500
|
+
var els = [], io, calm = still.matches;
|
|
501
|
+
each(ctx, '.gw-reveal', 'reveal', function (el) {
|
|
502
|
+
els.push(el);
|
|
503
|
+
return { stop: function () { if (io) io.unobserve(el); el.style.transitionDelay = ''; } };
|
|
504
|
+
});
|
|
505
|
+
function show(el) {
|
|
506
|
+
if (el.classList.contains('is-in')) return;
|
|
507
|
+
el.classList.add('is-in');
|
|
508
|
+
if (!calm) [].forEach.call(el.querySelectorAll('[data-gw-count]'), countUp);
|
|
509
|
+
}
|
|
490
510
|
if (!els.length) return;
|
|
491
|
-
if (!window.IntersectionObserver ||
|
|
511
|
+
if (!window.IntersectionObserver || calm) return els.forEach(show);
|
|
512
|
+
els.forEach(function (el) {
|
|
513
|
+
var i = [].filter.call(el.parentNode.children, function (c) { return c.classList.contains('gw-reveal'); }).indexOf(el);
|
|
514
|
+
if (i > 0) el.style.transitionDelay = Math.min(i * REVEAL.stagger, REVEAL.staggerMax) + 'ms';
|
|
515
|
+
});
|
|
492
516
|
io = new IntersectionObserver(function (list) {
|
|
493
|
-
list.forEach(function (en) { if (en.isIntersecting) { en.target
|
|
494
|
-
}, { threshold:
|
|
517
|
+
list.forEach(function (en) { if (en.isIntersecting) { show(en.target); io.unobserve(en.target); } });
|
|
518
|
+
}, { threshold: REVEAL.threshold, rootMargin: REVEAL.margin });
|
|
495
519
|
els.forEach(function (el) { io.observe(el); });
|
|
496
|
-
setTimeout(
|
|
520
|
+
setTimeout(function () { els.forEach(show); io.disconnect(); }, REVEAL.failsafe);
|
|
497
521
|
}
|
|
498
522
|
|
|
499
523
|
function scrolled() {
|