@gemboss/website-ui 0.12.0 → 0.14.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 +18 -5
- package/CHANGELOG.md +65 -0
- package/README.md +6 -3
- package/bin/gemboss-website.mjs +13 -1
- package/bin/lib/build.mjs +101 -0
- package/bin/lib/check.mjs +47 -3
- package/bin/lib/init.mjs +93 -3
- package/css/article.css +1 -1
- package/css/components.css +87 -27
- package/css/header.css +13 -0
- package/css/ratings.css +0 -1
- package/css/sections.css +2 -3
- package/css/tokens.css +5 -0
- package/demo/header.html +1 -1
- package/docs/components.md +48 -2
- package/docs/theming.md +38 -11
- package/js/website.js +2 -2
- package/js/website.mjs +1 -1
- package/package.json +1 -1
- package/system.json +3 -1
- package/templates/page.html +1 -1
- package/templates/sections/form.html +2 -0
- package/templates/sections/hero.html +2 -1
- package/templates/sections/split.html +1 -1
- package/templates/site/app/[[...path]]/route.js +23 -0
- package/templates/site/assets/kit-gaps.css +3 -0
- package/templates/site/assets/site.css +16 -0
- package/templates/site/gitignore +4 -0
- package/templates/site/lib/pages.mjs +41 -0
- package/templates/site/next.config.mjs +15 -0
- package/templates/site/stage-public.mjs +49 -0
- package/templates/site/vercel.json +5 -0
- package/website.css +109 -33
- package/website.min.css +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// The static HTML pages this site serves, and at which URL: the Next route (app/[[...path]]/route.js) serves them, and
|
|
2
|
+
// `npx gemboss-website build` writes the header and footer into them. An allow-list, not "every .html": partials/ and
|
|
3
|
+
// node_modules/ are full of HTML that is not a page. A new folder of pages goes into DIRS on purpose, e.g. ["blog", true].
|
|
4
|
+
// Written by `gemboss-website init --site` (@gemboss/website-ui); the site owns it from here.
|
|
5
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
6
|
+
import { join, relative, sep } from "node:path";
|
|
7
|
+
|
|
8
|
+
export const ROOT = process.cwd();
|
|
9
|
+
// [folder, recursive?]
|
|
10
|
+
const DIRS = [["", false]];
|
|
11
|
+
|
|
12
|
+
// turbopackIgnore: these reads only run at BUILD (prerendering every page); without it Next traces the whole project
|
|
13
|
+
// into the function. At runtime the function only needs 404.html (next.config.mjs).
|
|
14
|
+
function htmlIn(dir, deep) {
|
|
15
|
+
const abs = join(/*turbopackIgnore: true*/ ROOT, dir);
|
|
16
|
+
if (!existsSync(/*turbopackIgnore: true*/ abs)) return [];
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const name of readdirSync(/*turbopackIgnore: true*/ abs)) {
|
|
19
|
+
const full = join(/*turbopackIgnore: true*/ abs, name);
|
|
20
|
+
if (statSync(/*turbopackIgnore: true*/ full).isDirectory()) {
|
|
21
|
+
if (deep) out.push(...htmlIn(join(dir, name), true));
|
|
22
|
+
} else if (name.endsWith(".html")) {
|
|
23
|
+
out.push(relative(ROOT, full).split(sep).join("/"));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** file → clean URL, like Vercel's cleanUrls: index.html → /, pricing.html → /pricing. */
|
|
30
|
+
export function routeOf(file) {
|
|
31
|
+
const stem = file.slice(0, -".html".length);
|
|
32
|
+
if (stem === "index") return "/";
|
|
33
|
+
return "/" + stem.replace(/\/index$/, "");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** URL → file for every served page (404.html is the error page, not a page). */
|
|
37
|
+
export function pages() {
|
|
38
|
+
const map = new Map();
|
|
39
|
+
for (const [dir, deep] of DIRS) for (const f of htmlIn(dir, deep)) if (f !== "404.html") map.set(routeOf(f), f);
|
|
40
|
+
return map;
|
|
41
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Clean URLs: /pricing.html and /index.html redirect to the clean URL, /pricing/ to /pricing.
|
|
2
|
+
// Written by `gemboss-website init --site` (@gemboss/website-ui); the site owns it from here.
|
|
3
|
+
/** @type {import('next').NextConfig} */
|
|
4
|
+
export default {
|
|
5
|
+
trailingSlash: false,
|
|
6
|
+
poweredByHeader: false,
|
|
7
|
+
async redirects() {
|
|
8
|
+
return [
|
|
9
|
+
{ source: "/index.html", destination: "/", permanent: true },
|
|
10
|
+
{ source: "/:path(.*)\\.html", destination: "/:path", permanent: true },
|
|
11
|
+
];
|
|
12
|
+
},
|
|
13
|
+
// an unknown URL runs the route at request time and reads 404.html: ship that file with the function
|
|
14
|
+
outputFileTracingIncludes: { "/*": ["./404.html"] },
|
|
15
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copy the site's static files (not its HTML pages) into public/ before `next build`: Next serves only public/ and the
|
|
3
|
+
// routes in app/. An ALLOW-LIST: only what is listed here becomes a public URL. Written by
|
|
4
|
+
// `gemboss-website init --site` (@gemboss/website-ui); the site owns it from here.
|
|
5
|
+
// node stage-public.mjs # rebuild public/ (npm run build runs it)
|
|
6
|
+
// node stage-public.mjs --check # CI: public/ has what it needs and nothing internal
|
|
7
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
8
|
+
import { join, relative, sep } from "node:path";
|
|
9
|
+
|
|
10
|
+
const ROOT = process.cwd();
|
|
11
|
+
const OUT = join(ROOT, "public");
|
|
12
|
+
const ROOT_FILES = ["favicon.svg", "robots.txt", "sitemap.xml"];
|
|
13
|
+
const DIRS = ["assets"];
|
|
14
|
+
const SKIP = (name) => name.endsWith(".html") || name.endsWith(".md") || name === ".DS_Store";
|
|
15
|
+
|
|
16
|
+
function walk(dir) {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const name of readdirSync(dir)) {
|
|
19
|
+
const full = join(dir, name);
|
|
20
|
+
if (statSync(full).isDirectory()) out.push(...walk(full));
|
|
21
|
+
else out.push(relative(OUT, full).split(sep).join("/"));
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function stage() {
|
|
27
|
+
rmSync(OUT, { recursive: true, force: true });
|
|
28
|
+
mkdirSync(OUT, { recursive: true });
|
|
29
|
+
for (const f of ROOT_FILES) if (existsSync(join(ROOT, f))) cpSync(join(ROOT, f), join(OUT, f));
|
|
30
|
+
for (const d of DIRS) if (existsSync(join(ROOT, d))) cpSync(join(ROOT, d), join(OUT, d), { recursive: true, filter: (src) => !SKIP(src.split(sep).pop()) });
|
|
31
|
+
const files = walk(OUT);
|
|
32
|
+
console.log(`public/: ${files.length} files`);
|
|
33
|
+
return files;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function check() {
|
|
37
|
+
const files = stage();
|
|
38
|
+
const bad = [];
|
|
39
|
+
for (const need of ["favicon.svg", "assets/kit/website.css", "assets/kit/website.js", "assets/site.css"]) {
|
|
40
|
+
if (!files.includes(need)) bad.push(`missing public file: ${need}`);
|
|
41
|
+
}
|
|
42
|
+
for (const f of files) if (/\.(html|md|py|mjs|sh)$/.test(f) || /(^|\/)(package(-lock)?\.json|vercel\.json)$/.test(f)) bad.push(`internal file in public/: ${f}`);
|
|
43
|
+
for (const b of bad) console.error(" " + b);
|
|
44
|
+
console.log(bad.length ? `stage-public: RED, ${bad.length}` : "stage-public: green");
|
|
45
|
+
return bad.length ? 1 : 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (process.argv.includes("--check")) process.exit(check());
|
|
49
|
+
stage();
|
package/website.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @gemboss/website-ui 0.
|
|
1
|
+
/*! @gemboss/website-ui 0.14.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;
|
|
@@ -127,6 +127,7 @@
|
|
|
127
127
|
--gw-on-accent: var(--gw-on-accent-light);
|
|
128
128
|
--gw-success: #007f5f;
|
|
129
129
|
--gw-success-soft: #e3f1df;
|
|
130
|
+
--gw-success-strong: #0a6a4c;
|
|
130
131
|
--gw-warning: #9a6700;
|
|
131
132
|
--gw-warning-soft: #fbf1dc;
|
|
132
133
|
--gw-danger: #b42318;
|
|
@@ -159,6 +160,7 @@
|
|
|
159
160
|
--gw-on-accent: var(--gw-on-accent-dark);
|
|
160
161
|
--gw-success: #34c38f;
|
|
161
162
|
--gw-success-soft: #132a22;
|
|
163
|
+
--gw-success-strong: #34c38f;
|
|
162
164
|
--gw-warning: #e0b341;
|
|
163
165
|
--gw-warning-soft: #2b2410;
|
|
164
166
|
--gw-danger: #ff6b5a;
|
|
@@ -194,6 +196,7 @@
|
|
|
194
196
|
--gw-on-accent: var(--gw-on-accent-dark);
|
|
195
197
|
--gw-success: #34c38f;
|
|
196
198
|
--gw-success-soft: #132a22;
|
|
199
|
+
--gw-success-strong: #34c38f;
|
|
197
200
|
--gw-warning: #e0b341;
|
|
198
201
|
--gw-warning-soft: #2b2410;
|
|
199
202
|
--gw-danger: #ff6b5a;
|
|
@@ -228,6 +231,7 @@
|
|
|
228
231
|
--gw-on-accent: var(--gw-on-accent-light);
|
|
229
232
|
--gw-success: #007f5f;
|
|
230
233
|
--gw-success-soft: #e3f1df;
|
|
234
|
+
--gw-success-strong: #0a6a4c;
|
|
231
235
|
--gw-warning: #9a6700;
|
|
232
236
|
--gw-warning-soft: #fbf1dc;
|
|
233
237
|
--gw-danger: #b42318;
|
|
@@ -260,6 +264,7 @@
|
|
|
260
264
|
--gw-on-accent: var(--gw-on-accent-dark);
|
|
261
265
|
--gw-success: #34c38f;
|
|
262
266
|
--gw-success-soft: #132a22;
|
|
267
|
+
--gw-success-strong: #34c38f;
|
|
263
268
|
--gw-warning: #e0b341;
|
|
264
269
|
--gw-warning-soft: #2b2410;
|
|
265
270
|
--gw-danger: #ff6b5a;
|
|
@@ -662,6 +667,29 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
662
667
|
.gw-badge--danger { background: var(--gw-danger-soft); color: var(--gw-ink); }
|
|
663
668
|
.gw-badge--danger::before { content: ""; flex: none; width: 6px; height: 6px; border-radius: var(--gw-radius-chip); background: var(--gw-danger); }
|
|
664
669
|
|
|
670
|
+
/* Status chip (0.14.0): gemboss.ai's "pills", a state inside a drawn screen of the product (Live, a test, Early access), as
|
|
671
|
+
the GemBoss app draws it: a rounded rectangle (the action radius), 28px, 12px/500 words, neutral by default. --success is
|
|
672
|
+
live (its words in success's text form), --accent a test, --early neutral with the dot in the accent. The dot is an
|
|
673
|
+
optional `.gw-status__dot` before the words. A label that names a thing (New, Recommended) is a .gw-badge. */
|
|
674
|
+
.gw-status {
|
|
675
|
+
display: inline-flex;
|
|
676
|
+
align-items: center;
|
|
677
|
+
gap: 6px;
|
|
678
|
+
min-height: 28px;
|
|
679
|
+
padding: 6px 8px;
|
|
680
|
+
border-radius: var(--gw-radius-action);
|
|
681
|
+
background: color-mix(in srgb, var(--gw-ink) 8%, transparent);
|
|
682
|
+
color: var(--gw-ink-2);
|
|
683
|
+
font-size: 12px;
|
|
684
|
+
font-weight: 500;
|
|
685
|
+
line-height: 16px;
|
|
686
|
+
white-space: nowrap;
|
|
687
|
+
}
|
|
688
|
+
.gw-status--success { background: var(--gw-success-soft); color: var(--gw-success-strong); }
|
|
689
|
+
.gw-status--accent { background: var(--gw-accent-soft); color: var(--gw-accent-strong); }
|
|
690
|
+
.gw-status__dot { width: 6px; height: 6px; border-radius: var(--gw-radius-chip); background: currentColor; }
|
|
691
|
+
.gw-status--early .gw-status__dot { background: var(--gw-accent); }
|
|
692
|
+
|
|
665
693
|
/* cards sit: white, 1px hairline, the card radius of the site's profile (12, 20 in soft), the dashboard's resting
|
|
666
694
|
shadow (Chris 24/10/2026: one language with the app); no hover lift. A soft card is a tint and carries no shadow. */
|
|
667
695
|
.gw-card {
|
|
@@ -695,35 +723,48 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
695
723
|
:where(.gw-card, .gw-feature) p { font-size: 16px; color: var(--gw-muted); }
|
|
696
724
|
|
|
697
725
|
/* a framed screen: a browser or app window around a screenshot or a drawn mock-up */
|
|
726
|
+
/* 0.14.0: gemboss.ai's frame, 1:1: it floats (the raised shadow), 13px at 20px lines; the bar 40px with 12px sides, three
|
|
727
|
+
8px dots 6px apart, the words 16px after them and 8px between the bar's parts, one line, cut when too long; bold words in
|
|
728
|
+
the bar in ink. */
|
|
698
729
|
.gw-window {
|
|
730
|
+
position: relative;
|
|
699
731
|
overflow: hidden;
|
|
700
732
|
border: 1px solid var(--gw-line);
|
|
701
733
|
border-radius: var(--gw-radius-card);
|
|
702
734
|
background: var(--gw-surface);
|
|
703
735
|
color: var(--gw-ink);
|
|
704
|
-
box-shadow: var(--gw-shadow);
|
|
736
|
+
box-shadow: var(--gw-shadow-raised);
|
|
705
737
|
font-size: 13px;
|
|
706
|
-
line-height:
|
|
738
|
+
line-height: 20px;
|
|
707
739
|
}
|
|
708
740
|
.gw-window__bar {
|
|
709
741
|
display: flex;
|
|
710
742
|
align-items: center;
|
|
711
|
-
gap:
|
|
743
|
+
gap: 8px;
|
|
712
744
|
min-height: 40px;
|
|
713
|
-
padding:
|
|
745
|
+
padding: 0 12px;
|
|
714
746
|
border-bottom: 1px solid var(--gw-line);
|
|
715
747
|
color: var(--gw-muted);
|
|
716
748
|
font-size: 12px;
|
|
749
|
+
white-space: nowrap;
|
|
750
|
+
overflow: hidden;
|
|
717
751
|
}
|
|
718
752
|
.gw-window__bar::before {
|
|
719
753
|
content: "";
|
|
720
754
|
flex: none;
|
|
721
|
-
width:
|
|
755
|
+
width: 8px;
|
|
722
756
|
height: 8px;
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
757
|
+
margin-right: 36px;
|
|
758
|
+
border-radius: var(--gw-radius-chip);
|
|
759
|
+
background: var(--gw-line-strong);
|
|
760
|
+
box-shadow: 14px 0 0 var(--gw-line-strong), 28px 0 0 var(--gw-line-strong);
|
|
761
|
+
}
|
|
762
|
+
.gw-window__bar > :first-child { min-width: 0; overflow: hidden; }
|
|
763
|
+
.gw-window__bar b { color: var(--gw-ink); font-weight: 600; }
|
|
764
|
+
/* up to 560px the bar's words wrap rather than being cut: the bar grows, its parts at the top (gemboss.ai) */
|
|
765
|
+
@media (max-width: 560px) {
|
|
766
|
+
.gw-window__bar { align-items: flex-start; padding-block: 8px; white-space: normal; }
|
|
767
|
+
.gw-window__bar > :first-child { overflow: visible; }
|
|
727
768
|
}
|
|
728
769
|
.gw-window__body { padding: 16px; }
|
|
729
770
|
.gw-window__body:has(> img:only-child) { padding: 0; }
|
|
@@ -740,24 +781,43 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
740
781
|
}
|
|
741
782
|
.gw-window__body:has(> img:only-child) + .gw-window__caption { padding-top: 12px; }
|
|
742
783
|
|
|
784
|
+
/* figure (0.13.0, Gemians): a photo and its caption where a section shows one outside the prose (beside words in a
|
|
785
|
+
split, under a hero's actions): the picture at the card radius, the caption 14px, muted, 12px under it, as a figure
|
|
786
|
+
in .gw-prose. A screen of the product goes in .gw-window instead. */
|
|
787
|
+
.gw-figure > :is(img, video, picture) { display: block; width: 100%; height: auto; border-radius: var(--gw-radius-card); }
|
|
788
|
+
.gw-figure > picture > img { display: block; width: 100%; height: auto; border-radius: inherit; }
|
|
789
|
+
.gw-figure > figcaption { margin-top: 12px; color: var(--gw-muted); font-size: 14px; line-height: 1.5; }
|
|
790
|
+
|
|
743
791
|
/* icon: 20px inline stroke SVG (1.7 to 2px stroke, currentColor) */
|
|
744
792
|
.gw-icon { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 20px; height: 20px; }
|
|
745
793
|
.gw-icon svg { width: 20px; height: 20px; }
|
|
746
794
|
|
|
747
795
|
/* list with check marks */
|
|
748
|
-
.
|
|
749
|
-
|
|
796
|
+
/* 0.14.0: gemboss.ai's, 1:1: 14px at 1.5 lines, the words 28px in (a 20px column for the mark, 8px after it); the mark a
|
|
797
|
+
16px tile on the accent's tint, 4px down, with the tick in the accent (the tick follows the theme and the brand). The mark
|
|
798
|
+
is out of the flow, so an item may hold words and links side by side. */
|
|
799
|
+
.gw-list--check { display: grid; gap: 12px; padding: 0; list-style: none; font-size: 14px; line-height: 1.5; }
|
|
800
|
+
.gw-list--check > li { position: relative; padding-left: 28px; }
|
|
750
801
|
.gw-list--check > li::before {
|
|
751
802
|
content: "";
|
|
752
803
|
position: absolute;
|
|
753
804
|
left: 0;
|
|
754
|
-
top:
|
|
755
|
-
|
|
805
|
+
top: 4px;
|
|
806
|
+
width: 16px;
|
|
807
|
+
height: 16px;
|
|
808
|
+
border-radius: var(--gw-radius-4);
|
|
809
|
+
background: var(--gw-accent-soft);
|
|
810
|
+
}
|
|
811
|
+
.gw-list--check > li::after {
|
|
812
|
+
content: "";
|
|
813
|
+
position: absolute;
|
|
814
|
+
left: 0;
|
|
815
|
+
top: 4px;
|
|
756
816
|
width: 16px;
|
|
757
817
|
height: 16px;
|
|
758
818
|
background: var(--gw-accent);
|
|
759
|
-
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='
|
|
760
|
-
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='
|
|
819
|
+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 8.2l2.6 2.5L12 5.3' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px no-repeat;
|
|
820
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 8.2l2.6 2.5L12 5.3' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px no-repeat;
|
|
761
821
|
}
|
|
762
822
|
|
|
763
823
|
/* table: the wrapper scrolls inside its own box on phones, the page never does */
|
|
@@ -780,10 +840,12 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
780
840
|
.gw-table--compare thead th:not(:first-child) { text-align: center; }
|
|
781
841
|
.gw-table--compare tbody th { position: sticky; left: 0; z-index: 1; background: var(--gw-surface); min-width: 160px; }
|
|
782
842
|
|
|
783
|
-
/* FAQ: native details/summary, no script
|
|
843
|
+
/* FAQ: native details/summary, no script. 0.14.0: gemboss.ai's, 1:1 (the question in the brand face alone, at the line
|
|
844
|
+
height around it; the + is the face's own glyph in the accent, turned 45 degrees when open; the answer 33em wide,
|
|
845
|
+
rising in as the item opens) */
|
|
784
846
|
.gw-faq { display: grid; gap: 12px; }
|
|
785
847
|
.gw-faq__item {
|
|
786
|
-
padding:
|
|
848
|
+
padding: 2px 24px;
|
|
787
849
|
border: 1px solid var(--gw-line);
|
|
788
850
|
border-radius: var(--gw-radius-card);
|
|
789
851
|
background: var(--gw-surface);
|
|
@@ -796,25 +858,28 @@ h5, h6 { font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
|
|
796
858
|
gap: 16px;
|
|
797
859
|
min-height: 44px;
|
|
798
860
|
padding: 20px 0;
|
|
861
|
+
font-family: var(--gw-font-action);
|
|
799
862
|
font-size: 18px;
|
|
800
863
|
font-weight: 600;
|
|
801
|
-
line-height: 1.4;
|
|
802
864
|
list-style: none;
|
|
803
865
|
cursor: pointer;
|
|
804
866
|
}
|
|
805
867
|
.gw-faq__item > summary::-webkit-details-marker { display: none; }
|
|
806
868
|
.gw-faq__item > summary::after {
|
|
807
|
-
content: "";
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
linear-gradient(var(--gw-accent), var(--gw-accent)) center / 16px 2px no-repeat,
|
|
813
|
-
linear-gradient(var(--gw-accent), var(--gw-accent)) center / 2px 16px no-repeat;
|
|
814
|
-
transition: transform 0.2s var(--gw-ease);
|
|
869
|
+
content: "+";
|
|
870
|
+
color: var(--gw-accent);
|
|
871
|
+
font-size: 24px;
|
|
872
|
+
font-weight: 400;
|
|
873
|
+
transition: transform 0.2s ease;
|
|
815
874
|
}
|
|
816
875
|
.gw-faq__item[open] > summary::after { transform: rotate(45deg); }
|
|
817
|
-
.gw-faq__item > :not(summary) { margin: 0 0 20px;
|
|
876
|
+
.gw-faq__item > :not(summary) { margin: 0 0 20px; color: var(--gw-muted); font-size: 16px; }
|
|
877
|
+
.gw-faq__item > p { max-width: 33em; }
|
|
878
|
+
@keyframes gw-faq-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
|
|
879
|
+
.gw-faq__item[open] > :not(summary) { animation: gw-faq-in 0.28s ease; }
|
|
880
|
+
@media (prefers-reduced-motion: reduce) { .gw-faq__item[open] > :not(summary) { animation: none; } }
|
|
881
|
+
/* phones: the rows tighten (gemboss.ai), never under the 44px a finger needs */
|
|
882
|
+
@media (max-width: 560px) { .gw-faq__item > summary { padding-block: 4px; } }
|
|
818
883
|
|
|
819
884
|
/* 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
885
|
pointer), its label wrapping (and tighter) from 480px down in the page's <main>, so a long one never pushes the page
|
|
@@ -1541,6 +1606,19 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
|
|
|
1541
1606
|
:root:not([data-theme="light"]) .gw-theme-toggle__moon { display: none; }
|
|
1542
1607
|
}
|
|
1543
1608
|
|
|
1609
|
+
/* ---- 0.13.0: the brand's logo as one drawing (Gemians): the mark and the wordmark in one file, in place of the mark and
|
|
1610
|
+
the name, 26px tall, its width the file's. Its alt is the brand's name. A logo drawn for dark grounds only (a white
|
|
1611
|
+
wordmark) comes as a second file, .gw-brand__logo--dark, right after the first: it shows in dark (the system's or the
|
|
1612
|
+
toggle's) and the first one hides, the toggle icons' rule. One file that reads on both: no --dark. */
|
|
1613
|
+
.gw-brand__logo { display: block; flex: none; width: auto; height: 26px; }
|
|
1614
|
+
.gw-brand__logo--dark { display: none; }
|
|
1615
|
+
:root[data-theme="dark"] .gw-brand__logo--dark { display: block; }
|
|
1616
|
+
:root[data-theme="dark"] .gw-brand__logo:has(+ .gw-brand__logo--dark) { display: none; }
|
|
1617
|
+
@media (prefers-color-scheme: dark) {
|
|
1618
|
+
:root:not([data-theme="light"]) .gw-brand__logo--dark { display: block; }
|
|
1619
|
+
:root:not([data-theme="light"]) .gw-brand__logo:has(+ .gw-brand__logo--dark) { display: none; }
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1544
1622
|
/* footer: brand block, link columns, bottom row. One hairline on top, white like the page. */
|
|
1545
1623
|
.gw-footer {
|
|
1546
1624
|
padding-block: 64px 32px;
|
|
@@ -1807,7 +1885,7 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
|
|
|
1807
1885
|
radial-gradient(420px 320px at 52% 118%, color-mix(in srgb, var(--gw-wash-mint) 10%, transparent), transparent 62%);
|
|
1808
1886
|
}
|
|
1809
1887
|
.gw-hero h1 .gw-dim { display: block; }
|
|
1810
|
-
.gw-hero > .gw-wrap > .gw-window { margin-top: 48px; }
|
|
1888
|
+
.gw-hero > .gw-wrap > :is(.gw-window, .gw-figure) { margin-top: 48px; }
|
|
1811
1889
|
.gw-hero--center { text-align: center; }
|
|
1812
1890
|
.gw-hero--center > .gw-wrap > * { margin-inline: auto; }
|
|
1813
1891
|
.gw-hero--center .gw-actions { justify-content: center; }
|
|
@@ -1840,7 +1918,7 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
|
|
|
1840
1918
|
.gw-logos img { width: auto; height: 28px; }
|
|
1841
1919
|
|
|
1842
1920
|
/* stats: three or four facts, each with its source */
|
|
1843
|
-
.gw-stats { display: grid; grid-template-columns: minmax(0, 1fr); gap: 24px;
|
|
1921
|
+
.gw-stats { display: grid; grid-template-columns: minmax(0, 1fr); gap: 24px; padding: 0; list-style: none; }
|
|
1844
1922
|
.gw-stats__value { font-size: var(--gw-display-h2); font-weight: 600; letter-spacing: -0.03em; line-height: 1.1; font-variant-numeric: tabular-nums; }
|
|
1845
1923
|
.gw-stats__label { margin-top: 8px; max-width: 28ch; color: var(--gw-muted); font-size: 14px; }
|
|
1846
1924
|
.gw-stats__source { margin-top: 8px; color: var(--gw-muted); font-size: 12px; }
|
|
@@ -1873,7 +1951,6 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
|
|
|
1873
1951
|
display: grid;
|
|
1874
1952
|
grid-template-columns: minmax(0, 1fr);
|
|
1875
1953
|
gap: 32px 24px;
|
|
1876
|
-
margin: 0;
|
|
1877
1954
|
padding: 0;
|
|
1878
1955
|
list-style: none;
|
|
1879
1956
|
counter-reset: gw-step;
|
|
@@ -2305,7 +2382,7 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
|
|
|
2305
2382
|
.gw-card__media first, a .gw-label for the category, the title (its link covers the card, so the card is one target
|
|
2306
2383
|
and the link's name is the title alone), one or two lines of excerpt and a .gw-article-meta row at the foot. One
|
|
2307
2384
|
column on phones, two from 768px, three from 1080px. --compact: one column of rows, no card and no picture. */
|
|
2308
|
-
.gw-post-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px;
|
|
2385
|
+
.gw-post-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; padding: 0; list-style: none; }
|
|
2309
2386
|
.gw-post-list > li { display: flex; min-width: 0; }
|
|
2310
2387
|
.gw-post { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; }
|
|
2311
2388
|
.gw-post:hover { border-color: var(--gw-line-strong); }
|
|
@@ -2521,7 +2598,6 @@ html.is-menu-open .gw-header:has(> .gw-mobile-menu.is-drill) { position: sticky;
|
|
|
2521
2598
|
display: grid;
|
|
2522
2599
|
grid-template-columns: minmax(0, 1fr);
|
|
2523
2600
|
gap: 16px;
|
|
2524
|
-
margin: 0;
|
|
2525
2601
|
padding: 0;
|
|
2526
2602
|
list-style: none;
|
|
2527
2603
|
}
|