@grove-dev/astro 0.2.16 → 0.2.19
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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/display.js +4 -4
- package/dist/lib/display.js.map +1 -1
- package/dist/lib/repo.d.ts.map +1 -1
- package/dist/lib/repo.js +3 -4
- package/dist/lib/repo.js.map +1 -1
- package/dist/lib/search.d.ts +2 -2
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/search.js +17 -6
- package/dist/lib/search.js.map +1 -1
- package/dist/theme.test.d.ts +2 -0
- package/dist/theme.test.d.ts.map +1 -0
- package/dist/theme.test.js +41 -0
- package/dist/theme.test.js.map +1 -0
- package/package.json +1 -1
- package/src/components/FilterGroupMenu.astro +3 -1
- package/src/components/Hero.astro +3 -3
- package/src/components/Icon.astro +4 -4
- package/src/components/IndexRow.astro +15 -2
- package/src/index.ts +52 -1
- package/src/layouts/BaseLayout.astro +12 -11
- package/src/layouts/Seo.astro +12 -4
- package/src/lib/display.ts +4 -4
- package/src/lib/repo.ts +3 -4
- package/src/lib/search.ts +18 -8
- package/src/styles.css +184 -69
- package/src/theme.test.ts +52 -0
- package/templates/default/package.json +2 -1
- package/templates/default/public/llms-full.txt +1 -1
- package/templates/default/public/sitemap.xml +10 -10
- package/templates/default/src/data/records.ts +115 -77
- package/templates/default/src/pages/[slug]/[recordSlug].astro +29 -33
- package/templates/default/src/pages/[slug]/index.astro +20 -296
- package/templates/default/src/styles/global.css +14 -39
- package/templates/default/tailwind.config.mjs +0 -130
package/src/layouts/Seo.astro
CHANGED
|
@@ -85,6 +85,14 @@ const organizationLd = {
|
|
|
85
85
|
logo: new URL("/og-image.svg", siteUrl).toString(),
|
|
86
86
|
...(siteConfig.repoUrl ? { sameAs: [siteConfig.repoUrl] } : {}),
|
|
87
87
|
};
|
|
88
|
+
|
|
89
|
+
// Pre-stringify the JSON-LD payloads in the frontmatter so the
|
|
90
|
+
// template body just interpolates the resulting strings. For SSR
|
|
91
|
+
// this saves the JSON.stringify call on every render; for static
|
|
92
|
+
// builds the cost is unchanged but the template reads cleaner.
|
|
93
|
+
const websiteLdJson = JSON.stringify(websiteLd);
|
|
94
|
+
const organizationLdJson = JSON.stringify(organizationLd);
|
|
95
|
+
const perPageLdJson = jsonLd ? JSON.stringify(Array.isArray(jsonLd) ? jsonLd : [jsonLd]) : null;
|
|
88
96
|
---
|
|
89
97
|
|
|
90
98
|
<!-- Primary meta -->
|
|
@@ -113,14 +121,14 @@ const organizationLd = {
|
|
|
113
121
|
<meta name="twitter:image:alt" content={`${siteConfig.name} social preview`} />
|
|
114
122
|
|
|
115
123
|
<!-- JSON-LD: site-wide identity -->
|
|
116
|
-
<script is:inline type="application/ld+json" set:html={
|
|
117
|
-
<script is:inline type="application/ld+json" set:html={
|
|
124
|
+
<script is:inline type="application/ld+json" set:html={websiteLdJson} />
|
|
125
|
+
<script is:inline type="application/ld+json" set:html={organizationLdJson} />
|
|
118
126
|
|
|
119
127
|
<!-- JSON-LD: per-page (SoftwareSourceCode for /items/[slug], etc.) -->
|
|
120
|
-
{
|
|
128
|
+
{perPageLdJson && (
|
|
121
129
|
<script
|
|
122
130
|
is:inline
|
|
123
131
|
type="application/ld+json"
|
|
124
|
-
set:html={
|
|
132
|
+
set:html={perPageLdJson}
|
|
125
133
|
/>
|
|
126
134
|
)}
|
package/src/lib/display.ts
CHANGED
|
@@ -45,8 +45,8 @@ export const STATUS_DISPLAY: Record<string, string> = {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
export function statusDisplay(s: string | null | undefined): string {
|
|
48
|
-
if (!s) return STATUS_DISPLAY.unknown;
|
|
49
|
-
return STATUS_DISPLAY[s] ?? STATUS_DISPLAY.unknown;
|
|
48
|
+
if (!s) return STATUS_DISPLAY.unknown ?? "";
|
|
49
|
+
return STATUS_DISPLAY[s] ?? STATUS_DISPLAY.unknown ?? "";
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// ── Lens ids (the curated tabs on /items) ────────────────────────────
|
|
@@ -65,7 +65,7 @@ export const LENS_DISPLAY: Record<string, string> = {
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
export function lensDisplay(id: string | null | undefined): string {
|
|
68
|
-
if (!id) return LENS_DISPLAY.all;
|
|
68
|
+
if (!id) return LENS_DISPLAY.all ?? "All";
|
|
69
69
|
return LENS_DISPLAY[id] ?? id;
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -79,7 +79,7 @@ export const SORT_DISPLAY: Record<string, string> = {
|
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
export function sortDisplay(id: string | null | undefined): string {
|
|
82
|
-
if (!id) return SORT_DISPLAY["recently-updated"];
|
|
82
|
+
if (!id) return SORT_DISPLAY["recently-updated"] ?? "";
|
|
83
83
|
return SORT_DISPLAY[id] ?? id;
|
|
84
84
|
}
|
|
85
85
|
|
package/src/lib/repo.ts
CHANGED
|
@@ -17,10 +17,9 @@ export function getOwnerAndRepoFromRepoUrl(repoUrl: string): {
|
|
|
17
17
|
}
|
|
18
18
|
const m = repoUrl.match(/github\.com\/([^/]+)\/([^/?#]+)/);
|
|
19
19
|
if (!m) return { owner: null, repo: null };
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
};
|
|
20
|
+
const owner = m[1] ?? null;
|
|
21
|
+
const repo = m[2]?.replace(/\.git$/, "") ?? null;
|
|
22
|
+
return { owner, repo };
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
/**
|
package/src/lib/search.ts
CHANGED
|
@@ -18,9 +18,9 @@ export type IndexFilters = {
|
|
|
18
18
|
/** A curated lens (e.g. "good-to-learn", "production-like"). Single value. */
|
|
19
19
|
lens?: string;
|
|
20
20
|
/** Sort order. Single value, defaults to "recently-updated". */
|
|
21
|
-
sort?: IndexSort;
|
|
21
|
+
sort?: IndexSort | undefined;
|
|
22
22
|
/** 1-based page number. */
|
|
23
|
-
page?: number;
|
|
23
|
+
page?: number | undefined;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
/** Available sort orders, in UI order. */
|
|
@@ -65,17 +65,19 @@ export function filtersFromSearchParams(sp: URLSearchParams): IndexFilters {
|
|
|
65
65
|
: undefined;
|
|
66
66
|
const rawPage = Number(sp.get(KEYS.page));
|
|
67
67
|
const page = Number.isFinite(rawPage) && rawPage >= 1 ? Math.floor(rawPage) : undefined;
|
|
68
|
+
const q = sp.get(KEYS.q);
|
|
69
|
+
const lens = sp.get(KEYS.lens);
|
|
68
70
|
return {
|
|
69
|
-
q
|
|
71
|
+
...(q !== null ? { q } : {}),
|
|
70
72
|
stacks: sp.getAll(KEYS.stacks).filter(Boolean),
|
|
71
73
|
platforms: sp.getAll(KEYS.platforms).filter(Boolean),
|
|
72
74
|
categories: sp.getAll(KEYS.categories).filter(Boolean),
|
|
73
75
|
labels: sp.getAll(KEYS.labels).filter(Boolean),
|
|
74
76
|
licenses: sp.getAll(KEYS.licenses).filter(Boolean),
|
|
75
77
|
statuses: sp.getAll(KEYS.statuses).flatMap((s) => s.split(",")).filter(Boolean),
|
|
76
|
-
lens
|
|
77
|
-
sort,
|
|
78
|
-
page,
|
|
78
|
+
...(lens !== null ? { lens } : {}),
|
|
79
|
+
...(sort !== undefined ? { sort } : {}),
|
|
80
|
+
...(page !== undefined ? { page } : {}),
|
|
79
81
|
};
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -352,12 +354,20 @@ export function removeFilter(
|
|
|
352
354
|
): IndexFilters {
|
|
353
355
|
let next: IndexFilters;
|
|
354
356
|
if (key === "q") {
|
|
355
|
-
|
|
357
|
+
// `q` is `string | undefined` under exactOptionalPropertyTypes; the
|
|
358
|
+
// cleanest way to drop it is to omit the key entirely (delete-style).
|
|
359
|
+
const { q: _drop, ...rest } = f;
|
|
360
|
+
next = rest;
|
|
356
361
|
} else {
|
|
357
362
|
const current = f[key] as string[] | undefined;
|
|
358
363
|
if (!current) return f;
|
|
359
364
|
const filtered = current.filter((v) => v !== value);
|
|
360
|
-
|
|
365
|
+
if (filtered.length === 0) {
|
|
366
|
+
const { [key]: _drop, ...rest } = f;
|
|
367
|
+
next = rest;
|
|
368
|
+
} else {
|
|
369
|
+
next = { ...f, [key]: filtered };
|
|
370
|
+
}
|
|
361
371
|
}
|
|
362
372
|
next.page = 1;
|
|
363
373
|
return next;
|
package/src/styles.css
CHANGED
|
@@ -17,61 +17,106 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
@import "tailwindcss";
|
|
20
|
+
/*
|
|
21
|
+
* Custom `dark` variant — `dark:` utilities must key off the `.dark`
|
|
22
|
+
* class on <html> (set by the THEME_INIT script in BaseLayout.astro),
|
|
23
|
+
* NOT off `prefers-color-scheme`. Tailwind v4.3.0 splits the selector
|
|
24
|
+
* argument on commas — including commas inside `:where(...)` — so
|
|
25
|
+
* `(&:where(.dark, .dark *))` is silently parsed as two separate
|
|
26
|
+
* (and broken) selectors and the variant never registers. Use two
|
|
27
|
+
* selectors joined by a space instead.
|
|
28
|
+
*/
|
|
29
|
+
@custom-variant dark (&:where(.dark), &:where(.dark *));
|
|
20
30
|
@source "./**/*.{astro,html,js,jsx,ts,tsx}";
|
|
21
31
|
|
|
22
32
|
@theme {
|
|
23
|
-
/* ──
|
|
24
|
-
--color-ink-50:
|
|
25
|
-
--color-ink-100:
|
|
26
|
-
--color-ink-200:
|
|
27
|
-
--color-ink-300:
|
|
28
|
-
--color-ink-400:
|
|
29
|
-
--color-ink-500:
|
|
30
|
-
--color-ink-600:
|
|
31
|
-
--color-ink-700:
|
|
32
|
-
--color-ink-800:
|
|
33
|
-
--color-ink-900:
|
|
34
|
-
--color-ink-950:
|
|
33
|
+
/* ── Starlight-aligned neutral scale ──────────────────────────── */
|
|
34
|
+
--color-ink-50: oklch(98.5% 0 0);
|
|
35
|
+
--color-ink-100: oklch(97% 0 0);
|
|
36
|
+
--color-ink-200: oklch(92.2% 0 0);
|
|
37
|
+
--color-ink-300: oklch(87% 0 0);
|
|
38
|
+
--color-ink-400: oklch(70.8% 0 0);
|
|
39
|
+
--color-ink-500: oklch(55.6% 0 0);
|
|
40
|
+
--color-ink-600: oklch(43.9% 0 0);
|
|
41
|
+
--color-ink-700: oklch(37.1% 0 0);
|
|
42
|
+
--color-ink-800: oklch(26.9% 0 0);
|
|
43
|
+
--color-ink-900: oklch(20.5% 0 0);
|
|
44
|
+
--color-ink-950: oklch(14.5% 0 0);
|
|
45
|
+
|
|
46
|
+
/* ── Semantic colors ────────────────────────────────────────────
|
|
47
|
+
* These point at runtime variables instead of fixed palette steps so
|
|
48
|
+
* Tailwind utilities such as `bg-background` and `text-foreground`
|
|
49
|
+
* follow the resolved theme without requiring a `dark:` twin. The
|
|
50
|
+
* values below mirror packages/starlight/styles/{theme,base}.css.
|
|
51
|
+
*/
|
|
52
|
+
--color-background: var(--grove-background);
|
|
53
|
+
--color-foreground: var(--grove-foreground);
|
|
54
|
+
--color-primary: var(--grove-primary);
|
|
55
|
+
--color-primary-foreground: var(--grove-primary-foreground);
|
|
56
|
+
--color-secondary: var(--grove-secondary);
|
|
57
|
+
--color-secondary-foreground: var(--grove-secondary-foreground);
|
|
58
|
+
--color-muted: var(--grove-muted);
|
|
59
|
+
--color-muted-foreground: var(--grove-muted-foreground);
|
|
60
|
+
--color-accent: var(--grove-accent);
|
|
61
|
+
--color-accent-foreground: var(--grove-accent-foreground);
|
|
62
|
+
--color-border: var(--grove-border);
|
|
63
|
+
--color-input: var(--grove-input);
|
|
64
|
+
--color-ring: var(--grove-ring);
|
|
65
|
+
--color-card: var(--grove-card);
|
|
66
|
+
--color-card-foreground: var(--grove-card-foreground);
|
|
67
|
+
--color-popover: var(--grove-popover);
|
|
68
|
+
--color-popover-foreground: var(--grove-popover-foreground);
|
|
35
69
|
|
|
36
70
|
/* ── Accent (interactive states) ─────────────────────────────── */
|
|
37
|
-
--color-accent:
|
|
38
|
-
--color-accent-muted: #388bfd1a;
|
|
71
|
+
--color-accent-muted: color-mix(in oklab, var(--grove-foreground) 8%, transparent);
|
|
39
72
|
|
|
40
73
|
/* ── Status palettes (badges only) ───────────────────────────── */
|
|
41
|
-
--color-emerald-50:
|
|
42
|
-
--color-emerald-300:
|
|
43
|
-
--color-emerald-400:
|
|
44
|
-
--color-emerald-
|
|
45
|
-
--color-emerald-
|
|
46
|
-
--color-emerald-
|
|
47
|
-
|
|
48
|
-
--color-
|
|
49
|
-
|
|
50
|
-
--color-orange-
|
|
51
|
-
--color-orange-
|
|
52
|
-
--color-orange-
|
|
53
|
-
--color-orange-
|
|
54
|
-
|
|
55
|
-
--color-
|
|
56
|
-
--color-
|
|
57
|
-
|
|
58
|
-
--color-blue-
|
|
59
|
-
--color-blue-
|
|
60
|
-
--color-blue-
|
|
61
|
-
|
|
62
|
-
--color-
|
|
63
|
-
--color-
|
|
64
|
-
|
|
65
|
-
--color-amber-
|
|
66
|
-
--color-amber-
|
|
67
|
-
--color-amber-
|
|
74
|
+
--color-emerald-50: oklch(98.2% 0.018 155.826);
|
|
75
|
+
--color-emerald-300: oklch(87.1% 0.15 154.449);
|
|
76
|
+
--color-emerald-400: oklch(79.2% 0.209 151.711);
|
|
77
|
+
--color-emerald-500: oklch(72.3% 0.219 149.579);
|
|
78
|
+
--color-emerald-600: oklch(52.7% 0.154 150.069);
|
|
79
|
+
--color-emerald-700: oklch(52.7% 0.154 150.069);
|
|
80
|
+
--color-emerald-800: oklch(26.6% 0.065 152.934);
|
|
81
|
+
--color-emerald-950: oklch(26.6% 0.065 152.934);
|
|
82
|
+
|
|
83
|
+
--color-orange-50: oklch(98% 0.016 73.684);
|
|
84
|
+
--color-orange-300: oklch(90.1% 0.076 70.697);
|
|
85
|
+
--color-orange-400: oklch(83.7% 0.128 66.29);
|
|
86
|
+
--color-orange-500: oklch(70.5% 0.213 47.604);
|
|
87
|
+
--color-orange-700: oklch(70.5% 0.213 47.604);
|
|
88
|
+
--color-orange-800: oklch(55.3% 0.195 38.402);
|
|
89
|
+
--color-orange-950: oklch(40.8% 0.123 38.172);
|
|
90
|
+
|
|
91
|
+
--color-blue-50: oklch(97% 0.014 254.604);
|
|
92
|
+
--color-blue-300: oklch(82.8% 0.111 230.318);
|
|
93
|
+
--color-blue-400: oklch(70.7% 0.165 254.624);
|
|
94
|
+
--color-blue-700: oklch(48.8% 0.243 264.376);
|
|
95
|
+
--color-blue-800: oklch(29.3% 0.066 243.157);
|
|
96
|
+
--color-blue-950: oklch(29.3% 0.066 243.157);
|
|
97
|
+
|
|
98
|
+
--color-amber-50: oklch(98% 0.016 73.684);
|
|
99
|
+
--color-amber-300: oklch(90.1% 0.076 70.697);
|
|
100
|
+
--color-amber-400: oklch(83.7% 0.128 66.29);
|
|
101
|
+
--color-amber-500: oklch(83.7% 0.128 66.29);
|
|
102
|
+
--color-amber-600: oklch(70.5% 0.213 47.604);
|
|
103
|
+
--color-amber-700: oklch(70.5% 0.213 47.604);
|
|
104
|
+
--color-amber-800: oklch(55.3% 0.195 38.402);
|
|
105
|
+
--color-amber-900: oklch(40.8% 0.123 38.172);
|
|
106
|
+
--color-amber-950: oklch(40.8% 0.123 38.172);
|
|
68
107
|
|
|
69
108
|
/* ── Font stacks ─────────────────────────────────────────────── */
|
|
70
|
-
--font-sans:
|
|
71
|
-
"Helvetica Neue", Arial, sans-serif
|
|
109
|
+
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
110
|
+
"Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji",
|
|
111
|
+
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
72
112
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
|
73
113
|
"Liberation Mono", "Courier New", monospace;
|
|
74
114
|
|
|
115
|
+
/* ── Starlight typography weight/rhythm ──────────────────────── */
|
|
116
|
+
--font-weight-semibold: 500;
|
|
117
|
+
--font-weight-bold: 600;
|
|
118
|
+
--leading-relaxed: 1.75;
|
|
119
|
+
|
|
75
120
|
/* ── Font size extensions (2xs) ──────────────────────────────── */
|
|
76
121
|
--text-2xs: 0.6875rem;
|
|
77
122
|
--text-2xs--line-height: 1rem;
|
|
@@ -139,7 +184,61 @@
|
|
|
139
184
|
|
|
140
185
|
@layer base {
|
|
141
186
|
:root {
|
|
142
|
-
color-scheme: light
|
|
187
|
+
color-scheme: light;
|
|
188
|
+
|
|
189
|
+
/* Starlight light theme, expressed as Grove's public semantic API. */
|
|
190
|
+
--grove-background: oklch(100% 0 0);
|
|
191
|
+
--grove-foreground: var(--color-ink-950);
|
|
192
|
+
--grove-gray-1: var(--color-ink-900);
|
|
193
|
+
--grove-gray-2: var(--color-ink-800);
|
|
194
|
+
--grove-gray-3: var(--color-ink-700);
|
|
195
|
+
--grove-gray-4: var(--color-ink-500);
|
|
196
|
+
--grove-gray-5: var(--color-ink-400);
|
|
197
|
+
--grove-gray-6: var(--color-ink-300);
|
|
198
|
+
--grove-gray-7: var(--color-ink-100);
|
|
199
|
+
--grove-primary: var(--grove-foreground);
|
|
200
|
+
--grove-primary-foreground: var(--grove-gray-7);
|
|
201
|
+
--grove-secondary: var(--grove-gray-7);
|
|
202
|
+
--grove-secondary-foreground: var(--grove-foreground);
|
|
203
|
+
--grove-muted: var(--grove-gray-7);
|
|
204
|
+
--grove-muted-foreground: var(--grove-gray-4);
|
|
205
|
+
--grove-accent: var(--grove-gray-7);
|
|
206
|
+
--grove-accent-foreground: var(--grove-foreground);
|
|
207
|
+
--grove-border: var(--grove-gray-6);
|
|
208
|
+
--grove-input: var(--grove-gray-6);
|
|
209
|
+
--grove-ring: var(--grove-gray-4);
|
|
210
|
+
--grove-card: var(--grove-background);
|
|
211
|
+
--grove-card-foreground: var(--grove-foreground);
|
|
212
|
+
--grove-popover: var(--grove-background);
|
|
213
|
+
--grove-popover-foreground: var(--grove-foreground);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
:root.dark {
|
|
217
|
+
color-scheme: dark;
|
|
218
|
+
--grove-background: var(--color-ink-950);
|
|
219
|
+
--grove-foreground: var(--color-ink-50);
|
|
220
|
+
--grove-gray-1: var(--color-ink-50);
|
|
221
|
+
--grove-gray-2: var(--color-ink-300);
|
|
222
|
+
--grove-gray-3: var(--color-ink-400);
|
|
223
|
+
--grove-gray-4: var(--color-ink-500);
|
|
224
|
+
--grove-gray-5: var(--color-ink-700);
|
|
225
|
+
--grove-gray-6: var(--color-ink-800);
|
|
226
|
+
--grove-gray-7: var(--color-ink-900);
|
|
227
|
+
--grove-primary: var(--grove-foreground);
|
|
228
|
+
--grove-primary-foreground: var(--grove-gray-7);
|
|
229
|
+
--grove-secondary: var(--grove-gray-6);
|
|
230
|
+
--grove-secondary-foreground: var(--grove-foreground);
|
|
231
|
+
--grove-muted: var(--grove-gray-6);
|
|
232
|
+
--grove-muted-foreground: var(--grove-gray-3);
|
|
233
|
+
--grove-accent: var(--grove-gray-5);
|
|
234
|
+
--grove-accent-foreground: var(--grove-foreground);
|
|
235
|
+
--grove-border: var(--grove-gray-6);
|
|
236
|
+
--grove-input: var(--grove-gray-5);
|
|
237
|
+
--grove-ring: var(--grove-gray-4);
|
|
238
|
+
--grove-card: var(--grove-background);
|
|
239
|
+
--grove-card-foreground: var(--grove-foreground);
|
|
240
|
+
--grove-popover: var(--grove-gray-7);
|
|
241
|
+
--grove-popover-foreground: var(--grove-foreground);
|
|
143
242
|
}
|
|
144
243
|
|
|
145
244
|
html {
|
|
@@ -150,16 +249,11 @@
|
|
|
150
249
|
|
|
151
250
|
body {
|
|
152
251
|
margin: 0;
|
|
153
|
-
background:
|
|
154
|
-
color: var(--
|
|
252
|
+
background: var(--grove-background);
|
|
253
|
+
color: var(--grove-foreground);
|
|
155
254
|
font-family: var(--font-sans);
|
|
156
255
|
font-size: 15px;
|
|
157
|
-
line-height: 1.
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.dark body {
|
|
161
|
-
background: var(--color-ink-950);
|
|
162
|
-
color: var(--color-ink-100);
|
|
256
|
+
line-height: 1.75;
|
|
163
257
|
}
|
|
164
258
|
|
|
165
259
|
a {
|
|
@@ -169,30 +263,23 @@
|
|
|
169
263
|
|
|
170
264
|
/* Subtle selection */
|
|
171
265
|
::selection {
|
|
172
|
-
background: var(--
|
|
173
|
-
color: var(--
|
|
174
|
-
}
|
|
175
|
-
.dark ::selection {
|
|
176
|
-
background: var(--color-ink-800);
|
|
177
|
-
color: var(--color-ink-100);
|
|
266
|
+
background: var(--grove-muted);
|
|
267
|
+
color: var(--grove-foreground);
|
|
178
268
|
}
|
|
179
269
|
|
|
180
270
|
/* Slim, neutral scrollbar */
|
|
181
271
|
* {
|
|
182
272
|
scrollbar-width: thin;
|
|
183
|
-
scrollbar-color: var(--
|
|
273
|
+
scrollbar-color: var(--grove-border) transparent;
|
|
184
274
|
}
|
|
185
275
|
*::-webkit-scrollbar {
|
|
186
276
|
width: 8px;
|
|
187
277
|
height: 8px;
|
|
188
278
|
}
|
|
189
279
|
*::-webkit-scrollbar-thumb {
|
|
190
|
-
background: var(--
|
|
280
|
+
background: var(--grove-border);
|
|
191
281
|
border-radius: 4px;
|
|
192
282
|
}
|
|
193
|
-
.dark *::-webkit-scrollbar-thumb {
|
|
194
|
-
background: var(--color-ink-800);
|
|
195
|
-
}
|
|
196
283
|
|
|
197
284
|
input,
|
|
198
285
|
select,
|
|
@@ -207,26 +294,47 @@
|
|
|
207
294
|
* ────────────────────────────────────────────────────────────────── */
|
|
208
295
|
.grove-prose {
|
|
209
296
|
max-width: 72ch;
|
|
210
|
-
color: var(--color-ink-
|
|
211
|
-
font-size:
|
|
297
|
+
color: var(--color-ink-800);
|
|
298
|
+
font-size: 15px;
|
|
212
299
|
line-height: 1.75;
|
|
213
300
|
}
|
|
214
301
|
|
|
215
302
|
.dark .grove-prose {
|
|
216
|
-
color: var(--color-ink-
|
|
303
|
+
color: var(--color-ink-300);
|
|
217
304
|
}
|
|
218
305
|
|
|
219
306
|
.grove-prose :where(h1, h2, h3, h4) {
|
|
220
307
|
margin: 1.75em 0 0.65em;
|
|
221
308
|
color: var(--color-ink-900);
|
|
222
|
-
font-weight:
|
|
223
|
-
|
|
309
|
+
font-weight: 500;
|
|
310
|
+
letter-spacing: -0.025em;
|
|
224
311
|
}
|
|
225
312
|
|
|
226
313
|
.dark .grove-prose :where(h1, h2, h3, h4) {
|
|
227
314
|
color: var(--color-ink-100);
|
|
228
315
|
}
|
|
229
316
|
|
|
317
|
+
.grove-prose h1 {
|
|
318
|
+
font-size: 1.875rem;
|
|
319
|
+
font-weight: 600;
|
|
320
|
+
line-height: 2.25rem;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.grove-prose h2 {
|
|
324
|
+
font-size: 1.25rem;
|
|
325
|
+
line-height: 1.75rem;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.grove-prose h3 {
|
|
329
|
+
font-size: 1.125rem;
|
|
330
|
+
line-height: 1.75rem;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.grove-prose h4 {
|
|
334
|
+
font-size: 1rem;
|
|
335
|
+
line-height: 1.5rem;
|
|
336
|
+
}
|
|
337
|
+
|
|
230
338
|
.grove-prose :where(p, ul, ol, pre, blockquote) {
|
|
231
339
|
margin: 1em 0;
|
|
232
340
|
}
|
|
@@ -236,9 +344,16 @@
|
|
|
236
344
|
}
|
|
237
345
|
|
|
238
346
|
.grove-prose a {
|
|
239
|
-
color: var(--color-
|
|
347
|
+
color: var(--color-ink-900);
|
|
348
|
+
font-weight: 500;
|
|
240
349
|
text-decoration: underline;
|
|
241
350
|
text-underline-offset: 0.18em;
|
|
351
|
+
text-decoration-color: var(--color-ink-500);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.dark .grove-prose a {
|
|
355
|
+
color: var(--color-ink-50);
|
|
356
|
+
text-decoration-color: var(--color-ink-400);
|
|
242
357
|
}
|
|
243
358
|
|
|
244
359
|
.grove-prose :where(code, pre) {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
const astroTheme = readFileSync(resolve(import.meta.dirname, "styles.css"), "utf8");
|
|
6
|
+
const scaffoldTheme = readFileSync(
|
|
7
|
+
resolve(import.meta.dirname, "../templates/default/src/styles/global.css"),
|
|
8
|
+
"utf8",
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
describe("Astro theme contract", () => {
|
|
12
|
+
it("keeps Starlight's light and dark foundation values", () => {
|
|
13
|
+
expect(astroTheme).toContain("--grove-background: oklch(100% 0 0)");
|
|
14
|
+
expect(astroTheme).toContain("--grove-foreground: var(--color-ink-950)");
|
|
15
|
+
expect(astroTheme).toContain("--grove-background: var(--color-ink-950)");
|
|
16
|
+
expect(astroTheme).toContain("--grove-foreground: var(--color-ink-50)");
|
|
17
|
+
expect(astroTheme).toContain("--color-ink-50: oklch(98.5% 0 0)");
|
|
18
|
+
expect(astroTheme).toContain("--color-ink-950: oklch(14.5% 0 0)");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("exposes runtime semantic colors to Tailwind", () => {
|
|
22
|
+
for (const token of [
|
|
23
|
+
"background",
|
|
24
|
+
"foreground",
|
|
25
|
+
"primary",
|
|
26
|
+
"secondary",
|
|
27
|
+
"muted",
|
|
28
|
+
"accent",
|
|
29
|
+
"border",
|
|
30
|
+
"card",
|
|
31
|
+
"popover",
|
|
32
|
+
]) {
|
|
33
|
+
expect(astroTheme).toContain(`--color-${token}: var(--grove-${token})`);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("uses the same system and mono font stacks as the docs", () => {
|
|
38
|
+
expect(astroTheme).toContain(
|
|
39
|
+
'--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,',
|
|
40
|
+
);
|
|
41
|
+
expect(astroTheme).toContain(
|
|
42
|
+
"--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,",
|
|
43
|
+
);
|
|
44
|
+
expect(astroTheme).toContain("font-family: var(--font-sans)");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("does not duplicate package-owned foundation tokens in the scaffold", () => {
|
|
48
|
+
expect(scaffoldTheme).not.toContain("--color-ink-");
|
|
49
|
+
expect(scaffoldTheme).not.toContain("font-family:");
|
|
50
|
+
expect(scaffoldTheme).not.toContain("color-scheme:");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -25,12 +25,13 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@astrojs/check": "^0.9.9",
|
|
28
|
-
"@grove-dev/astro": "0.2.
|
|
28
|
+
"@grove-dev/astro": "0.2.18",
|
|
29
29
|
"@grove-dev/cli": "0.2.16",
|
|
30
30
|
"@grove-dev/core": "0.2.16",
|
|
31
31
|
"@tailwindcss/vite": "^4.3.0",
|
|
32
32
|
"astro": "^6.4.4",
|
|
33
33
|
"marked": "^18.0.0",
|
|
34
|
+
"sanitize-html": "^2.17.5",
|
|
34
35
|
"tailwindcss": "^4.3.0",
|
|
35
36
|
"yaml": "^2.9.0"
|
|
36
37
|
}
|
|
@@ -2,61 +2,61 @@
|
|
|
2
2
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3
3
|
<url>
|
|
4
4
|
<loc>https://example.com/</loc>
|
|
5
|
-
<lastmod>2026-06-
|
|
5
|
+
<lastmod>2026-06-22T14:50:53.941Z</lastmod>
|
|
6
6
|
<changefreq>daily</changefreq>
|
|
7
7
|
<priority>1.0</priority>
|
|
8
8
|
</url>
|
|
9
9
|
<url>
|
|
10
10
|
<loc>https://example.com/projects</loc>
|
|
11
|
-
<lastmod>2026-06-
|
|
11
|
+
<lastmod>2026-06-22T14:50:53.941Z</lastmod>
|
|
12
12
|
<changefreq>daily</changefreq>
|
|
13
13
|
<priority>0.9</priority>
|
|
14
14
|
</url>
|
|
15
15
|
<url>
|
|
16
16
|
<loc>https://example.com/projects/coolify</loc>
|
|
17
|
-
<lastmod>2026-06-
|
|
17
|
+
<lastmod>2026-06-22T14:50:53.941Z</lastmod>
|
|
18
18
|
<changefreq>weekly</changefreq>
|
|
19
19
|
<priority>0.7</priority>
|
|
20
20
|
</url>
|
|
21
21
|
<url>
|
|
22
22
|
<loc>https://example.com/projects/gitea</loc>
|
|
23
|
-
<lastmod>2026-06-
|
|
23
|
+
<lastmod>2026-06-22T14:50:53.941Z</lastmod>
|
|
24
24
|
<changefreq>weekly</changefreq>
|
|
25
25
|
<priority>0.7</priority>
|
|
26
26
|
</url>
|
|
27
27
|
<url>
|
|
28
28
|
<loc>https://example.com/projects/hoppscotch</loc>
|
|
29
|
-
<lastmod>2026-06-
|
|
29
|
+
<lastmod>2026-06-22T14:50:53.941Z</lastmod>
|
|
30
30
|
<changefreq>weekly</changefreq>
|
|
31
31
|
<priority>0.7</priority>
|
|
32
32
|
</url>
|
|
33
33
|
<url>
|
|
34
34
|
<loc>https://example.com/projects/inventree</loc>
|
|
35
|
-
<lastmod>2026-06-
|
|
35
|
+
<lastmod>2026-06-22T14:50:53.941Z</lastmod>
|
|
36
36
|
<changefreq>weekly</changefreq>
|
|
37
37
|
<priority>0.7</priority>
|
|
38
38
|
</url>
|
|
39
39
|
<url>
|
|
40
40
|
<loc>https://example.com/projects/label-studio</loc>
|
|
41
|
-
<lastmod>2026-06-
|
|
41
|
+
<lastmod>2026-06-22T14:50:53.941Z</lastmod>
|
|
42
42
|
<changefreq>weekly</changefreq>
|
|
43
43
|
<priority>0.7</priority>
|
|
44
44
|
</url>
|
|
45
45
|
<url>
|
|
46
46
|
<loc>https://example.com/projects/logseq</loc>
|
|
47
|
-
<lastmod>2026-06-
|
|
47
|
+
<lastmod>2026-06-22T14:50:53.941Z</lastmod>
|
|
48
48
|
<changefreq>weekly</changefreq>
|
|
49
49
|
<priority>0.7</priority>
|
|
50
50
|
</url>
|
|
51
51
|
<url>
|
|
52
52
|
<loc>https://example.com/projects/private-gpt</loc>
|
|
53
|
-
<lastmod>2026-06-
|
|
53
|
+
<lastmod>2026-06-22T14:50:53.941Z</lastmod>
|
|
54
54
|
<changefreq>weekly</changefreq>
|
|
55
55
|
<priority>0.7</priority>
|
|
56
56
|
</url>
|
|
57
57
|
<url>
|
|
58
58
|
<loc>https://example.com/projects/wakapi</loc>
|
|
59
|
-
<lastmod>2026-06-
|
|
59
|
+
<lastmod>2026-06-22T14:50:53.941Z</lastmod>
|
|
60
60
|
<changefreq>weekly</changefreq>
|
|
61
61
|
<priority>0.7</priority>
|
|
62
62
|
</url>
|