@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/css/components.css
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* components: buttons, links, chips, badges, cards, windows, icons, lists, tables, FAQ. */
|
|
2
2
|
|
|
3
|
-
/* actions are rectangles: 40px, radius 8, 14px/600, sentence case
|
|
3
|
+
/* actions are rectangles: 40px, the action radius of the site's profile (8, 12 in soft), 14px/600, sentence case.
|
|
4
|
+
The fill is the action colour: ink, or the brand's accent under data-gw-action="accent" (tokens.css). Never a
|
|
5
|
+
gradient, a wash hue or a shadow. */
|
|
4
6
|
.gw-btn {
|
|
5
7
|
position: relative;
|
|
6
8
|
display: inline-flex;
|
|
@@ -35,6 +37,43 @@
|
|
|
35
37
|
.gw-btn--lg { height: 48px; padding: 0 24px; font-size: 16px; }
|
|
36
38
|
.gw-btn--block { display: flex; width: 100%; }
|
|
37
39
|
.gw-btn:disabled, .gw-btn[aria-disabled="true"] { opacity: 0.48; cursor: not-allowed; pointer-events: none; }
|
|
40
|
+
/* pressed: the ink one step darker (the hover ink, as the dashboard's primary), nothing moves; the frameless variants
|
|
41
|
+
take one more step of tint */
|
|
42
|
+
.gw-btn:active { background: var(--gw-action-hover); }
|
|
43
|
+
.gw-btn--secondary:active { background: var(--gw-bg-soft); border-color: var(--gw-ink); }
|
|
44
|
+
.gw-btn--ghost:active { background: var(--gw-line); }
|
|
45
|
+
.gw-btn--on-night:active { background: var(--gw-on-night); opacity: 0.76; }
|
|
46
|
+
/* loading: aria-busy="true" on the <button>. The label stays (it keeps the width and the accessible name) but is not
|
|
47
|
+
painted; a 16px ring in the button's own ink turns in its place, the dashboard's spinner. ::before, because
|
|
48
|
+
.gw-btn--sm draws its hit area with ::after. website.js ignores presses on a busy button (no double submit). */
|
|
49
|
+
.gw-btn[aria-busy="true"] { cursor: progress; -webkit-text-fill-color: transparent; }
|
|
50
|
+
.gw-btn[aria-busy="true"] svg { opacity: 0; }
|
|
51
|
+
.gw-btn[aria-busy="true"]::before {
|
|
52
|
+
content: "";
|
|
53
|
+
position: absolute;
|
|
54
|
+
top: 50%;
|
|
55
|
+
left: 50%;
|
|
56
|
+
width: 16px;
|
|
57
|
+
height: 16px;
|
|
58
|
+
margin: -8px 0 0 -8px;
|
|
59
|
+
border: 2px solid color-mix(in srgb, currentColor 18%, transparent);
|
|
60
|
+
border-top-color: currentColor;
|
|
61
|
+
border-radius: var(--gw-radius-chip);
|
|
62
|
+
animation: gw-spin var(--gw-spin-ms) linear infinite;
|
|
63
|
+
}
|
|
64
|
+
@keyframes gw-spin { to { transform: rotate(360deg); } }
|
|
65
|
+
/* reduced motion: the ring stands still as an ellipsis, three dots in the button's ink */
|
|
66
|
+
@media (prefers-reduced-motion: reduce) {
|
|
67
|
+
.gw-btn[aria-busy="true"]::before {
|
|
68
|
+
border: 0;
|
|
69
|
+
border-radius: var(--gw-radius-0);
|
|
70
|
+
background:
|
|
71
|
+
radial-gradient(circle at 3px 50%, currentColor 1.5px, transparent 2px),
|
|
72
|
+
radial-gradient(circle at 8px 50%, currentColor 1.5px, transparent 2px),
|
|
73
|
+
radial-gradient(circle at 13px 50%, currentColor 1.5px, transparent 2px);
|
|
74
|
+
animation: none;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
38
77
|
|
|
39
78
|
/* inline text link with an arrow that moves 2px on hover */
|
|
40
79
|
.gw-link--arrow {
|
|
@@ -72,6 +111,45 @@
|
|
|
72
111
|
color: var(--gw-accent-strong);
|
|
73
112
|
}
|
|
74
113
|
|
|
114
|
+
/* segmented control: one choice out of two to four (Monthly / Yearly), a radio group of buttons. The dashboard's look
|
|
115
|
+
(@gemboss/ui .ui-seg): one hairline group, the action radius, a hairline between options, the chosen one on the
|
|
116
|
+
accent tint, so it follows a brand's accent. The weight never changes with the choice: the labels do not move. */
|
|
117
|
+
.gw-segmented {
|
|
118
|
+
display: inline-flex;
|
|
119
|
+
max-width: 100%;
|
|
120
|
+
border: 1px solid var(--gw-line-strong);
|
|
121
|
+
border-radius: var(--gw-radius-action);
|
|
122
|
+
background: var(--gw-surface);
|
|
123
|
+
overflow: hidden;
|
|
124
|
+
overflow: clip;
|
|
125
|
+
vertical-align: middle;
|
|
126
|
+
}
|
|
127
|
+
.gw-segmented--block { display: flex; width: 100%; }
|
|
128
|
+
.gw-segmented > button {
|
|
129
|
+
display: inline-flex;
|
|
130
|
+
flex: 0 1 auto;
|
|
131
|
+
align-items: center;
|
|
132
|
+
justify-content: center;
|
|
133
|
+
min-width: 0;
|
|
134
|
+
height: calc(var(--gw-button-height) - 2px);
|
|
135
|
+
padding: 0 16px;
|
|
136
|
+
border: 0;
|
|
137
|
+
background: transparent;
|
|
138
|
+
color: var(--gw-muted);
|
|
139
|
+
font-size: 14px;
|
|
140
|
+
font-weight: 500;
|
|
141
|
+
line-height: 1.4;
|
|
142
|
+
white-space: nowrap;
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
}
|
|
145
|
+
.gw-segmented--block > button { flex: 1 1 0; }
|
|
146
|
+
.gw-segmented > button + button { border-left: 1px solid var(--gw-line-strong); }
|
|
147
|
+
.gw-segmented > button:hover { background: var(--gw-bg-soft); color: var(--gw-ink); }
|
|
148
|
+
.gw-segmented > button:active { background: var(--gw-line); }
|
|
149
|
+
/* inside the group: the group clips its corners, so the ring sits within the option */
|
|
150
|
+
.gw-segmented > button:focus-visible { outline-offset: -2px; }
|
|
151
|
+
.gw-segmented > button[aria-checked="true"] { background: var(--gw-accent-soft); color: var(--gw-accent-strong); }
|
|
152
|
+
|
|
75
153
|
/* badges: small status, pill */
|
|
76
154
|
.gw-badge {
|
|
77
155
|
display: inline-flex;
|
|
@@ -94,8 +172,8 @@
|
|
|
94
172
|
.gw-badge--success::before, .gw-badge--warning::before { content: ""; flex: none; width: 6px; height: 6px; border-radius: var(--gw-radius-chip); background: var(--gw-success); }
|
|
95
173
|
.gw-badge--warning::before { background: var(--gw-warning); }
|
|
96
174
|
|
|
97
|
-
/* cards sit: white, 1px hairline, radius
|
|
98
|
-
app); no hover lift. A soft card is a tint and carries no shadow. */
|
|
175
|
+
/* cards sit: white, 1px hairline, the card radius of the site's profile (12, 20 in soft), the dashboard's resting
|
|
176
|
+
shadow (Chris 24/10/2026: one language with the app); no hover lift. A soft card is a tint and carries no shadow. */
|
|
99
177
|
.gw-card {
|
|
100
178
|
padding: 24px;
|
|
101
179
|
border: 1px solid var(--gw-line);
|
|
@@ -145,6 +223,17 @@
|
|
|
145
223
|
.gw-window__body { padding: 16px; }
|
|
146
224
|
.gw-window__body:has(> img:only-child) { padding: 0; }
|
|
147
225
|
.gw-window__body > img { width: 100%; }
|
|
226
|
+
/* what the numbers in the window are: "Illustrative data." under a drawn mock-up, or the source of real ones. The
|
|
227
|
+
last child of .gw-window, after __body (a <figcaption> when the window is a <figure>). */
|
|
228
|
+
.gw-window__caption {
|
|
229
|
+
margin: 0;
|
|
230
|
+
padding: 0 16px 12px;
|
|
231
|
+
color: var(--gw-muted);
|
|
232
|
+
font-size: 12px;
|
|
233
|
+
line-height: 1.5;
|
|
234
|
+
text-align: end;
|
|
235
|
+
}
|
|
236
|
+
.gw-window__body:has(> img:only-child) + .gw-window__caption { padding-top: 12px; }
|
|
148
237
|
|
|
149
238
|
/* icon: 20px inline stroke SVG (1.7 to 2px stroke, currentColor) */
|
|
150
239
|
.gw-icon { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 20px; height: 20px; }
|
|
@@ -233,4 +322,5 @@
|
|
|
233
322
|
}
|
|
234
323
|
.gw-btn--lg { min-height: 48px; }
|
|
235
324
|
:is(a, button, label).gw-chip { min-height: var(--gw-tap-min); }
|
|
325
|
+
.gw-segmented > button { height: auto; min-height: var(--gw-tap-min); padding-block: 8px; white-space: normal; text-wrap: balance; }
|
|
236
326
|
}
|
package/css/header.css
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* header: announcement, a flat 64px bar with one hairline, mega menu, mobile menu below 1080px.
|
|
2
|
-
Not a floating pill: no radius, no blur, no shadow on the bar. Nothing tucks under it.
|
|
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
|
+
other shape, .gw-header--float (an inset card from 1080px, at the end of this file). */
|
|
3
4
|
.gw-announce {
|
|
4
5
|
padding: 8px var(--gw-gutter);
|
|
5
6
|
border-bottom: 1px solid var(--gw-line);
|
|
@@ -83,9 +84,9 @@
|
|
|
83
84
|
text-underline-offset: 8px;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
/* mega menu: a floating card centred under the bar, as on gemboss.ai (Chris 24/10/2026): one hairline,
|
|
87
|
-
the raised shadow (it is a popover), 32px inside. The bridge
|
|
88
|
-
the 8px gap from the trigger. */
|
|
87
|
+
/* mega menu: a floating card centred under the bar, as on gemboss.ai (Chris 24/10/2026): one hairline, the popover
|
|
88
|
+
radius of the site's profile (20, 24 in soft), the raised shadow (it is a popover), 32px inside. The bridge
|
|
89
|
+
(::before) keeps the pointer inside while it crosses the 8px gap from the trigger. */
|
|
89
90
|
.gw-mega {
|
|
90
91
|
position: absolute;
|
|
91
92
|
top: calc(100% + 8px);
|
|
@@ -95,7 +96,7 @@
|
|
|
95
96
|
overflow-y: auto;
|
|
96
97
|
transform: translateX(-50%);
|
|
97
98
|
border: 1px solid var(--gw-line);
|
|
98
|
-
border-radius:
|
|
99
|
+
border-radius: var(--gw-radius-popover);
|
|
99
100
|
background: var(--gw-surface);
|
|
100
101
|
box-shadow: var(--gw-shadow-raised);
|
|
101
102
|
}
|
|
@@ -229,3 +230,34 @@ html.is-menu-open .gw-header { position: fixed; top: 0; left: 0; right: 0; }
|
|
|
229
230
|
@media (max-width: 768px), (pointer: coarse) {
|
|
230
231
|
.gw-theme-toggle, .gw-menu-toggle { width: var(--gw-tap-min); height: var(--gw-tap-min); }
|
|
231
232
|
}
|
|
233
|
+
|
|
234
|
+
/* floating header (0.3.0), a brand's header profile: <header class="gw-header gw-header--float">. The same bar, mega
|
|
235
|
+
menu and script, drawn from 1080px as an inset card: the panel radius of the site's profile (16, 24 in soft), the
|
|
236
|
+
surface at 85% over a 10px blur, one hairline, the raised shadow, a 16px gap above it and when it sticks. It is
|
|
237
|
+
wrap + 2 x 24px + its border wide, so the bar's content lines up with the page's from 1170px. Below 1080px it is
|
|
238
|
+
the flat bar, so the phone menu is unchanged. Solid for a visitor who asks for less transparency, and where the
|
|
239
|
+
browser cannot blur. No other floating or blurred header: it is a profile, not a style to copy. */
|
|
240
|
+
@media (min-width: 1080px) {
|
|
241
|
+
.gw-header--float {
|
|
242
|
+
top: var(--gw-header-gap);
|
|
243
|
+
width: min(100% - 32px, var(--gw-wrap) + 50px);
|
|
244
|
+
margin: var(--gw-header-gap) auto 0;
|
|
245
|
+
border: 1px solid var(--gw-line);
|
|
246
|
+
border-radius: var(--gw-radius-panel);
|
|
247
|
+
background: color-mix(in srgb, var(--gw-surface) var(--gw-header-surface), transparent);
|
|
248
|
+
-webkit-backdrop-filter: blur(var(--gw-header-blur));
|
|
249
|
+
backdrop-filter: blur(var(--gw-header-blur));
|
|
250
|
+
box-shadow: var(--gw-shadow-raised);
|
|
251
|
+
}
|
|
252
|
+
.gw-header--float.is-scrolled { border-color: var(--gw-line-strong); }
|
|
253
|
+
.gw-header--float > .gw-header__bar { width: auto; padding-inline: 24px; }
|
|
254
|
+
.gw-header--float .gw-mega { max-height: calc(100vh - var(--gw-header-height) - var(--gw-header-gap) - 24px); }
|
|
255
|
+
/* an anchor lands below the card, not under it */
|
|
256
|
+
:root:has(.gw-header--float) [id] { scroll-margin-top: calc(var(--gw-header-height) + var(--gw-header-gap) + 18px); }
|
|
257
|
+
}
|
|
258
|
+
@media (min-width: 1080px) and (prefers-reduced-transparency: reduce) {
|
|
259
|
+
.gw-header--float { background: var(--gw-surface); -webkit-backdrop-filter: none; backdrop-filter: none; }
|
|
260
|
+
}
|
|
261
|
+
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
|
262
|
+
.gw-header--float { background: var(--gw-surface); }
|
|
263
|
+
}
|
package/css/layout.css
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
@media (min-width: 768px) { .gw-section__head:not(:last-child) { margin-bottom: 48px; } }
|
|
20
20
|
|
|
21
21
|
: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
|
-
:where(.gw-section__head, .gw-hero > .gw-wrap, .gw-split > :not(.gw-window), .gw-cta, .gw-card, .gw-plan) > .gw-actions { margin-top: 32px; }
|
|
22
|
+
: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
23
|
: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; }
|
|
24
24
|
|
|
25
25
|
/* a row of actions: one .gw-btn, then a secondary or a link */
|
package/css/prose.css
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/* prose: long-form reading (a blog post, a docs page, terms and privacy) inside one wrapper, .gw-prose. The plain HTML a
|
|
2
|
+
CMS or a markdown renderer writes is styled as it comes: headings, paragraphs, lists, quotes, code, tables, images
|
|
3
|
+
and captions, on the kit's scales. The rules sit at :where() weight and reach bare elements only (lists without a
|
|
4
|
+
class, the wrapper's own headings, quotes and tables), so a kit component placed inside the prose (.gw-table,
|
|
5
|
+
.gw-window, .gw-list--check, .gw-quote, .gw-btn) keeps its own look. --sm: a 16px body for docs and legal. */
|
|
6
|
+
.gw-prose {
|
|
7
|
+
max-width: 68ch;
|
|
8
|
+
min-width: 0;
|
|
9
|
+
color: var(--gw-ink);
|
|
10
|
+
font-size: var(--gw-body-size);
|
|
11
|
+
line-height: var(--gw-body-leading);
|
|
12
|
+
overflow-wrap: break-word;
|
|
13
|
+
}
|
|
14
|
+
.gw-prose--sm { font-size: 16px; }
|
|
15
|
+
|
|
16
|
+
/* the flow: each block 20px below the one before it (16px in --sm); more air around headings and large blocks */
|
|
17
|
+
:where(.gw-prose) > * + * { margin-top: 20px; }
|
|
18
|
+
:where(.gw-prose--sm) > * + * { margin-top: 16px; }
|
|
19
|
+
:where(.gw-prose) > * + :where(figure, img, video, pre, table, hr, .gw-table, .gw-window),
|
|
20
|
+
:where(.gw-prose) > :where(figure, img, video, pre, table, hr, .gw-table, .gw-window) + * { margin-top: 32px; }
|
|
21
|
+
:where(.gw-prose) > * + h2 { margin-top: 48px; }
|
|
22
|
+
:where(.gw-prose) > * + h3 { margin-top: 40px; }
|
|
23
|
+
:where(.gw-prose) > * + h4 { margin-top: 32px; }
|
|
24
|
+
:where(.gw-prose--sm) > * + h2 { margin-top: 40px; }
|
|
25
|
+
:where(.gw-prose--sm) > * + h3 { margin-top: 32px; }
|
|
26
|
+
:where(.gw-prose--sm) > * + h4 { margin-top: 24px; }
|
|
27
|
+
:where(.gw-prose) > h1 + * { margin-top: 24px; }
|
|
28
|
+
:where(.gw-prose) > :is(h2, h3, h4) + * { margin-top: 12px; }
|
|
29
|
+
|
|
30
|
+
/* headings: the article's own steps of the type scale, not the page's display sizes (an h2 in a post is not a
|
|
31
|
+
section headline); h1 is the article's title at the section-headline size */
|
|
32
|
+
:where(.gw-prose) > :is(h1, h2, h3, h4) {
|
|
33
|
+
color: var(--gw-ink);
|
|
34
|
+
font-weight: var(--gw-heading-weight);
|
|
35
|
+
letter-spacing: var(--gw-heading-tracking);
|
|
36
|
+
line-height: 1.25;
|
|
37
|
+
}
|
|
38
|
+
:where(.gw-prose) > h1 { font-size: var(--gw-display-h2); font-weight: var(--gw-display-weight); letter-spacing: var(--gw-display-tracking); line-height: var(--gw-heading-leading); }
|
|
39
|
+
:where(.gw-prose) > h2 { font-size: 30px; line-height: 1.2; }
|
|
40
|
+
:where(.gw-prose) > h3 { font-size: 24px; }
|
|
41
|
+
:where(.gw-prose) > h4 { font-size: 20px; }
|
|
42
|
+
:where(.gw-prose--sm) > h2 { font-size: 24px; }
|
|
43
|
+
:where(.gw-prose--sm) > h3 { font-size: 20px; }
|
|
44
|
+
:where(.gw-prose--sm) > h4 { font-size: 18px; }
|
|
45
|
+
|
|
46
|
+
/* lists without a class: plain bullets and numbers, the markers muted */
|
|
47
|
+
:where(.gw-prose :is(ul, ol):not([class])) { padding-left: 24px; }
|
|
48
|
+
:where(.gw-prose ul:not([class])) { list-style: disc; }
|
|
49
|
+
:where(.gw-prose ol:not([class])) { list-style: decimal; }
|
|
50
|
+
:where(.gw-prose :is(ul, ol):not([class]) > li + li),
|
|
51
|
+
:where(.gw-prose :is(ul, ol):not([class]) > li > * + *),
|
|
52
|
+
:where(.gw-prose :is(ul, ol):not([class]) > li > :is(ul, ol)) { margin-top: 8px; }
|
|
53
|
+
:where(.gw-prose :is(ul, ol):not([class]) > li)::marker { color: var(--gw-muted); font-variant-numeric: tabular-nums; }
|
|
54
|
+
|
|
55
|
+
/* a quotation: a rule on its left and muted words, as the dashboard's reader draws it */
|
|
56
|
+
:where(.gw-prose) > blockquote { padding-left: 20px; border-left: 3px solid var(--gw-line-strong); color: var(--gw-muted); }
|
|
57
|
+
:where(.gw-prose) > blockquote > * + * { margin-top: 12px; }
|
|
58
|
+
|
|
59
|
+
:where(.gw-prose) > hr { height: 0; border: 0; border-top: 1px solid var(--gw-line); }
|
|
60
|
+
:where(.gw-prose) :is(sup, sub) { line-height: 0; }
|
|
61
|
+
|
|
62
|
+
/* code: inline, a tint chip that wraps on a phone (radius 6 in every profile: it follows the text, not the shapes);
|
|
63
|
+
a block scrolls inside its own box */
|
|
64
|
+
:where(.gw-prose) :is(pre, code, kbd, samp) { font-family: var(--gw-font-mono); }
|
|
65
|
+
:where(.gw-prose) code {
|
|
66
|
+
padding: 2px 6px;
|
|
67
|
+
border-radius: var(--gw-radius-6);
|
|
68
|
+
background: color-mix(in srgb, var(--gw-ink) 8%, transparent);
|
|
69
|
+
color: var(--gw-ink);
|
|
70
|
+
font-size: 16px;
|
|
71
|
+
overflow-wrap: anywhere;
|
|
72
|
+
-webkit-box-decoration-break: clone;
|
|
73
|
+
box-decoration-break: clone;
|
|
74
|
+
}
|
|
75
|
+
:where(.gw-prose--sm) code { font-size: 14px; }
|
|
76
|
+
:where(.gw-prose) :is(h1, h2, h3, h4) code { font-size: inherit; }
|
|
77
|
+
:where(.gw-prose) figcaption code { font-size: 13px; }
|
|
78
|
+
/* in a table a name stays whole: the table scrolls instead */
|
|
79
|
+
:where(.gw-prose) :is(th, td) code { font-size: 13px; overflow-wrap: normal; white-space: nowrap; }
|
|
80
|
+
:where(.gw-prose) pre {
|
|
81
|
+
overflow-x: auto;
|
|
82
|
+
padding: 16px 20px;
|
|
83
|
+
border: 1px solid var(--gw-line);
|
|
84
|
+
border-radius: var(--gw-radius-card);
|
|
85
|
+
background: var(--gw-bg-soft);
|
|
86
|
+
color: var(--gw-ink);
|
|
87
|
+
font-size: 14px;
|
|
88
|
+
line-height: 1.6;
|
|
89
|
+
tab-size: 2;
|
|
90
|
+
}
|
|
91
|
+
:where(.gw-prose) pre code { padding: 0; border-radius: var(--gw-radius-0); background: none; font-size: inherit; overflow-wrap: normal; }
|
|
92
|
+
|
|
93
|
+
/* images and captions (an image inside a .gw-window keeps the window's square edges) */
|
|
94
|
+
:where(.gw-prose) > :is(img, video),
|
|
95
|
+
:where(.gw-prose) > :is(p, figure) > :is(img, video) { border-radius: var(--gw-radius-card); }
|
|
96
|
+
:where(.gw-prose) figcaption { margin-top: 12px; color: var(--gw-muted); font-size: 14px; line-height: 1.5; }
|
|
97
|
+
|
|
98
|
+
/* a table straight from markdown scrolls inside its own box, never the page. When you write the markup, the .gw-table
|
|
99
|
+
wrapper (a region with a name and tabindex="0") is the better choice. */
|
|
100
|
+
:where(.gw-prose) > table {
|
|
101
|
+
display: block;
|
|
102
|
+
width: max-content;
|
|
103
|
+
max-width: 100%;
|
|
104
|
+
overflow-x: auto;
|
|
105
|
+
border: 1px solid var(--gw-line);
|
|
106
|
+
border-radius: var(--gw-radius-card);
|
|
107
|
+
background: var(--gw-surface);
|
|
108
|
+
border-collapse: collapse;
|
|
109
|
+
font-size: 14px;
|
|
110
|
+
line-height: 1.5;
|
|
111
|
+
-webkit-overflow-scrolling: touch;
|
|
112
|
+
}
|
|
113
|
+
:where(.gw-prose) > table :is(th, td) { padding: 12px 16px; border-bottom: 1px solid var(--gw-line); text-align: left; vertical-align: top; }
|
|
114
|
+
:where(.gw-prose) > table thead th { background: var(--gw-bg-soft); color: var(--gw-ink); font-size: 13px; font-weight: 600; white-space: nowrap; }
|
|
115
|
+
:where(.gw-prose) > table tbody th { font-weight: 500; }
|
|
116
|
+
:where(.gw-prose) > table td { color: var(--gw-ink-2); font-variant-numeric: tabular-nums; }
|
|
117
|
+
:where(.gw-prose) > table tbody tr:last-child > * { border-bottom: 0; }
|
|
118
|
+
:where(.gw-prose) > table caption { padding: 12px 16px; color: var(--gw-muted); font-size: 13px; text-align: left; }
|
|
119
|
+
/* on a phone a cell keeps room for a few words a line: the table scrolls further instead of squeezing its text */
|
|
120
|
+
@media (max-width: 767px) { :where(.gw-prose) > table td { min-width: 160px; } }
|
package/css/tokens.css
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
:root {
|
|
3
3
|
color-scheme: light;
|
|
4
4
|
--gw-font: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
5
|
+
--gw-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
5
6
|
--gw-text-11: 11px;
|
|
6
7
|
--gw-text-12: 12px;
|
|
7
8
|
--gw-text-13: 13px;
|
|
@@ -45,11 +46,13 @@
|
|
|
45
46
|
--gw-radius-12: 12px;
|
|
46
47
|
--gw-radius-16: 16px;
|
|
47
48
|
--gw-radius-20: 20px;
|
|
49
|
+
--gw-radius-24: 24px;
|
|
48
50
|
--gw-radius-999: 999px;
|
|
49
51
|
--gw-radius-action: 8px;
|
|
50
52
|
--gw-radius-chip: 999px;
|
|
51
53
|
--gw-radius-card: 12px;
|
|
52
54
|
--gw-radius-panel: 16px;
|
|
55
|
+
--gw-radius-popover: 20px;
|
|
53
56
|
--gw-shadow: 0 1px 2px rgba(31,30,29,.04), 0 2px 6px rgba(31,30,29,.06);
|
|
54
57
|
--gw-shadow-sm: 0 1px 2px rgba(31,30,29,.04);
|
|
55
58
|
--gw-shadow-raised: 0 4px 16px rgba(31,30,29,.10);
|
|
@@ -57,10 +60,14 @@
|
|
|
57
60
|
--gw-wrap-narrow: 780px;
|
|
58
61
|
--gw-gutter: 22px;
|
|
59
62
|
--gw-header-height: 64px;
|
|
63
|
+
--gw-header-gap: 16px;
|
|
64
|
+
--gw-header-surface: 85%;
|
|
65
|
+
--gw-header-blur: 10px;
|
|
60
66
|
--gw-button-height: 40px;
|
|
61
67
|
--gw-tap-min: 44px;
|
|
62
68
|
--gw-ease: cubic-bezier(.22,1,.36,1);
|
|
63
69
|
--gw-reveal-ms: 700ms;
|
|
70
|
+
--gw-spin-ms: 700ms;
|
|
64
71
|
--gw-wash-pink: #ff6f5e;
|
|
65
72
|
--gw-wash-violet: #7c8af7;
|
|
66
73
|
--gw-wash-sky: #43b0ff;
|
|
@@ -196,3 +203,17 @@
|
|
|
196
203
|
--gw-success-soft: #132a22;
|
|
197
204
|
--gw-warning: #e0b341;
|
|
198
205
|
}
|
|
206
|
+
/* action profile "accent": data-gw-action="accent" on <html>. In night bands too, where the dark palette applies. */
|
|
207
|
+
:root[data-gw-action="accent"],
|
|
208
|
+
:root[data-gw-action="accent"] :is(.gw-band--night, .gw-showcase) {
|
|
209
|
+
--gw-action: var(--gw-accent);
|
|
210
|
+
--gw-action-hover: var(--gw-accent-strong);
|
|
211
|
+
--gw-on-action: var(--gw-on-accent);
|
|
212
|
+
}
|
|
213
|
+
/* radius profile "soft": data-gw-radius="soft" on <html> */
|
|
214
|
+
:root[data-gw-radius="soft"] {
|
|
215
|
+
--gw-radius-action: 12px;
|
|
216
|
+
--gw-radius-card: 20px;
|
|
217
|
+
--gw-radius-panel: 24px;
|
|
218
|
+
--gw-radius-popover: 24px;
|
|
219
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!-- Kit demo: one brand that chose all three profiles (docs/theming.md). The profiles are two attributes on <html> and
|
|
3
|
+
one class on the header; the only CSS of the brand is the accent family below. Sample text, not a brand's copy. -->
|
|
4
|
+
<html lang="en" data-gw-action="accent" data-gw-radius="soft">
|
|
5
|
+
<head>
|
|
6
|
+
<meta charset="utf-8">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<title>GemPages-style example · website UI kit</title>
|
|
9
|
+
<meta name="description" content="A kit demo with all three brand profiles on: the accent action, soft corners and the floating header, with a GemPages-style blue.">
|
|
10
|
+
<script>try{var t=localStorage.getItem('gw-theme');if(t==='light'||t==='dark')document.documentElement.setAttribute('data-theme',t)}catch(e){}</script>
|
|
11
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
12
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
13
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;650;700&display=swap">
|
|
14
|
+
<link rel="stylesheet" href="../website.css">
|
|
15
|
+
<style>
|
|
16
|
+
/* The brand's whole CSS: the accent family, light and dark, in one :root block (docs/theming.md).
|
|
17
|
+
Light: #3c67ff fills the primary button, white words on it are 4.58:1 (6.36:1 on the hover #2f52d6).
|
|
18
|
+
#2f52d6 as accent-strong is an EXAMPLE, not confirmed by the GemPages team: accent text needs it, because
|
|
19
|
+
#3c67ff is 4.13:1 on --gw-bg-soft; #2f52d6 is 6.36:1 on white, 5.73:1 on --gw-bg-soft, 5.54:1 on its tint.
|
|
20
|
+
Dark and night bands: #7c9bff is 7.37:1 on the dark page, and #0d0d12 words on it are 7.37:1. */
|
|
21
|
+
:root {
|
|
22
|
+
--gw-accent-light: #3c67ff;
|
|
23
|
+
--gw-accent-strong-light: #2f52d6;
|
|
24
|
+
--gw-accent-soft-light: #eaefff;
|
|
25
|
+
--gw-on-accent-light: #ffffff;
|
|
26
|
+
--gw-accent-dark: #7c9bff;
|
|
27
|
+
--gw-accent-strong-dark: #a9beff;
|
|
28
|
+
--gw-accent-soft-dark: #16213d;
|
|
29
|
+
--gw-on-accent-dark: #0d0d12;
|
|
30
|
+
}
|
|
31
|
+
</style>
|
|
32
|
+
</head>
|
|
33
|
+
<body class="gw-page">
|
|
34
|
+
<a class="gw-skip" href="#main">Skip to content</a>
|
|
35
|
+
|
|
36
|
+
<div class="gw-announce" role="region" aria-label="Announcement">Kit demo: a GemPages-style brand with all three profiles on. Sample text, not a brand's page. <a href="index.html">Back to the full demo</a></div>
|
|
37
|
+
<header class="gw-header gw-header--float" data-gw-header>
|
|
38
|
+
<div class="gw-wrap gw-header__bar">
|
|
39
|
+
<a class="gw-brand" href="brand-profiles.html"><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>
|
|
40
|
+
<nav class="gw-nav" aria-label="Main">
|
|
41
|
+
<ul class="gw-nav__list">
|
|
42
|
+
<li class="gw-nav__item">
|
|
43
|
+
<button class="gw-nav__trigger" type="button" aria-expanded="false" aria-controls="gw-mega-kit" data-gw-mega="gw-mega-kit">The kit</button>
|
|
44
|
+
<div class="gw-mega" id="gw-mega-kit" hidden>
|
|
45
|
+
<div class="gw-wrap gw-mega__grid">
|
|
46
|
+
<div class="gw-mega__col">
|
|
47
|
+
<p class="gw-mega__heading">See it</p>
|
|
48
|
+
<a class="gw-mega__link" href="index.html#components-h"><span class="gw-mega__icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/></svg></span><span><b>Components</b><i>Buttons, chips, cards, tables</i></span></a>
|
|
49
|
+
<a class="gw-mega__link" href="index.html#features-h"><span class="gw-mega__icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8h8M8 12h8M8 16h5"/></svg></span><span><b>Sections</b><i>Hero, features, pricing, FAQ</i></span></a>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="gw-mega__col">
|
|
52
|
+
<p class="gw-mega__heading">Brands</p>
|
|
53
|
+
<a class="gw-mega__link" href="theme-example.html"><span class="gw-mega__icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8"/><path d="M12 4a8 8 0 0 1 0 16z"/></svg></span><span><b>Accent only</b><i>A brand that changed one colour</i></span></a>
|
|
54
|
+
<a class="gw-mega__link" href="#profiles"><span class="gw-mega__icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h10M18 7h2M4 17h2M10 17h10"/><circle cx="16" cy="7" r="2"/><circle cx="8" cy="17" r="2"/></svg></span><span><b>All three profiles</b><i>This page</i></span></a>
|
|
55
|
+
</div>
|
|
56
|
+
<a class="gw-mega__feature" href="#profiles"><b>Three named choices</b><i>Action colour, corners and header. Chosen once per site.</i><span class="gw-link gw-link--arrow">See the profiles</span></a>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</li>
|
|
60
|
+
<li class="gw-nav__item"><a class="gw-nav__link" href="index.html">Default demo</a></li>
|
|
61
|
+
<li class="gw-nav__item"><a class="gw-nav__link" href="theme-example.html">Accent only</a></li>
|
|
62
|
+
</ul>
|
|
63
|
+
</nav>
|
|
64
|
+
<div class="gw-header__actions">
|
|
65
|
+
<button class="gw-theme-toggle" type="button" data-gw-theme-toggle aria-label="Switch to dark theme"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="12" r="8"/><path d="M12 4a8 8 0 0 1 0 16z" fill="currentColor"/></svg></button>
|
|
66
|
+
<a class="gw-btn gw-btn--secondary gw-btn--sm" href="index.html">Default demo</a>
|
|
67
|
+
<a class="gw-btn gw-btn--sm" href="#profiles">See the profiles</a>
|
|
68
|
+
<button class="gw-menu-toggle" type="button" aria-expanded="false" aria-controls="gw-mobile-menu" data-gw-menu-toggle aria-label="Open menu"><svg class="gw-menu-toggle__open" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg><svg class="gw-menu-toggle__close" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="gw-mobile-menu" id="gw-mobile-menu" hidden>
|
|
72
|
+
<nav class="gw-wrap" aria-label="Main, mobile">
|
|
73
|
+
<details class="gw-mobile-menu__group">
|
|
74
|
+
<summary>The kit</summary>
|
|
75
|
+
<ul class="gw-mobile-menu__list">
|
|
76
|
+
<li><a class="gw-mobile-menu__link" href="index.html#components-h">Components</a></li>
|
|
77
|
+
<li><a class="gw-mobile-menu__link" href="index.html#features-h">Sections</a></li>
|
|
78
|
+
<li><a class="gw-mobile-menu__link" href="#profiles">All three profiles</a></li>
|
|
79
|
+
</ul>
|
|
80
|
+
</details>
|
|
81
|
+
<a class="gw-mobile-menu__top" href="index.html">Default demo</a>
|
|
82
|
+
<a class="gw-mobile-menu__top" href="theme-example.html">Accent only</a>
|
|
83
|
+
<div class="gw-mobile-menu__actions">
|
|
84
|
+
<a class="gw-btn gw-btn--block" href="#profiles">See the profiles</a>
|
|
85
|
+
<a class="gw-btn gw-btn--secondary gw-btn--block" href="index.html">Default demo</a>
|
|
86
|
+
</div>
|
|
87
|
+
</nav>
|
|
88
|
+
</div>
|
|
89
|
+
</header>
|
|
90
|
+
|
|
91
|
+
<main id="main">
|
|
92
|
+
<section class="gw-hero" aria-labelledby="hero-h">
|
|
93
|
+
<div class="gw-hero__wash" aria-hidden="true"></div>
|
|
94
|
+
<div class="gw-wrap">
|
|
95
|
+
<p class="gw-label">Brand profiles</p>
|
|
96
|
+
<h1 id="hero-h"><span class="gw-dim">The same kit, three named choices.</span> Still one family.</h1>
|
|
97
|
+
<p class="gw-lead">This brand fills its primary button with its accent, rounds its corners one step and floats its header as a card. Everything else is the kit as it ships.</p>
|
|
98
|
+
<div class="gw-actions">
|
|
99
|
+
<a class="gw-btn" href="#profiles">See the three profiles</a>
|
|
100
|
+
<a class="gw-btn gw-btn--secondary" href="index.html">Open the default demo</a>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</section>
|
|
104
|
+
|
|
105
|
+
<section class="gw-section gw-section--soft" id="profiles" aria-labelledby="profiles-h">
|
|
106
|
+
<div class="gw-wrap">
|
|
107
|
+
<div class="gw-section__head">
|
|
108
|
+
<p class="gw-label">Three knobs</p>
|
|
109
|
+
<h2 id="profiles-h"><span class="gw-dim">Set once for the site.</span> Every component follows.</h2>
|
|
110
|
+
<p class="gw-lead">Each knob is a named profile, not a free value. A brand picks one of each, and the kit does the rest.</p>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="gw-grid--3">
|
|
113
|
+
<div class="gw-card">
|
|
114
|
+
<h3>Action colour</h3>
|
|
115
|
+
<p>The primary button takes the accent. Its label must reach 4.5:1 on the fill in light and dark, or the check fails.</p>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="gw-card">
|
|
118
|
+
<h3>Soft corners</h3>
|
|
119
|
+
<p>Actions 12px, cards 20px, panels 24px. Chips stay pills, and nothing else is allowed in between.</p>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="gw-card">
|
|
122
|
+
<h3>Floating header</h3>
|
|
123
|
+
<p>An inset card over a light blur, from 1080px. The same bar and mega menu; phones keep the flat bar.</p>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</section>
|
|
128
|
+
|
|
129
|
+
<section class="gw-section" aria-labelledby="shapes-h">
|
|
130
|
+
<div class="gw-wrap gw-split">
|
|
131
|
+
<div>
|
|
132
|
+
<p class="gw-label">Radius profiles</p>
|
|
133
|
+
<h2 id="shapes-h">Two sets of corners, nothing in between.</h2>
|
|
134
|
+
<p class="gw-lead">Tight is the default. Soft rounds every action, card and panel by one step.</p>
|
|
135
|
+
<div class="gw-actions">
|
|
136
|
+
<button class="gw-chip" type="button" aria-pressed="false">Tight</button>
|
|
137
|
+
<button class="gw-chip" type="button" aria-pressed="true">Soft</button>
|
|
138
|
+
<span class="gw-badge gw-badge--accent">Chosen on the page</span>
|
|
139
|
+
</div>
|
|
140
|
+
<div class="gw-actions">
|
|
141
|
+
<a class="gw-btn" href="#rules-h">See what stays fixed</a>
|
|
142
|
+
<a class="gw-btn gw-btn--ghost" href="theme-example.html">Accent only</a>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="gw-table" role="region" aria-labelledby="shapes-h" tabindex="0">
|
|
146
|
+
<table>
|
|
147
|
+
<thead><tr><th scope="col">Profile</th><th scope="col">Action</th><th scope="col">Card</th><th scope="col">Panel</th></tr></thead>
|
|
148
|
+
<tbody>
|
|
149
|
+
<tr><th scope="row">Tight, the default</th><td>8px</td><td>12px</td><td>16px</td></tr>
|
|
150
|
+
<tr><th scope="row">Soft</th><td>12px</td><td>20px</td><td>24px</td></tr>
|
|
151
|
+
</tbody>
|
|
152
|
+
</table>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</section>
|
|
156
|
+
|
|
157
|
+
<section class="gw-section gw-section--soft" aria-labelledby="rules-h">
|
|
158
|
+
<div class="gw-wrap gw-wrap--narrow">
|
|
159
|
+
<div class="gw-section__head">
|
|
160
|
+
<p class="gw-label">Still fixed</p>
|
|
161
|
+
<h2 id="rules-h"><span class="gw-dim">Three knobs, no more.</span> The rest is the kit's.</h2>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="gw-faq">
|
|
164
|
+
<details class="gw-faq__item"><summary>Can a brand choose its own radius?</summary><p>No. It picks tight or soft. A radius of its own, or a token it redefines, is reported by the check.</p></details>
|
|
165
|
+
<details class="gw-faq__item"><summary>Can a heading use the accent?</summary><p>No. Words in a heading stay ink or muted, never the accent or a gradient. Bands are white, soft grey or night, never the accent. Labels stay in sentence case.</p></details>
|
|
166
|
+
<details class="gw-faq__item"><summary>What changes on a phone?</summary><p>Nothing. Below 1080px the floating header is the flat bar, and the menu opens the same way.</p></details>
|
|
167
|
+
<details class="gw-faq__item"><summary>What if a visitor turns transparency off?</summary><p>The header becomes a solid card. A browser that cannot blur gets the same solid card.</p></details>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</section>
|
|
171
|
+
|
|
172
|
+
<section class="gw-section gw-band--night" aria-labelledby="cta-h">
|
|
173
|
+
<div class="gw-wrap gw-cta">
|
|
174
|
+
<p class="gw-label">Night band</p>
|
|
175
|
+
<h2 id="cta-h">The accent button keeps its contrast at night.</h2>
|
|
176
|
+
<p class="gw-lead">In a night band and in dark mode it takes the dark accent with dark words, also checked at 4.5:1.</p>
|
|
177
|
+
<div class="gw-actions">
|
|
178
|
+
<a class="gw-btn" href="theme-example.html">Open the accent example</a>
|
|
179
|
+
<a class="gw-btn gw-btn--secondary" href="index.html">Open the default demo</a>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</section>
|
|
183
|
+
</main>
|
|
184
|
+
|
|
185
|
+
<footer class="gw-footer">
|
|
186
|
+
<div class="gw-wrap">
|
|
187
|
+
<div class="gw-footer__bottom">
|
|
188
|
+
<span>Website UI kit demo. Sample text.</span>
|
|
189
|
+
<ul aria-label="Demo pages">
|
|
190
|
+
<li><a href="index.html">Default demo</a></li>
|
|
191
|
+
<li><a href="theme-example.html">Accent only</a></li>
|
|
192
|
+
</ul>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</footer>
|
|
196
|
+
|
|
197
|
+
<script src="../js/website.js" defer></script>
|
|
198
|
+
</body>
|
|
199
|
+
</html>
|