@gemboss/website-ui 0.9.0 → 0.11.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 +109 -0
- package/README.md +2 -2
- package/bin/lib/check.mjs +6 -2
- package/css/base.css +2 -1
- package/css/effects.css +1 -1
- package/css/footer.css +74 -58
- package/css/header.css +347 -115
- package/css/tokens.css +24 -5
- package/demo/article.html +58 -70
- package/demo/blog.html +58 -70
- package/demo/brand-profiles.html +57 -27
- package/demo/footer.html +59 -71
- package/demo/forms.html +58 -70
- package/demo/header.html +58 -108
- package/demo/index.html +58 -70
- package/demo/spotlight.html +58 -71
- package/demo/theme-example.html +1 -1
- package/docs/components.md +31 -21
- package/docs/markup-contract.md +53 -22
- package/js/website.js +285 -42
- package/js/website.mjs +319 -47
- package/package.json +1 -1
- package/system.json +23 -8
- package/templates/footer.html +11 -9
- package/templates/header.html +47 -24
- package/templates/page.html +54 -20
- package/website.css +449 -181
- package/website.min.css +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,115 @@ Every release lists what changed for a site that uses the kit. A release that ad
|
|
|
4
4
|
says so with `Closes gap #N` (the GitHub issue number), so `npx gemboss-website upgrade` can tell that site which
|
|
5
5
|
stopgap (`kitgap-*`) it can now delete.
|
|
6
6
|
|
|
7
|
+
## 0.11.0 (2026-10-26)
|
|
8
|
+
|
|
9
|
+
The header, 1:1 with gemboss.ai. Since the footer (0.10.0), the header was the last part of gemboss.ai not drawn by
|
|
10
|
+
the kit, and the kit's header was a simpler one: a narrower panel with icons, a button for each section, no pictures,
|
|
11
|
+
nothing behind. Chris asked for it identical, with nothing missed, and for every value to live in the kit so that
|
|
12
|
+
GemPages, Gemians and GemX inherit it (26/10/2026). 0.11.0 takes every value and every motion from gemboss.ai's own
|
|
13
|
+
header and was measured against it in Chromium, pixel by pixel: the bar at 320 to 1920px, every menu open at 1081,
|
|
14
|
+
1366, 1440 and 1920px, rows hovered (their previews), the pill under a hovered name, the keyboard's focus, the phone
|
|
15
|
+
menu's top level, each level and each level's end at 390, 768 and 1080px, light and dark, on pages in each section.
|
|
16
|
+
All of it is identical but one thing, on purpose: on the phone, gemboss.ai's own menu gives "Start free" a light
|
|
17
|
+
bottom hairline and takes "Book a demo"'s away (a rule meant for its menu's rows reaches them); the kit draws the two
|
|
18
|
+
buttons whole.
|
|
19
|
+
|
|
20
|
+
A minor version: a 0.10.0 header changes to these values, and its mega menu's markup changes (below).
|
|
21
|
+
|
|
22
|
+
- The bar: the page's wrap + 12px (1132px on GemBoss), 24px inside (6px up to 640px), its own width whatever order the
|
|
23
|
+
stylesheets load in (`.gw-header > .gw-header__bar`; drop `.gw-wrap` from it). The logo 34px with 8px corners (was
|
|
24
|
+
26px), a 44px row up to 768px. The menu's words 0.005em tighter, 38px tall (8px x 12px, 1.6 lines); the current page
|
|
25
|
+
or section in the accent's strong ink, weight 600 (was ink with an underline; a section's name carries
|
|
26
|
+
`aria-current`). Actions 4px apart: Sign in from 1081px, a second text action `.gw-header__link` (Book a demo) from
|
|
27
|
+
1181px, the button from 561px, 8px further out. The menu button 42px (44px on touch) with one strong hairline on
|
|
28
|
+
the surface, the card radius, ink while open; its icons gemboss.ai's. The phone menu runs to 1080px, the desktop
|
|
29
|
+
starts at 1081px (was 1080px), in the CSS and in `website.js`.
|
|
30
|
+
- The mega menu, gemboss.ai's own (its v5): a section's name is a link to its page (`a.gw-nav__trigger` with a 12px
|
|
31
|
+
`.gw-nav__chev`) that opens its panel after the pointer rests 200ms (90ms to switch, 350ms to close; the keyboard's
|
|
32
|
+
focus at once; ArrowDown steps into the first row). One surface morphs to the open panel's size and place, the
|
|
33
|
+
panels crossfade on it, a caret points at the name, a pill glides under the hovered name, a highlight under the
|
|
34
|
+
hovered row, the page behind dims and blurs (`.gw-scrim`, `--gw-scrim`). A panel: `.gw-mega__body` (columns of
|
|
35
|
+
rows, then `.gw-mega__aside`, the pictures) and an optional `.gw-mega__foot` strip; its widths and columns are its
|
|
36
|
+
markup's (`--gw-mega-w`, `--gw-mega-pv`, `--gw-mega-cols`). Pictures sit on the Stage (`.gw-mega__art`, new tokens
|
|
37
|
+
`--gw-stage-*`): the panel's own `.gw-mega__card`, and a row's preview, the `<template>` its `data-gw-preview` names
|
|
38
|
+
(`"feature"`: the panel's card). A card's pictures load the first time it shows (`data-gw-bg`,
|
|
39
|
+
`template[data-gw-hydrate]`, `.is-hydrated`). A picture straight on the Stage (an `<img>`) fills it.
|
|
40
|
+
- Moving a 0.10.0 header: the panel's `.gw-wrap.gw-mega__grid` becomes `.gw-mega__body > .gw-mega__grid`, the
|
|
41
|
+
`.gw-mega__feature` card becomes a `.gw-mega__card` in a `.gw-mega__aside` (its words in `.gw-mega__card-text`, a
|
|
42
|
+
picture in `.gw-mega__art`), the rows' `.gw-mega__icon` is not drawn on desktop. A `<button>` name still works: a
|
|
43
|
+
click opens its panel and keeps it open, a second click closes it. `templates/header.html` has the new markup.
|
|
44
|
+
- The script: the mega menu is wired per header (the `mega` mark is on the `.gw-header` now, not on each name);
|
|
45
|
+
`gw:mega` fires on the name. One panel open on the page, across headers. `destroy` takes the surface, caret, pill,
|
|
46
|
+
scrim and previews away and hides the panels again.
|
|
47
|
+
- The phone menu (the drill-down): 24px in from the edges, its links set at 18px, weight 500, 0.005em tighter, its
|
|
48
|
+
heads and headings on 1.6 lines; a row under the pointer or the keyboard takes the soft ground, 2px to the right, its
|
|
49
|
+
icon tile in the accent; a row may carry a 48x32 picture; a level may end with its panel's card and "All of … →"
|
|
50
|
+
(the arrow after a space); the action bar's buttons 8px apart, 16px words; `.gw-mobile-menu__link--muted`, a
|
|
51
|
+
quieter link before Sign in (What's new). The current page is not marked on the phone.
|
|
52
|
+
- Tokens, gemboss.ai's: the dark theme's shadows (`shadow.dark`: a 1px ledge, a deeper raised shadow; the kit kept
|
|
53
|
+
the light ones in dark before), `--gw-shadow-lift` (an object on the Stage), `--gw-stage-*` (glow, glow-2, top,
|
|
54
|
+
bottom, dot, ink), `--gw-scrim` light and dark, `--gw-radius-2` (the caret's corner), the dark theme's primary
|
|
55
|
+
button `#e3e4e8` (gemboss.ai's ink, was `#f2f3f5`). The focus ring 3px out with 4px corners (was 2px out).
|
|
56
|
+
- `check`: a mega panel's `--gw-mega-w`, `--gw-mega-pv`, `--gw-mega-cols` are inputs set in the markup, not tokens;
|
|
57
|
+
every kit shadow (dark ones and the lift included) is a token shadow.
|
|
58
|
+
- Budget: `js/website.js` is 34 KB, 9.9 KB gzipped (0.10.0: 23.1 KB, 7.1 KB); the test's budget is now 36 KB and
|
|
59
|
+
10 KB.
|
|
60
|
+
- Tests: `test/run.mjs` holds the header's base rules byte for byte again from this version (their hash renewed once),
|
|
61
|
+
the mega menu in its fake DOM (the marks, the drawn parts, hover 200/90/350ms, touch, focus, ArrowDown, the pinned
|
|
62
|
+
button, one open across headers, destroy); `test/browser.mjs` drives the desktop mega menu at 1366px (its timing,
|
|
63
|
+
the surface and caret placement, a row's preview, ArrowDown, Escape, destroy). Demos and templates carry the new
|
|
64
|
+
markup.
|
|
65
|
+
|
|
66
|
+
## 0.10.0 (2026-10-26)
|
|
67
|
+
|
|
68
|
+
The footer, 1:1 with gemboss.ai. 0.7.0 drew the footer "as gemboss.ai has it", but not to the pixel: when gemboss.ai
|
|
69
|
+
moved onto the kit (site PR #76) its footer changed in about a dozen places (the gaps, the bottom row, the logo, the
|
|
70
|
+
wordmark, the widths where the columns fold). Chris asked for it identical, with nothing missed, and for the values to
|
|
71
|
+
live in the kit, so every site that uses it inherits them (26/10/2026). 0.10.0 takes every value from the site's own
|
|
72
|
+
footer before #76 and was measured against it in Chromium: part by part (position, size, type, colour of every word)
|
|
73
|
+
and pixel by pixel, at 14 widths from 320 to 1920px, light and dark, folded and open, every part hovered and focused
|
|
74
|
+
from the keyboard, on every page of the site. All of it is pixel-identical but one thing, on purpose: at 320px
|
|
75
|
+
gemboss.ai squeezed the chevron of its longest row to 7.3px, and the kit keeps it 8px.
|
|
76
|
+
|
|
77
|
+
A minor version: a footer that already uses the kit changes to these values. Nothing to change in its markup.
|
|
78
|
+
|
|
79
|
+
- Widths: the columns fold up to 620px and stand open from 621px (were 767 and 768), in the CSS and in `website.js`
|
|
80
|
+
(`matchMedia('(min-width: 621px)')`); the footer is one row from 1081px (was 1080px); two columns between (was three
|
|
81
|
+
from 768px). The wordmark shows from 621px (was 480px), its gap 16px up to 640px.
|
|
82
|
+
- The footer: 64px above and 32px below (was 40px), no ground of its own (`background: transparent`, was `--gw-bg`), 32px
|
|
83
|
+
between columns (24px x 20px up to 620px).
|
|
84
|
+
- The brand block: the mark 34px with 8px corners (`--gw-radius-8`), the row 34px tall, as wide as the block (44px on
|
|
85
|
+
touch); its sentence 30 characters a line at most, 22px lines (was 320px wide). The block keeps its width beside the
|
|
86
|
+
Ask AI row; the 0.7.0 rules that widened it are gone.
|
|
87
|
+
- Headings: 12px over their links (was 8px), 1.6 line height, balanced, whatever the page's `h2` rule says (a site's
|
|
88
|
+
`h2` gave them its display face, and balance on some pages, not on others).
|
|
89
|
+
- The face: the brand's name, the headings and the wordmark are set in `--gw-font` whatever the page's body says (a
|
|
90
|
+
page in Chinese puts PingFang first; gemboss.ai keeps Inter for these three). A brand sets its face with the token.
|
|
91
|
+
- Links in a column: 44px rows up to 768px and on touch (gemboss.ai's), 20px lines.
|
|
92
|
+
- The bottom row: 32px under the columns (was 48px), 12px x 24px between its parts, 16px between its links (8px x 16px on
|
|
93
|
+
touch); no 24px minimum size on a desktop link; a social link at least 44px wide on touch.
|
|
94
|
+
- Folds up to 620px: each column's hairline on top, the last column's and each family row's below; a 52px row without
|
|
95
|
+
padding (was 44px padded to 52px); the chevron is two 2px borders, 8px, turned 45deg and 225deg open, at once (it
|
|
96
|
+
eased 0.18s); a focused row draws 2px of accent 2px out with 6px corners.
|
|
97
|
+
- Family rows: four items a line from 621px, 44px items from 621 to 768px, the list pulled 12px left only from 1081px;
|
|
98
|
+
each item fills its cell.
|
|
99
|
+
- `.gw-footer__ext` takes the glyph: `<span class="gw-footer__ext" aria-hidden="true">↗</span>`, set muted at weight
|
|
100
|
+
400, as gemboss.ai writes it. An empty one is still drawn (the 12px mask).
|
|
101
|
+
- The Ask AI label has 22px lines; a hovered Ask AI link takes the browser's own underline (was 1px, 4px under).
|
|
102
|
+
- The theme toggle: on hover its hairline turns ink and its icon the accent, its ground stays (colour only, 0.15s; was
|
|
103
|
+
the soft ground). Two icons: `.gw-theme-toggle__moon` shows in light, `.gw-theme-toggle__sun` in dark (a new section
|
|
104
|
+
at the end of `header.css`, for any `.gw-theme-toggle`); a toggle with one plain `<svg>` stays as it was.
|
|
105
|
+
- The wordmark: weight 800 (added to `type.weights`, its one use), 48px above and nothing under but the footer's 32px
|
|
106
|
+
(was 24px), the mark 7vw wide (40 to 96px; was 0.9em tall). Its `<svg>` may carry `width` and `height` for its shape:
|
|
107
|
+
gemboss.ai's gem says `width="86" height="100"`.
|
|
108
|
+
- Token: light `--gw-muted` is `#6a6d73` (was `#686b71`), gemboss.ai's: 5.18:1 on white, 4.67:1 on the soft ground
|
|
109
|
+
(were 5.34 and 4.81), both above 4.5:1.
|
|
110
|
+
- `templates/footer.html` follows: the ↗ glyph, the two toggle icons, a 34px mark, the new widths in its notes.
|
|
111
|
+
- Docs: `components.md` (Footer) and `markup-contract.md` (the folds' widths, the ↗, the toggle's icons, the
|
|
112
|
+
wordmark's mark).
|
|
113
|
+
- Tests: `test/run.mjs` holds the base rules byte for byte again from this version (their hash, renewed once on purpose)
|
|
114
|
+
and pins the values above; the fold tests read 620/621px.
|
|
115
|
+
|
|
7
116
|
## 0.9.0 (2026-10-25)
|
|
8
117
|
|
|
9
118
|
The spotlight, a named hover effect for the things you click (Chris, 25/10/2026, chosen over a heading that glows
|
package/README.md
CHANGED
|
@@ -40,9 +40,9 @@ nó đọc `AGENTS.md` của kit trước, dựng từ mẫu, và chạy `check`
|
|
|
40
40
|
**Site HTML tĩnh hoặc không có bước build:** dùng CDN của npm, ghim đúng phiên bản.
|
|
41
41
|
|
|
42
42
|
```html
|
|
43
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gemboss/website-ui@0.
|
|
43
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gemboss/website-ui@0.11.0/website.min.css">
|
|
44
44
|
<link rel="stylesheet" href="/site.css"> <!-- CSS riêng của site, luôn SAU kit -->
|
|
45
|
-
<script src="https://cdn.jsdelivr.net/npm/@gemboss/website-ui@0.
|
|
45
|
+
<script src="https://cdn.jsdelivr.net/npm/@gemboss/website-ui@0.11.0/js/website.js" defer></script>
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
Chèn markup của kit sau khi trang đã tải (block của builder, bản xem trước CMS): gọi `gwWebsite.init(phầnMới)` ngay
|
package/bin/lib/check.mjs
CHANGED
|
@@ -335,7 +335,9 @@ export function subjectOf(sel) {
|
|
|
335
335
|
const ATTR_THEME = String.raw`\[\s*data-theme(?:\s*[~|^$*]?=\s*(?:"[^"]*"|'[^']*'|[\w-]+))?\s*\]`;
|
|
336
336
|
const ROOTISH = new RegExp(`^(?::root|html)?(?:${ATTR_THEME}|:not\\(\\s*${ATTR_THEME}\\s*\\))*$`, "i");
|
|
337
337
|
const isRootish = (sel) => splitTop(sel).every((s) => s && ROOTISH.test(s));
|
|
338
|
-
|
|
338
|
+
// set on one element in the markup, read by the kit's CSS: the stars' value (style="--gw-stars: 4.9"); a mega panel's width,
|
|
339
|
+
// its pictures' width and its columns (0.11.0: style="--gw-mega-w: 1120px; --gw-mega-pv: 380px; --gw-mega-cols: 2")
|
|
340
|
+
const INPUTS = new Set(["--gw-stars", "--gw-mega-w", "--gw-mega-pv", "--gw-mega-cols"]);
|
|
339
341
|
const THEMABLE = /^--gw-(?:accent(?:-[\w-]+)?|on-accent(?:-(?:light|dark))?|wash(?:-[\w-]+)?)$/; // docs/theming.md: the eight accent variables (-light/-dark) + wash
|
|
340
342
|
|
|
341
343
|
const COLOR_PROPS = /^(?:color|background(?:-color|-image)?|border(?:-(?:top|right|bottom|left|block|inline)(?:-(?:start|end))?)?(?:-color)?|outline(?:-color)?|fill|stroke|text-decoration(?:-color)?|caret-color|accent-color|column-rule(?:-color)?|text-emphasis(?:-color)?|-webkit-text-fill-color|-webkit-text-stroke(?:-color)?|scrollbar-color|stop-color|flood-color|lighting-color)$/;
|
|
@@ -361,7 +363,9 @@ const BUTTONISH = /btn|button/i;
|
|
|
361
363
|
const pxNumbers = (v) => [...v.matchAll(/(-?\d*\.?\d+)(px|rem)\b/gi)].map((m) => ({ raw: m[0], px: Math.abs(parseFloat(m[1])) * (m[2].toLowerCase() === "rem" ? 16 : 1), unit: m[2].toLowerCase() }));
|
|
362
364
|
const isFraction = (n) => Math.abs(n - Math.round(n)) > 1e-6;
|
|
363
365
|
const normShadow = (s) => s.toLowerCase().replace(/\b0\./g, ".").replace(/\s*([,()])\s*/g, "$1").replace(/\s+/g, " ").trim();
|
|
364
|
-
|
|
366
|
+
// every shadow the kit draws: rest, sm, raised, the Stage's lift and (0.11.0) the dark theme's own
|
|
367
|
+
const TOKEN_SHADOWS = new Set(Object.entries(SYSTEM.shadow).filter(([k]) => !k.startsWith("$"))
|
|
368
|
+
.flatMap(([, v]) => (typeof v === "string" ? [v] : Object.values(v))).map(normShadow));
|
|
365
369
|
|
|
366
370
|
function okShadowPart(p) {
|
|
367
371
|
const v = p.trim();
|
package/css/base.css
CHANGED
|
@@ -74,7 +74,8 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
74
74
|
:where(p, dd, td, figcaption, .gw-list--check li, .gw-prose) a:not([class]):hover,
|
|
75
75
|
.gw-link:hover { text-decoration-color: currentColor; }
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
/* the focus ring, as gemboss.ai draws it (0.11.0): 2px of accent, 3px out, 4px corners */
|
|
78
|
+
:focus-visible { border-radius: var(--gw-radius-4); outline: 2px solid var(--gw-accent); outline-offset: 3px; }
|
|
78
79
|
[id] { scroll-margin-top: calc(var(--gw-header-height) + 16px); }
|
|
79
80
|
|
|
80
81
|
.gw-skip {
|
package/css/effects.css
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
the build checks the words on. */
|
|
51
51
|
.gw-btn.gw-spot:not([aria-busy="true"])::before { --gw-spot-mix: var(--gw-spot-strength-action); }
|
|
52
52
|
/* the mega menu's feature card is the Stage, a night ground: a stronger light, or it would not show */
|
|
53
|
-
.gw-
|
|
53
|
+
.gw-mega__art.gw-spot:not([aria-busy="true"])::before { --gw-spot-mix: var(--gw-spot-strength-stage); }
|
|
54
54
|
|
|
55
55
|
/* words under the light: lit, muted words are ink-2 at once (rules above); back to muted only once the light has gone
|
|
56
56
|
out (a colour that waits the light's time, no movement). :where(), so a part's own transition wins. */
|
package/css/footer.css
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
/* footer: brand block, link columns, bottom row. One hairline on top, white like the page. */
|
|
2
2
|
.gw-footer {
|
|
3
|
-
padding-block: 64px
|
|
3
|
+
padding-block: 64px 32px;
|
|
4
4
|
border-top: 1px solid var(--gw-line);
|
|
5
|
-
background:
|
|
5
|
+
background: transparent;
|
|
6
6
|
color: var(--gw-ink);
|
|
7
7
|
font-size: 14px;
|
|
8
8
|
}
|
|
9
9
|
.gw-footer__grid {
|
|
10
10
|
display: grid;
|
|
11
11
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
12
|
-
gap: 32px
|
|
13
|
-
}
|
|
14
|
-
.gw-footer__brand {
|
|
15
|
-
.gw-footer__brand
|
|
16
|
-
.gw-
|
|
12
|
+
gap: 32px;
|
|
13
|
+
}
|
|
14
|
+
.gw-footer__brand p { max-width: 30ch; margin-top: 16px; color: var(--gw-muted); line-height: 22px; }
|
|
15
|
+
.gw-footer__brand .gw-brand { display: flex; min-height: 34px; font-family: var(--gw-font); line-height: 1.6; }
|
|
16
|
+
.gw-footer__brand .gw-brand__mark { width: 34px; height: 34px; border-radius: var(--gw-radius-8); }
|
|
17
|
+
/* the brand's name, the column headings and the wordmark are set in the kit's face (--gw-font), whatever the page
|
|
18
|
+
does to its body or its h2 (a page in Chinese puts PingFang first; gemboss.ai keeps Inter for these three); a
|
|
19
|
+
heading is balanced, like gemboss.ai's main pages */
|
|
20
|
+
.gw-footer__heading { margin: 0 0 12px; color: var(--gw-ink); font-family: var(--gw-font); font-size: 13px; font-weight: 600; letter-spacing: 0; line-height: 1.6; text-wrap: balance; }
|
|
17
21
|
/* an optional newsletter form in the brand block (0.6.0): label, the inline row (field and button), a note on what the
|
|
18
22
|
reader agrees to; only when the form is wired to a list that keeps the address */
|
|
19
23
|
.gw-footer__newsletter { margin-top: 24px; }
|
|
@@ -26,33 +30,35 @@
|
|
|
26
30
|
flex-wrap: wrap;
|
|
27
31
|
align-items: center;
|
|
28
32
|
justify-content: space-between;
|
|
29
|
-
gap:
|
|
30
|
-
margin-top:
|
|
33
|
+
gap: 12px 24px;
|
|
34
|
+
margin-top: 32px;
|
|
31
35
|
padding-top: 24px;
|
|
32
36
|
border-top: 1px solid var(--gw-line);
|
|
33
37
|
color: var(--gw-muted);
|
|
34
38
|
font-size: 13px;
|
|
35
39
|
}
|
|
36
40
|
.gw-footer__bottom:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
|
|
37
|
-
.gw-footer__bottom ul { display: flex; flex-wrap: wrap; gap:
|
|
41
|
+
.gw-footer__bottom ul { display: flex; flex-wrap: wrap; gap: 16px; margin: 0; padding: 0; list-style: none; }
|
|
38
42
|
.gw-footer__bottom a {
|
|
39
43
|
display: inline-flex;
|
|
40
44
|
align-items: center;
|
|
41
45
|
justify-content: center;
|
|
42
|
-
min-width: 24px;
|
|
43
|
-
min-height: 24px;
|
|
44
46
|
color: var(--gw-muted);
|
|
45
47
|
}
|
|
46
48
|
.gw-footer__bottom a:hover { color: var(--gw-ink); }
|
|
47
|
-
@media (
|
|
48
|
-
.gw-footer__grid {
|
|
49
|
+
@media (max-width: 620px) {
|
|
50
|
+
.gw-footer__grid { gap: 24px 20px; }
|
|
51
|
+
.gw-footer__brand { grid-column: 1 / -1; }
|
|
49
52
|
}
|
|
50
|
-
@media (min-width:
|
|
51
|
-
.gw-footer__grid { grid-auto-flow: column; grid-template-columns: minmax(0, 1.6fr); grid-auto-columns: minmax(0, 1fr); }
|
|
52
|
-
.gw-footer__brand { grid-column: auto; }
|
|
53
|
+
@media (min-width: 1081px) {
|
|
54
|
+
.gw-footer__grid { grid-auto-flow: column; grid-template-columns: minmax(0, 1.6fr); grid-auto-columns: minmax(0, 1fr); gap: 32px 24px; }
|
|
53
55
|
}
|
|
54
56
|
@media (max-width: 768px), (pointer: coarse) {
|
|
57
|
+
.gw-footer__brand .gw-brand { min-height: var(--gw-tap-min); }
|
|
58
|
+
.gw-footer__col a { display: flex; align-items: center; min-height: var(--gw-tap-min); padding-block: 0; }
|
|
55
59
|
.gw-footer__bottom a { min-height: var(--gw-tap-min); }
|
|
60
|
+
.gw-footer__bottom ul:last-child a { min-width: var(--gw-tap-min); }
|
|
61
|
+
.gw-footer__bottom ul:not(:last-child) { gap: 8px 16px; }
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
/* ---- 0.7.0: the footer as gemboss.ai has it (issue #71) ---------------------------------------------------------------
|
|
@@ -66,16 +72,14 @@
|
|
|
66
72
|
touch. */
|
|
67
73
|
.gw-footer__ask { margin-top: 24px; }
|
|
68
74
|
/* the row may run wider than the brand's sentence (four names on one line at 390px) */
|
|
69
|
-
.gw-
|
|
70
|
-
.gw-footer__brand:has(> .gw-footer__ask) > :not(.gw-footer__ask) { max-width: 320px; }
|
|
71
|
-
.gw-footer__ask > .gw-footer__ask-label { margin: 0; color: var(--gw-muted); font-size: 13px; }
|
|
75
|
+
.gw-footer__ask > .gw-footer__ask-label { margin: 0; color: var(--gw-muted); font-size: 13px; line-height: 22px; }
|
|
72
76
|
.gw-footer__ask > ul { display: flex; flex-wrap: wrap; gap: 0 16px; margin: 4px 0 0; padding: 0; list-style: none; }
|
|
73
77
|
.gw-footer__ask a { display: block; padding-block: 4px; color: var(--gw-ink); line-height: 20px; }
|
|
74
|
-
.gw-footer__ask a:hover { text-decoration: underline;
|
|
78
|
+
.gw-footer__ask a:hover { text-decoration: underline; }
|
|
75
79
|
|
|
76
80
|
/* the mark after a link that leaves the site (↗, drawn), aria-hidden; the words "(opens in a new tab)" are a separate
|
|
77
81
|
.gw-visually-hidden span, only when the link has target="_blank" */
|
|
78
|
-
.gw-footer__ext {
|
|
82
|
+
.gw-footer__ext:empty {
|
|
79
83
|
display: inline-block;
|
|
80
84
|
width: 12px;
|
|
81
85
|
height: 12px;
|
|
@@ -85,6 +89,7 @@
|
|
|
85
89
|
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3.5 8.5l5-5M4.5 3.5h4v4' fill='none' stroke='%23000' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px no-repeat;
|
|
86
90
|
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3.5 8.5l5-5M4.5 3.5h4v4' fill='none' stroke='%23000' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px no-repeat;
|
|
87
91
|
}
|
|
92
|
+
.gw-footer__ext:not(:empty) { color: var(--gw-muted); font-weight: 400; }
|
|
88
93
|
|
|
89
94
|
/* Folds: <details class="gw-footer__fold"> inside a .gw-footer__col (or a .gw-footer__family), its <summary> holding the
|
|
90
95
|
.gw-footer__heading, then the <ul>. Below 768px each is a 52px row with a chevron, folded unless the markup says
|
|
@@ -93,48 +98,52 @@
|
|
|
93
98
|
(::details-content; website.js also sets `open` there, for browsers without it). */
|
|
94
99
|
.gw-footer__fold > summary { list-style: none; }
|
|
95
100
|
.gw-footer__fold > summary::-webkit-details-marker { display: none; }
|
|
96
|
-
.gw-footer__fold .gw-footer__heading { margin: 0;
|
|
101
|
+
.gw-footer__fold .gw-footer__heading { margin: 0; }
|
|
97
102
|
.gw-footer__fold > ul { margin: 0; padding: 0; list-style: none; }
|
|
98
103
|
.gw-footer__col > .gw-footer__fold a { line-height: 20px; }
|
|
99
|
-
@media (min-width:
|
|
104
|
+
@media (min-width: 621px) {
|
|
100
105
|
.gw-footer__fold > summary { display: contents; }
|
|
101
106
|
.gw-footer__col > .gw-footer__fold .gw-footer__heading { margin-bottom: 12px; }
|
|
102
107
|
}
|
|
103
|
-
@media (min-width:
|
|
108
|
+
@media (min-width: 621px) {
|
|
104
109
|
.gw-footer__fold::details-content { content-visibility: visible; }
|
|
105
110
|
}
|
|
106
|
-
@media (max-width:
|
|
111
|
+
@media (max-width: 620px) {
|
|
107
112
|
.gw-footer__grid:has(> .gw-footer__col > .gw-footer__fold) { grid-template-columns: minmax(0, 1fr); row-gap: 0; }
|
|
108
113
|
.gw-footer__grid:has(> .gw-footer__col > .gw-footer__fold) > .gw-footer__brand { padding-bottom: 24px; }
|
|
109
|
-
.
|
|
114
|
+
/* a column's hairline on top, the last column's below too, a family row's below (gemboss.ai's order: a focused
|
|
115
|
+
row's ring passes under the next row's line) */
|
|
116
|
+
.gw-footer__col:has(> .gw-footer__fold) { border-top: 1px solid var(--gw-line); }
|
|
117
|
+
.gw-footer__col:has(> .gw-footer__fold):last-child,
|
|
110
118
|
.gw-footer__family { border-bottom: 1px solid var(--gw-line); }
|
|
111
|
-
.gw-footer__brand + .gw-footer__col:has(> .gw-footer__fold) { border-top: 1px solid var(--gw-line); }
|
|
112
119
|
.gw-footer__fold > summary {
|
|
113
120
|
display: flex;
|
|
114
121
|
align-items: center;
|
|
115
122
|
justify-content: space-between;
|
|
116
123
|
gap: 16px;
|
|
117
|
-
min-height:
|
|
118
|
-
padding
|
|
124
|
+
min-height: 52px;
|
|
125
|
+
padding: 0;
|
|
119
126
|
cursor: pointer;
|
|
120
127
|
}
|
|
121
128
|
.gw-footer__fold > summary::after {
|
|
122
129
|
content: "";
|
|
123
130
|
flex: none;
|
|
124
|
-
width:
|
|
125
|
-
height:
|
|
126
|
-
|
|
127
|
-
-
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
width: 8px;
|
|
132
|
+
height: 8px;
|
|
133
|
+
margin-right: 4px;
|
|
134
|
+
border-right: 2px solid var(--gw-muted);
|
|
135
|
+
border-bottom: 2px solid var(--gw-muted);
|
|
136
|
+
rotate: 45deg;
|
|
137
|
+
translate: 0 -2px;
|
|
130
138
|
}
|
|
131
|
-
.gw-footer__fold[open] > summary::after {
|
|
139
|
+
.gw-footer__fold[open] > summary::after { rotate: 225deg; translate: 0 2px; }
|
|
140
|
+
.gw-footer__fold > summary:focus-visible { outline: 2px solid var(--gw-accent); outline-offset: 2px; border-radius: var(--gw-radius-6); }
|
|
132
141
|
.gw-footer__fold > ul { padding-bottom: 12px; }
|
|
133
142
|
.gw-footer__col > .gw-footer__fold a { display: flex; align-items: center; min-height: var(--gw-tap-min); }
|
|
134
143
|
}
|
|
135
144
|
/* desktop: the brand's track is as wide as gemboss.ai draws it beside five columns (1.2 parts of 6.2, after five 24px
|
|
136
145
|
gaps), whatever the number of columns, so a family row can line up with it; the columns share the rest */
|
|
137
|
-
@media (min-width:
|
|
146
|
+
@media (min-width: 1081px) {
|
|
138
147
|
.gw-footer__grid:has(> .gw-footer__col > .gw-footer__fold) { grid-template-columns: minmax(0, calc((100% - 120px) * 6 / 31)); }
|
|
139
148
|
}
|
|
140
149
|
|
|
@@ -144,6 +153,8 @@
|
|
|
144
153
|
the items start where the first column starts. */
|
|
145
154
|
.gw-footer__family .gw-footer__heading { color: var(--gw-muted); font-weight: 500; }
|
|
146
155
|
.gw-footer__family .gw-footer__fold > ul { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
|
156
|
+
.gw-footer__family .gw-footer__fold > ul > li { display: flex; }
|
|
157
|
+
.gw-footer__family .gw-footer__fold > ul > li > a { flex: 1 1 auto; }
|
|
147
158
|
.gw-footer__family a {
|
|
148
159
|
display: flex;
|
|
149
160
|
flex-direction: column;
|
|
@@ -157,14 +168,18 @@
|
|
|
157
168
|
.gw-footer__family a:hover { background: var(--gw-bg-soft); }
|
|
158
169
|
.gw-footer__family b { font-size: 14px; font-weight: 600; line-height: 20px; }
|
|
159
170
|
.gw-footer__family i { color: var(--gw-muted); font-size: 13px; font-style: normal; line-height: 18px; }
|
|
160
|
-
@media (min-width:
|
|
171
|
+
@media (min-width: 621px) {
|
|
161
172
|
.gw-footer__family { margin-top: 48px; padding-top: 32px; border-top: 1px solid var(--gw-line); }
|
|
162
173
|
.gw-footer__family .gw-footer__heading { margin-bottom: 12px; }
|
|
163
|
-
.gw-footer__family .gw-footer__fold > ul { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px 16px;
|
|
174
|
+
.gw-footer__family .gw-footer__fold > ul { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px 16px; }
|
|
164
175
|
.gw-footer__family a { min-height: 0; padding: 8px 12px; }
|
|
165
176
|
.gw-footer__family + .gw-footer__bottom { margin-top: 32px; }
|
|
166
177
|
}
|
|
167
|
-
@media (min-width:
|
|
178
|
+
@media (min-width: 621px) and (max-width: 768px) {
|
|
179
|
+
.gw-footer__family a { min-height: var(--gw-tap-min); }
|
|
180
|
+
}
|
|
181
|
+
@media (min-width: 1081px) {
|
|
182
|
+
.gw-footer__family .gw-footer__fold > ul { margin-left: -12px; }
|
|
168
183
|
.gw-footer__family { display: flow-root; }
|
|
169
184
|
.gw-footer__family .gw-footer__heading { float: left; width: calc((100% - 120px) * 6 / 31); margin: 8px 0 0; }
|
|
170
185
|
.gw-footer__family .gw-footer__fold > ul { margin-left: calc((100% - 120px) * 6 / 31 + 12px); }
|
|
@@ -172,7 +187,7 @@
|
|
|
172
187
|
|
|
173
188
|
/* The theme toggle in the bottom row (.gw-theme-toggle with data-gw-theme-toggle, wired like the header's): a 32px
|
|
174
189
|
square with one hairline, its hit area 40px, 44px on touch. It pushes itself and the social links to the right.
|
|
175
|
-
|
|
190
|
+
Up to 620px the copyright and the toggle share the first line, legal and social links the second. */
|
|
176
191
|
.gw-footer__toggle {
|
|
177
192
|
position: relative;
|
|
178
193
|
width: 32px;
|
|
@@ -183,12 +198,15 @@
|
|
|
183
198
|
}
|
|
184
199
|
.gw-footer__toggle::after { content: ""; position: absolute; inset: -5px; }
|
|
185
200
|
.gw-footer__toggle svg { width: 18px; height: 18px; }
|
|
201
|
+
/* on hover the hairline turns ink and the icon the accent, the ground stays (gemboss.ai's); colour only, 0.15s */
|
|
202
|
+
.gw-footer__toggle { transition: border-color 0.15s, color 0.15s; }
|
|
203
|
+
.gw-footer__toggle:hover { border-color: var(--gw-ink); background: var(--gw-surface); color: var(--gw-accent); }
|
|
186
204
|
@media (max-width: 768px), (pointer: coarse) {
|
|
187
205
|
.gw-footer__toggle { width: var(--gw-tap-min); height: var(--gw-tap-min); }
|
|
188
206
|
}
|
|
189
|
-
@media (max-width:
|
|
190
|
-
.gw-footer__bottom:has(> .gw-footer__toggle) {
|
|
191
|
-
.gw-footer__bottom:has(> .gw-footer__toggle) > :first-child { flex: 1 1 calc(100% -
|
|
207
|
+
@media (max-width: 620px) {
|
|
208
|
+
.gw-footer__bottom:has(> .gw-footer__toggle) { gap: 8px 16px; }
|
|
209
|
+
.gw-footer__bottom:has(> .gw-footer__toggle) > :first-child { flex: 1 1 calc(100% - 64px); }
|
|
192
210
|
.gw-footer__bottom > .gw-footer__toggle { order: 1; margin: 0; }
|
|
193
211
|
.gw-footer__bottom:has(> .gw-footer__toggle) > ul { order: 2; }
|
|
194
212
|
.gw-footer__family + .gw-footer__bottom,
|
|
@@ -198,37 +216,35 @@
|
|
|
198
216
|
/* The wordmark: the brand's mark and name, large and faint, the footer's last line. Decoration (aria-hidden="true"):
|
|
199
217
|
the name is already in the brand block. Its size follows the viewport (--gw-display-wordmark) and it never makes the
|
|
200
218
|
page scroll sideways: a name too long for the line is cut. The mark is drawn in currentColor (an inline SVG); an
|
|
201
|
-
<img> mark keeps its own colours, so leave it out and keep the name.
|
|
202
|
-
|
|
203
|
-
|
|
219
|
+
<img> mark keeps its own colours, so leave it out and keep the name. Values are gemboss.ai's, 1:1 (Chris 26/10/2026):
|
|
220
|
+
weight 800, 48px above, nothing below but the footer's own 32px, the mark 7vw wide (40–96px), hidden up to 620px.
|
|
221
|
+
The footer is not painted (transparent on the page), so the breathing gate reads it as part of the page. */
|
|
204
222
|
.gw-footer__wordmark {
|
|
205
223
|
display: flex;
|
|
206
224
|
align-items: center;
|
|
207
225
|
justify-content: center;
|
|
208
226
|
gap: 24px;
|
|
209
|
-
padding-block: 48px
|
|
227
|
+
padding-block: 48px 0;
|
|
210
228
|
color: color-mix(in srgb, var(--gw-ink) 10%, transparent);
|
|
229
|
+
font-family: var(--gw-font);
|
|
211
230
|
font-size: var(--gw-display-wordmark);
|
|
212
|
-
font-weight:
|
|
231
|
+
font-weight: 800;
|
|
213
232
|
letter-spacing: var(--gw-display-tracking);
|
|
214
233
|
line-height: 1;
|
|
215
234
|
white-space: nowrap;
|
|
216
235
|
user-select: none;
|
|
217
236
|
}
|
|
218
237
|
.gw-footer__wordmark > svg,
|
|
219
|
-
.gw-footer__wordmark > img { flex: none; width:
|
|
238
|
+
.gw-footer__wordmark > img { flex: none; width: clamp(40px, 7vw, 96px); height: auto; }
|
|
220
239
|
.gw-footer__wordmark > span { min-width: 0; overflow-x: clip; }
|
|
221
|
-
@media (max-width:
|
|
222
|
-
@media (max-width:
|
|
240
|
+
@media (max-width: 640px) { .gw-footer__wordmark { gap: 16px; } }
|
|
241
|
+
@media (max-width: 620px) { .gw-footer__wordmark { display: none; } }
|
|
223
242
|
|
|
224
243
|
@media (max-width: 768px), (pointer: coarse) {
|
|
225
244
|
.gw-footer__ask a { display: flex; align-items: center; min-height: var(--gw-tap-min); padding-block: 0; }
|
|
226
245
|
}
|
|
227
|
-
@media (prefers-reduced-motion: reduce) {
|
|
228
|
-
.gw-footer__fold > summary::after { transition: none; }
|
|
229
|
-
}
|
|
230
246
|
/* a forced-colours screen drops backgrounds: draw the chevron and the ↗ in its text colour */
|
|
231
247
|
@media (forced-colors: active) {
|
|
232
|
-
.gw-footer__ext
|
|
233
|
-
.gw-footer__fold > summary::after {
|
|
248
|
+
.gw-footer__ext:empty { forced-color-adjust: none; background: CanvasText; }
|
|
249
|
+
.gw-footer__fold > summary::after { border-color: CanvasText; }
|
|
234
250
|
}
|