@gemboss/website-ui 0.11.0 → 0.12.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/CHANGELOG.md +40 -0
- package/README.md +2 -2
- package/bin/lib/check.mjs +3 -2
- package/css/base.css +23 -18
- package/css/components.css +22 -9
- package/css/forms.css +1 -1
- package/css/header.css +39 -5
- package/css/layout.css +7 -3
- package/css/sections.css +1 -1
- package/css/tokens.css +11 -0
- package/demo/header.html +1 -1
- package/docs/components.md +20 -14
- package/docs/markup-contract.md +10 -6
- package/docs/theming.md +1 -1
- 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 +1 -1
- package/website.css +105 -38
- package/website.min.css +1 -1
package/js/website.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// the export line and the // comments, plus auto-init): edit this file, then run the build. Keep comments on lines of
|
|
7
7
|
// their own, so the build can leave them out.
|
|
8
8
|
|
|
9
|
-
var version = '0.
|
|
9
|
+
var version = '0.12.0';
|
|
10
10
|
var api = { version: version, init: init, destroy: destroy };
|
|
11
11
|
// Page state. Set up by the first init(), never at import: a server can import this file.
|
|
12
12
|
var doc, root, mine, fine, sys, wide, desk, mid, live = [], queued = false;
|
|
@@ -554,19 +554,46 @@ function theme(ctx) {
|
|
|
554
554
|
relabel();
|
|
555
555
|
}
|
|
556
556
|
|
|
557
|
-
// Reveal
|
|
558
|
-
//
|
|
557
|
+
// Reveal (0.12.0, as gemboss.ai; system.json motion.reveal): once, when 12% of it is in view with the bottom 6% of the
|
|
558
|
+
// view taken off. Each .gw-reveal after the first among its .gw-reveal siblings waits 70ms more (at most 380ms), so a
|
|
559
|
+
// grid cascades. A [data-gw-count] number inside counts up from 0 when its reveal shows. Failsafe: all shown 1.6s after
|
|
560
|
+
// the init that found them, or at once without IntersectionObserver or under reduced motion (and then nothing counts).
|
|
561
|
+
var REVEAL = { threshold: 0.12, margin: '0px 0px -6% 0px', stagger: 70, staggerMax: 380, failsafe: 1600, count: 1100 };
|
|
562
|
+
// the number in the element's first words: "$1,200+" counts from 0 to 1,200 with its sign, grouping and decimals kept
|
|
563
|
+
function countUp(el) {
|
|
564
|
+
var n = el.firstChild, m = n && n.nodeType === 3 && /^(\s*\D*?)(\d[\d,]*(?:\.\d+)?)(.*)$/.exec(n.nodeValue), t0;
|
|
565
|
+
if (!m) return;
|
|
566
|
+
var num = m[2].replace(/,/g, ''), dec = (num.split('.')[1] || '').length;
|
|
567
|
+
function fmt(v) { v = v.toFixed(dec); return m[1] + (num !== m[2] ? Number(v).toLocaleString('en-US') : v) + m[3]; }
|
|
568
|
+
n.nodeValue = fmt(0);
|
|
569
|
+
requestAnimationFrame(function step(ts) {
|
|
570
|
+
var p = Math.min((ts - (t0 = t0 || ts)) / REVEAL.count, 1);
|
|
571
|
+
n.nodeValue = fmt(p < 1 ? num * (1 - Math.pow(1 - p, 3)) : +num);
|
|
572
|
+
if (p < 1) requestAnimationFrame(step);
|
|
573
|
+
});
|
|
574
|
+
}
|
|
559
575
|
function reveal(ctx) {
|
|
560
|
-
var els = [], io =
|
|
561
|
-
each(ctx, '.gw-reveal', 'reveal', function (el) {
|
|
562
|
-
|
|
576
|
+
var els = [], io, calm = still.matches;
|
|
577
|
+
each(ctx, '.gw-reveal', 'reveal', function (el) {
|
|
578
|
+
els.push(el);
|
|
579
|
+
return { stop: function () { if (io) io.unobserve(el); el.style.transitionDelay = ''; } };
|
|
580
|
+
});
|
|
581
|
+
function show(el) {
|
|
582
|
+
if (el.classList.contains('is-in')) return;
|
|
583
|
+
el.classList.add('is-in');
|
|
584
|
+
if (!calm) [].forEach.call(el.querySelectorAll('[data-gw-count]'), countUp);
|
|
585
|
+
}
|
|
563
586
|
if (!els.length) return;
|
|
564
|
-
if (!window.IntersectionObserver ||
|
|
587
|
+
if (!window.IntersectionObserver || calm) return els.forEach(show);
|
|
588
|
+
els.forEach(function (el) {
|
|
589
|
+
var i = [].filter.call(el.parentNode.children, function (c) { return c.classList.contains('gw-reveal'); }).indexOf(el);
|
|
590
|
+
if (i > 0) el.style.transitionDelay = Math.min(i * REVEAL.stagger, REVEAL.staggerMax) + 'ms';
|
|
591
|
+
});
|
|
565
592
|
io = new IntersectionObserver(function (list) {
|
|
566
|
-
list.forEach(function (en) { if (en.isIntersecting) { en.target
|
|
567
|
-
}, { threshold:
|
|
593
|
+
list.forEach(function (en) { if (en.isIntersecting) { show(en.target); io.unobserve(en.target); } });
|
|
594
|
+
}, { threshold: REVEAL.threshold, rootMargin: REVEAL.margin });
|
|
568
595
|
els.forEach(function (el) { io.observe(el); });
|
|
569
|
-
setTimeout(
|
|
596
|
+
setTimeout(function () { els.forEach(show); io.disconnect(); }, REVEAL.failsafe);
|
|
570
597
|
}
|
|
571
598
|
|
|
572
599
|
// Header: .is-scrolled past 8px, read once per frame (one scroll listener for every header).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemboss/website-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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/system.json
CHANGED
|
@@ -78,6 +78,16 @@
|
|
|
78
78
|
"dot": "rgba(255, 255, 255, 0.1)",
|
|
79
79
|
"ink": "#ffffff"
|
|
80
80
|
},
|
|
81
|
+
"announce": {
|
|
82
|
+
"$comment": "The announcement bar (0.12.0): one sentence over the header, as gemboss.ai draws it, the same in light and dark. bg its ground, ink its words, line its hairline at the bottom; tag and tag-ink the small pill before the sentence; link the link's words and link-line their underline. A brand sets its own bar here.",
|
|
83
|
+
"bg": "#171a38",
|
|
84
|
+
"ink": "#b9beda",
|
|
85
|
+
"line": "rgba(255, 255, 255, 0.06)",
|
|
86
|
+
"tag": "rgba(122, 132, 255, 0.2)",
|
|
87
|
+
"tag-ink": "#a7aeff",
|
|
88
|
+
"link": "#ffffff",
|
|
89
|
+
"link-line": "rgba(255, 255, 255, 0.28)"
|
|
90
|
+
},
|
|
81
91
|
"roles": {
|
|
82
92
|
"action": "The thing you click: ink, or the brand's accent when the site chose the accent action profile (profiles.action).",
|
|
83
93
|
"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.",
|
|
@@ -85,7 +95,8 @@
|
|
|
85
95
|
"line-control": "The border of a form control (a field, a checkbox, a radio), 0.6.0: it must reach 3:1 on bg, bg-soft and surface, light and dark, where line and line-strong are hairlines that do not. Like the dashboard's --gemboss-field-line.",
|
|
86
96
|
"wash": "The hero wash and illustrations only.",
|
|
87
97
|
"scrim": "The veil over the page while a mega menu is open (0.11.0): a dim and a 4px blur, as gemboss.ai.",
|
|
88
|
-
"stage": "The ground of a mega menu's pictures and nothing else (0.11.0)."
|
|
98
|
+
"stage": "The ground of a mega menu's pictures and nothing else (0.11.0).",
|
|
99
|
+
"announce": "The announcement bar over the header and nothing else (0.12.0)."
|
|
89
100
|
}
|
|
90
101
|
},
|
|
91
102
|
"radius": {
|
|
@@ -113,13 +124,16 @@
|
|
|
113
124
|
"wordmark": "clamp(40px, 9vw, 128px)",
|
|
114
125
|
"$comment": "wordmark (0.7.0): the footer's last line on gemboss.ai, the brand's name, faint, at the end of the page. Decoration only, never a heading. Weight 800 (0.10.0, as gemboss.ai draws it): the one use of 800 in the kit.",
|
|
115
126
|
"weight": 600,
|
|
116
|
-
"tracking": "-0.03em"
|
|
127
|
+
"tracking": "-0.03em",
|
|
128
|
+
"h1_leading": 1.1,
|
|
129
|
+
"$comment_leading": "1.1 and 1.06 (0.12.0, gemboss.ai's): at 1.02 the descenders of a first line touched the capitals of the second (Chris, 15/10/2026).",
|
|
130
|
+
"h2_leading": 1.06
|
|
117
131
|
},
|
|
118
132
|
"heading": { "weight": 650, "tracking": "-0.022em", "line_height": 1.08 },
|
|
119
133
|
"subhead": { "weight": 600 },
|
|
120
134
|
"body": { "size": 18, "line_height": 1.6 },
|
|
121
|
-
"label": { "size": 13, "weight": 500, "case": "sentence", "$comment": "muted, led by the 8px
|
|
122
|
-
"button": { "size": 14, "weight": 600, "case": "sentence" }
|
|
135
|
+
"label": { "size": 13, "weight": 500, "case": "sentence", "$comment": "muted, led by the 8px square in ink (0.12.0, as gemboss.ai; the input --gw-label-mark paints it another colour); never uppercase tracking" },
|
|
136
|
+
"button": { "size": 14, "weight": 600, "case": "sentence", "family": "Inter", "$comment": "family (0.12.0): a button names the brand face alone, as gemboss.ai does. A glyph the face does not carry (the arrow → is not in Inter's web subset) is then drawn by the system's own fallback, the width gemboss.ai's buttons have (with the full stack it is a different face, 1px narrower, and a pair of buttons fits where gemboss.ai wraps it)." }
|
|
123
137
|
},
|
|
124
138
|
"space": {
|
|
125
139
|
"scale": [0, 2, 4, 6, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96, 120],
|
|
@@ -168,6 +182,16 @@
|
|
|
168
182
|
"properties": ["transform", "opacity"],
|
|
169
183
|
"ease": "cubic-bezier(.22,1,.36,1)",
|
|
170
184
|
"reveal_ms": 700,
|
|
185
|
+
"reveal": {
|
|
186
|
+
"$comment": "Scroll reveal (0.12.0, as gemboss.ai): a .gw-reveal rises distance px and fades in over reveal_ms when threshold of it is in view, the bottom margin of the view taken off; each one after the first among its .gw-reveal siblings waits stagger_ms more (at most stagger_max_ms); all are shown failsafe_ms after the script starts. A [data-gw-count] number inside counts up from 0 over count_ms when its reveal shows. The script's numbers are held equal to these by the tests.",
|
|
187
|
+
"distance": 20,
|
|
188
|
+
"threshold": 0.12,
|
|
189
|
+
"margin": "0px 0px -6% 0px",
|
|
190
|
+
"stagger_ms": 70,
|
|
191
|
+
"stagger_max_ms": 380,
|
|
192
|
+
"failsafe_ms": 1600,
|
|
193
|
+
"count_ms": 1100
|
|
194
|
+
},
|
|
171
195
|
"spin_ms": 700,
|
|
172
196
|
"$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). One exception, the spotlight (spot): its light is one painted layer of one element, so its radius may grow.",
|
|
173
197
|
"spot": {
|
package/templates/header.html
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
the panel's (--gw-mega-w, --gw-mega-pv; 1000 and 320 when it says nothing), its columns too (--gw-mega-cols, 3).
|
|
12
12
|
Limits: at most five top-level items, one .gw-btn (the primary action), one second text action (.gw-header__link).
|
|
13
13
|
Each panel: two or three columns of four or five rows, at most one card, three links in its strip. The announcement
|
|
14
|
-
bar is optional: one sentence.
|
|
14
|
+
bar is optional: one sentence, one link, a small pill before it if you like (.gw-announce__tag, one word); its
|
|
15
|
+
colours are the brand's (system.json color.announce), the same in light and dark.
|
|
15
16
|
Two options (0.8.0):
|
|
16
17
|
- The mobile menu drills down (data-gw-menu="drill" on #gw-mobile-menu, as gemboss.ai's phone menu): the top level
|
|
17
18
|
lists the sections, a tap on one shows its level alone with Back and its title, and the actions stay in a bar at
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
never be translated. Put the word for "Language" in the page's language in data-gw-label-language.
|
|
26
27
|
Replace every [bracketed] placeholder; delete what you do not use.
|
|
27
28
|
-->
|
|
28
|
-
<div class="gw-announce" role="region" aria-label="Announcement">[One sentence of news.] <a href="[link]">[Where the link goes]</a></div>
|
|
29
|
+
<div class="gw-announce" role="region" aria-label="Announcement"><span class="gw-announce__tag">[New]</span> [One sentence of news.] <a href="[link]">[Where the link goes]</a></div>
|
|
29
30
|
<header class="gw-header" data-gw-header>
|
|
30
31
|
<div class="gw-header__bar">
|
|
31
32
|
<a class="gw-brand" href="/"><img class="gw-brand__mark" src="[/logo.svg]" alt="" width="34" height="34"><span class="gw-brand__name">[Brand]</span></a>
|
package/templates/page.html
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
<body class="gw-page">
|
|
27
27
|
<a class="gw-skip" href="#main">Skip to content</a>
|
|
28
28
|
|
|
29
|
-
<div class="gw-announce" role="region" aria-label="Announcement">[One sentence of news.] <a href="[link]">[Where the link goes]</a></div>
|
|
29
|
+
<div class="gw-announce" role="region" aria-label="Announcement"><span class="gw-announce__tag">[New]</span> [One sentence of news.] <a href="[link]">[Where the link goes]</a></div>
|
|
30
30
|
<header class="gw-header" data-gw-header>
|
|
31
31
|
<div class="gw-header__bar">
|
|
32
32
|
<a class="gw-brand" href="/"><img class="gw-brand__mark" src="[/logo.svg]" alt="" width="34" height="34"><span class="gw-brand__name">[Brand]</span></a>
|
package/website.css
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
/*! @gemboss/website-ui 0.
|
|
1
|
+
/*! @gemboss/website-ui 0.12.0 | generated by scripts/build.mjs from the files in css/. Edit those and not this file */
|
|
2
2
|
/* @gemboss/website-ui tokens. GENERATED from system.json by scripts/build.mjs: do not edit by hand. */
|
|
3
3
|
:root {
|
|
4
4
|
color-scheme: light;
|
|
5
5
|
--gw-font: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
6
6
|
--gw-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
7
|
+
--gw-font-action: Inter;
|
|
7
8
|
--gw-text-11: 11px;
|
|
8
9
|
--gw-text-12: 12px;
|
|
9
10
|
--gw-text-13: 13px;
|
|
@@ -19,6 +20,8 @@
|
|
|
19
20
|
--gw-display-wordmark: clamp(40px, 9vw, 128px);
|
|
20
21
|
--gw-display-weight: 600;
|
|
21
22
|
--gw-display-tracking: -0.03em;
|
|
23
|
+
--gw-display-h1-leading: 1.1;
|
|
24
|
+
--gw-display-h2-leading: 1.06;
|
|
22
25
|
--gw-heading-weight: 650;
|
|
23
26
|
--gw-heading-tracking: -0.022em;
|
|
24
27
|
--gw-heading-leading: 1.08;
|
|
@@ -71,6 +74,7 @@
|
|
|
71
74
|
--gw-tap-min: 44px;
|
|
72
75
|
--gw-ease: cubic-bezier(.22,1,.36,1);
|
|
73
76
|
--gw-reveal-ms: 700ms;
|
|
77
|
+
--gw-reveal-distance: 20px;
|
|
74
78
|
--gw-spin-ms: 700ms;
|
|
75
79
|
--gw-spot-radius: 260px;
|
|
76
80
|
--gw-spot-radius-action: 120px;
|
|
@@ -89,6 +93,13 @@
|
|
|
89
93
|
--gw-stage-bottom: #0d0f2e;
|
|
90
94
|
--gw-stage-dot: rgba(255, 255, 255, 0.1);
|
|
91
95
|
--gw-stage-ink: #ffffff;
|
|
96
|
+
--gw-announce-bg: #171a38;
|
|
97
|
+
--gw-announce-ink: #b9beda;
|
|
98
|
+
--gw-announce-line: rgba(255, 255, 255, 0.06);
|
|
99
|
+
--gw-announce-tag: rgba(122, 132, 255, 0.2);
|
|
100
|
+
--gw-announce-tag-ink: #a7aeff;
|
|
101
|
+
--gw-announce-link: #ffffff;
|
|
102
|
+
--gw-announce-link-line: rgba(255, 255, 255, 0.28);
|
|
92
103
|
/* accent primitives: the only colours a brand overrides (docs/theming.md) */
|
|
93
104
|
--gw-accent-light: #5661f5;
|
|
94
105
|
--gw-accent-strong-light: #3a44de;
|
|
@@ -305,33 +316,34 @@ h1, h2, h3, h4, h5, h6, .gw-h1, .gw-h2, .gw-h3, .gw-h4 {
|
|
|
305
316
|
text-wrap: balance;
|
|
306
317
|
}
|
|
307
318
|
h1, .gw-h1, h2, .gw-h2 { font-weight: var(--gw-display-weight); letter-spacing: var(--gw-display-tracking); }
|
|
308
|
-
h1, .gw-h1 { font-size: var(--gw-display-h1); }
|
|
309
|
-
h2, .gw-h2 { font-size: var(--gw-display-h2); }
|
|
310
|
-
h3,
|
|
319
|
+
h1, .gw-h1 { font-size: var(--gw-display-h1); line-height: var(--gw-display-h1-leading); }
|
|
320
|
+
h2, .gw-h2 { font-size: var(--gw-display-h2); line-height: var(--gw-display-h2-leading); }
|
|
321
|
+
/* h3 (0.12.0): 20px/600, as every h3 on gemboss.ai */
|
|
322
|
+
h3, .gw-h3 { font-size: 20px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.2; }
|
|
311
323
|
h4, .gw-h4 { font-size: 20px; font-weight: 600; letter-spacing: -0.01em; }
|
|
312
324
|
h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
313
325
|
|
|
314
|
-
.gw-lead { font-size: 18px; line-height: 1.55; color: var(--gw-muted); max-width:
|
|
326
|
+
.gw-lead { font-size: 18px; line-height: 1.55; color: var(--gw-muted); max-width: 58ch; }
|
|
315
327
|
.gw-note { font-size: 14px; color: var(--gw-muted); }
|
|
316
328
|
.gw-dim { color: var(--gw-muted); }
|
|
317
329
|
|
|
318
|
-
/* the label above a heading: sentence case, muted, led by the 8px
|
|
330
|
+
/* the label above a heading: sentence case, muted, led by the 8px square, ink by default (0.12.0, as gemboss.ai). The
|
|
331
|
+
input --gw-label-mark paints the square another colour (a product's own, say): set it on the label or on anything
|
|
332
|
+
around it, e.g. style="--gw-label-mark: var(--gw-accent)". */
|
|
319
333
|
.gw-label {
|
|
320
334
|
display: inline-flex;
|
|
321
335
|
align-items: center;
|
|
322
336
|
gap: 8px;
|
|
323
337
|
font-size: 13px;
|
|
324
338
|
font-weight: 500;
|
|
325
|
-
line-height: 1.4;
|
|
326
339
|
color: var(--gw-muted);
|
|
327
340
|
}
|
|
328
341
|
.gw-label::before {
|
|
329
342
|
content: "";
|
|
330
|
-
flex: none;
|
|
331
343
|
width: 8px;
|
|
332
344
|
height: 8px;
|
|
333
|
-
border-radius: var(--gw-radius-
|
|
334
|
-
background: var(--gw-
|
|
345
|
+
border-radius: var(--gw-radius-2);
|
|
346
|
+
background: var(--gw-label-mark, var(--gw-ink));
|
|
335
347
|
}
|
|
336
348
|
|
|
337
349
|
/* links inside running text (and anywhere in .gw-prose) get a visible link style; everything else opts in with .gw-link */
|
|
@@ -351,19 +363,22 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
351
363
|
:focus-visible { border-radius: var(--gw-radius-4); outline: 2px solid var(--gw-accent); outline-offset: 3px; }
|
|
352
364
|
[id] { scroll-margin-top: calc(var(--gw-header-height) + 16px); }
|
|
353
365
|
|
|
366
|
+
/* the skip link (0.12.0, gemboss.ai's): the accent tab in the top left corner, shown while it has the focus */
|
|
354
367
|
.gw-skip {
|
|
355
368
|
position: absolute;
|
|
356
369
|
left: -9999px;
|
|
357
|
-
top:
|
|
358
|
-
z-index:
|
|
359
|
-
padding: 12px
|
|
360
|
-
border-radius: var(--gw-radius-
|
|
361
|
-
background: var(--gw-
|
|
362
|
-
color: var(--gw-on-
|
|
370
|
+
top: 0;
|
|
371
|
+
z-index: 200;
|
|
372
|
+
padding: 12px 20px;
|
|
373
|
+
border-radius: 0 0 var(--gw-radius-card) 0;
|
|
374
|
+
background: var(--gw-accent);
|
|
375
|
+
color: var(--gw-on-accent);
|
|
376
|
+
box-shadow: var(--gw-shadow);
|
|
363
377
|
font-size: 14px;
|
|
364
378
|
font-weight: 600;
|
|
365
379
|
}
|
|
366
|
-
|
|
380
|
+
/* its corner again with the focus: a page's own :focus-visible corners, loaded after the kit, must not round the tab */
|
|
381
|
+
.gw-skip:focus { left: 0; border-radius: 0 0 var(--gw-radius-card) 0; }
|
|
367
382
|
|
|
368
383
|
/* words for a screen reader only (0.7.0): " (opens in a new tab)" in a link with target="_blank" */
|
|
369
384
|
.gw-visually-hidden {
|
|
@@ -377,10 +392,11 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
377
392
|
border: 0;
|
|
378
393
|
}
|
|
379
394
|
|
|
380
|
-
/* scroll reveal: the script adds .is-in
|
|
395
|
+
/* scroll reveal: the script adds .is-in (system.json motion.reveal: when, the stagger, the failsafe); the animation is
|
|
396
|
+
a second failsafe, for a page whose script never ran, that shows it anyway after 2.5s */
|
|
381
397
|
.gw-reveal {
|
|
382
398
|
opacity: 0;
|
|
383
|
-
transform: translateY(
|
|
399
|
+
transform: translateY(var(--gw-reveal-distance));
|
|
384
400
|
transition: opacity var(--gw-reveal-ms) var(--gw-ease), transform var(--gw-reveal-ms) var(--gw-ease);
|
|
385
401
|
animation: gw-reveal-failsafe 1ms linear 2.5s forwards;
|
|
386
402
|
}
|
|
@@ -406,15 +422,19 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
406
422
|
@media (min-width: 768px) { .gw-section--tight { padding-block: 64px; } }
|
|
407
423
|
|
|
408
424
|
/* label + h2 + one or two lines; the vertical flow of every text block in the kit */
|
|
409
|
-
.gw-section__head { max-width:
|
|
425
|
+
.gw-section__head { max-width: 760px; }
|
|
410
426
|
.gw-section__head--center { margin-inline: auto; text-align: center; }
|
|
411
427
|
.gw-section__head--center .gw-lead { margin-inline: auto; }
|
|
412
|
-
.
|
|
413
|
-
|
|
428
|
+
/* what follows a head starts 40px under it, 48px from 768px (0.12.0): a top margin on the next thing, at no
|
|
429
|
+
specificity, so a block that brings its own (a FAQ's 40px, say) keeps it */
|
|
430
|
+
:where(.gw-section__head) + * { margin-top: 40px; }
|
|
431
|
+
@media (min-width: 768px) { :where(.gw-section__head) + * { margin-top: 48px; } }
|
|
414
432
|
|
|
415
433
|
:where(.gw-section__head, .gw-hero > .gw-wrap, .gw-split > :not(.gw-window), .gw-cta, .gw-card, .gw-feature, .gw-plan) > * + * { margin-top: 16px; }
|
|
416
434
|
: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; }
|
|
417
435
|
: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; }
|
|
436
|
+
/* a lead under a heading: 20px (0.12.0, as gemboss.ai); in a hero it keeps the 16px of the flow */
|
|
437
|
+
:where(.gw-section__head, .gw-split > :not(.gw-window), .gw-cta) > :where(* + .gw-lead) { margin-top: 20px; }
|
|
418
438
|
|
|
419
439
|
/* a row of actions: one .gw-btn, then a secondary or a link */
|
|
420
440
|
.gw-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
|
|
@@ -456,12 +476,12 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
456
476
|
|
|
457
477
|
/* actions are rectangles: 40px, the action radius of the site's profile (8, 12 in soft), 14px/600, sentence case.
|
|
458
478
|
The fill is the action colour: ink, or the brand's accent under data-gw-action="accent" (tokens.css). Never a
|
|
459
|
-
gradient
|
|
479
|
+
gradient or a wash hue. On hover (0.12.0, as gemboss.ai) the filled button rises 1px onto the rest shadow; the framed
|
|
480
|
+
and frameless ones only change their frame or tint. Phones: see "touch" below. */
|
|
460
481
|
.gw-btn {
|
|
461
482
|
position: relative;
|
|
462
483
|
display: inline-flex;
|
|
463
484
|
align-items: center;
|
|
464
|
-
justify-content: center;
|
|
465
485
|
gap: 8px;
|
|
466
486
|
max-width: 100%;
|
|
467
487
|
height: var(--gw-button-height);
|
|
@@ -470,26 +490,34 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
470
490
|
border-radius: var(--gw-radius-action);
|
|
471
491
|
background: var(--gw-action);
|
|
472
492
|
color: var(--gw-on-action);
|
|
493
|
+
font-family: var(--gw-font-action);
|
|
473
494
|
font-size: 14px;
|
|
474
495
|
font-weight: 600;
|
|
475
|
-
line-height: 1.4;
|
|
476
496
|
letter-spacing: 0;
|
|
477
497
|
text-align: center;
|
|
478
498
|
white-space: nowrap;
|
|
479
499
|
cursor: pointer;
|
|
500
|
+
transition: background 0.15s ease, border-color 0.15s ease;
|
|
480
501
|
}
|
|
481
|
-
.gw-btn:hover {
|
|
502
|
+
.gw-btn:hover { transform: translateY(-1px); box-shadow: var(--gw-shadow); }
|
|
482
503
|
.gw-btn svg { flex: none; width: 16px; height: 16px; }
|
|
483
504
|
.gw-btn--secondary { background: transparent; border-color: var(--gw-line-strong); color: var(--gw-ink); }
|
|
484
505
|
.gw-btn--secondary:hover { background: transparent; border-color: var(--gw-ink); }
|
|
485
506
|
.gw-btn--ghost { background: transparent; color: var(--gw-ink); }
|
|
486
507
|
.gw-btn--ghost:hover { background: var(--gw-bg-soft); }
|
|
508
|
+
.gw-btn:is(.gw-btn--secondary, .gw-btn--ghost):hover { transform: none; box-shadow: none; }
|
|
509
|
+
/* an arrow after the label (→), nudged 3px on hover */
|
|
510
|
+
.gw-btn__arrow { letter-spacing: 0; transition: transform 0.18s ease; }
|
|
511
|
+
.gw-btn:hover .gw-btn__arrow { transform: translateX(3px); }
|
|
512
|
+
|
|
487
513
|
.gw-btn--on-night { background: var(--gw-on-night); color: var(--gw-night); }
|
|
488
514
|
.gw-btn--on-night:hover { background: var(--gw-on-night); opacity: 0.88; }
|
|
489
515
|
.gw-btn--sm { height: 36px; padding: 0 16px; }
|
|
490
516
|
.gw-btn--sm::after { content: ""; position: absolute; top: -6px; right: 0; bottom: -6px; left: 0; }
|
|
491
517
|
.gw-btn--lg { height: 48px; padding: 0 24px; font-size: 16px; }
|
|
492
|
-
.
|
|
518
|
+
/* a button wider than its label keeps the label at its start (as gemboss.ai), unless it is made wide on purpose: full
|
|
519
|
+
width (--block), a phone's row of actions, an inline form */
|
|
520
|
+
.gw-btn--block { display: flex; width: 100%; justify-content: center; }
|
|
493
521
|
.gw-btn:disabled, .gw-btn[aria-disabled="true"] { opacity: 0.48; cursor: not-allowed; pointer-events: none; }
|
|
494
522
|
/* pressed: the ink one step darker (the hover ink, as the dashboard's primary), nothing moves; the frameless variants
|
|
495
523
|
take one more step of tint */
|
|
@@ -788,16 +816,21 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
788
816
|
.gw-faq__item[open] > summary::after { transform: rotate(45deg); }
|
|
789
817
|
.gw-faq__item > :not(summary) { margin: 0 0 20px; max-width: 68ch; color: var(--gw-muted); font-size: 16px; }
|
|
790
818
|
|
|
791
|
-
/* touch: every control is at least 44px tall,
|
|
792
|
-
|
|
819
|
+
/* touch: every control is at least 44px tall. A button up to 768px (0.12.0, as gemboss.ai: by the width, not the
|
|
820
|
+
pointer), its label wrapping (and tighter) from 480px down in the page's <main>, so a long one never pushes the page
|
|
821
|
+
sideways (a header or an overlay outside <main> keeps its one line) */
|
|
822
|
+
@media (max-width: 768px) {
|
|
793
823
|
.gw-btn, .gw-btn--sm, .gw-btn--lg {
|
|
794
824
|
height: auto;
|
|
795
825
|
min-height: var(--gw-tap-min);
|
|
796
826
|
padding-block: 8px;
|
|
797
|
-
white-space: normal;
|
|
798
|
-
text-wrap: balance;
|
|
799
827
|
}
|
|
800
828
|
.gw-btn--lg { min-height: 48px; }
|
|
829
|
+
}
|
|
830
|
+
@media (max-width: 480px) {
|
|
831
|
+
:where(main) .gw-btn { white-space: normal; line-height: 1.35; }
|
|
832
|
+
}
|
|
833
|
+
@media (max-width: 768px), (pointer: coarse) {
|
|
801
834
|
:is(a, button, label).gw-chip { min-height: var(--gw-tap-min); }
|
|
802
835
|
.gw-segmented > button { height: auto; min-height: var(--gw-tap-min); padding-block: 8px; white-space: normal; text-wrap: balance; }
|
|
803
836
|
}
|
|
@@ -805,16 +838,50 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
805
838
|
/* header: announcement, a flat 64px bar with one hairline, mega menu, mobile menu up to 1080px.
|
|
806
839
|
Not a floating pill: no radius, no blur, no shadow on the bar. Nothing tucks under it. A brand may choose the one
|
|
807
840
|
other shape, .gw-header--float (an inset card from 1081px, at the end of this file). */
|
|
841
|
+
/* the announcement bar (0.12.0, gemboss.ai's): one sentence on its own ground over the header, the same in both themes
|
|
842
|
+
(system.json color.announce). A small pill may lead it (.gw-announce__tag); its link is light, underlined by a hairline
|
|
843
|
+
that brightens on hover. From 520px down, 12px words and a slimmer bar. */
|
|
808
844
|
.gw-announce {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
845
|
+
position: relative;
|
|
846
|
+
z-index: 60;
|
|
847
|
+
padding: 12px 16px;
|
|
848
|
+
border-bottom: 1px solid var(--gw-announce-line);
|
|
849
|
+
background: var(--gw-announce-bg);
|
|
850
|
+
color: var(--gw-announce-ink);
|
|
813
851
|
font-size: 13px;
|
|
852
|
+
font-weight: 500;
|
|
814
853
|
line-height: 1.5;
|
|
854
|
+
letter-spacing: 0.005em;
|
|
815
855
|
text-align: center;
|
|
816
856
|
}
|
|
817
|
-
.
|
|
857
|
+
/* 2.5px above and below, as gemboss.ai draws the pill (21.5px tall): the one half pixel in the kit */
|
|
858
|
+
.gw-announce__tag {
|
|
859
|
+
display: inline-block;
|
|
860
|
+
margin-right: 12px;
|
|
861
|
+
padding: 2.5px 8px;
|
|
862
|
+
border-radius: var(--gw-radius-chip);
|
|
863
|
+
background: var(--gw-announce-tag);
|
|
864
|
+
color: var(--gw-announce-tag-ink);
|
|
865
|
+
font-size: 11px;
|
|
866
|
+
font-weight: 700;
|
|
867
|
+
letter-spacing: 0.09em;
|
|
868
|
+
vertical-align: 1px;
|
|
869
|
+
}
|
|
870
|
+
.gw-announce a {
|
|
871
|
+
margin-left: 8px;
|
|
872
|
+
padding-bottom: 2px;
|
|
873
|
+
border-bottom: 1px solid var(--gw-announce-link-line);
|
|
874
|
+
color: var(--gw-announce-link);
|
|
875
|
+
font-weight: 600;
|
|
876
|
+
text-decoration: none;
|
|
877
|
+
white-space: nowrap;
|
|
878
|
+
transition: border-color 0.18s;
|
|
879
|
+
}
|
|
880
|
+
.gw-announce a:hover { border-bottom-color: var(--gw-announce-link); }
|
|
881
|
+
@media (max-width: 520px) {
|
|
882
|
+
.gw-announce { padding: 8px 12px; font-size: 12px; }
|
|
883
|
+
.gw-announce__tag { margin-right: 6px; }
|
|
884
|
+
}
|
|
818
885
|
|
|
819
886
|
.gw-header {
|
|
820
887
|
position: sticky;
|
|
@@ -1753,7 +1820,7 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
|
|
|
1753
1820
|
.gw-hero > .gw-wrap > .gw-window { margin-top: 64px; }
|
|
1754
1821
|
}
|
|
1755
1822
|
@media (max-width: 479px) {
|
|
1756
|
-
:is(.gw-hero, .gw-cta) .gw-actions > .gw-btn { flex: 1 1 100%; }
|
|
1823
|
+
:is(.gw-hero, .gw-cta) .gw-actions > .gw-btn { flex: 1 1 100%; justify-content: center; }
|
|
1757
1824
|
}
|
|
1758
1825
|
|
|
1759
1826
|
/* logos: a proof strip of real logos or wordmarks */
|
|
@@ -2407,7 +2474,7 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
|
|
|
2407
2474
|
room; below 480px, and in a box too narrow for both, the field and the button each take a full line. */
|
|
2408
2475
|
.gw-form-inline { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
2409
2476
|
.gw-form-inline > :not(.gw-btn) { flex: 999 1 160px; min-width: 0; }
|
|
2410
|
-
.gw-form-inline > .gw-btn { flex: 1 0 auto; }
|
|
2477
|
+
.gw-form-inline > .gw-btn { flex: 1 0 auto; justify-content: center; }
|
|
2411
2478
|
@media (max-width: 479px) {
|
|
2412
2479
|
.gw-form-inline > :not(.gw-btn) { flex-basis: 100%; }
|
|
2413
2480
|
}
|