@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.
@@ -0,0 +1,4 @@
1
+ /node_modules/
2
+ /.next/
3
+ /public/
4
+ .DS_Store
@@ -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();
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://openapi.vercel.sh/vercel.json",
3
+ "framework": "nextjs",
4
+ "buildCommand": "npm run build"
5
+ }
package/website.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! @gemboss/website-ui 0.12.0 | generated by scripts/build.mjs from the files in css/. Edit those and not this file */
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: 1.5;
738
+ line-height: 20px;
707
739
  }
708
740
  .gw-window__bar {
709
741
  display: flex;
710
742
  align-items: center;
711
- gap: 12px;
743
+ gap: 8px;
712
744
  min-height: 40px;
713
- padding: 8px 16px;
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: 36px;
755
+ width: 8px;
722
756
  height: 8px;
723
- background:
724
- radial-gradient(circle at 4px 4px, var(--gw-line-strong) 4px, transparent 4px),
725
- radial-gradient(circle at 18px 4px, var(--gw-line-strong) 4px, transparent 4px),
726
- radial-gradient(circle at 32px 4px, var(--gw-line-strong) 4px, transparent 4px);
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
- .gw-list--check { display: grid; gap: 12px; padding: 0; list-style: none; }
749
- .gw-list--check > li { position: relative; padding-left: 24px; }
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: 6px;
755
- top: calc((1lh - 16px) / 2);
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='M3.5 8.4l2.9 2.8L12.5 5' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px no-repeat;
760
- mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.4l2.9 2.8L12.5 5' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px no-repeat;
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: 0 24px;
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
- flex: none;
809
- width: 16px;
810
- height: 16px;
811
- background:
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; max-width: 68ch; color: var(--gw-muted); font-size: 16px; }
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; margin: 0; padding: 0; list-style: none; }
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; margin: 0; padding: 0; list-style: none; }
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
  }