@grove-dev/astro 0.2.18 → 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/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/Hero.astro +3 -3
- package/src/components/Icon.astro +4 -4
- package/src/index.ts +52 -1
- package/src/layouts/BaseLayout.astro +1 -1
- package/src/styles.css +184 -69
- package/src/theme.test.ts +52 -0
- package/templates/default/package.json +1 -1
- package/templates/default/public/llms-full.txt +1 -1
- package/templates/default/public/sitemap.xml +10 -10
- package/templates/default/src/pages/[slug]/index.astro +0 -29
- package/templates/default/src/styles/global.css +14 -39
- package/templates/default/tailwind.config.mjs +0 -130
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ import type { AstroIntegration } from "astro";
|
|
|
2
2
|
export * from "@grove-dev/core";
|
|
3
3
|
export * from "./lib/index.js";
|
|
4
4
|
export default function groveAstro(): AstroIntegration;
|
|
5
|
+
export declare const CONSUMER_GLOBAL_CSS_ID = "virtual:grove-consumer-global-css";
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAE9C,cAAc,iBAAiB,CAAC;AAMhC,cAAc,gBAAgB,CAAC;AAkB/B,MAAM,CAAC,OAAO,UAAU,UAAU,IAAI,gBAAgB,CAuDrD;AAID,eAAO,MAAM,sBAAsB,sCAAiC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,15 @@
|
|
|
16
16
|
* - Re-exports the generic `lib/` helpers (search, lenses, scores,
|
|
17
17
|
* repo, format, display, taxonomy-counts) under a single import
|
|
18
18
|
* path so consumers can `import { ... } from "@grove-dev/astro"`.
|
|
19
|
+
*
|
|
20
|
+
* Also auto-loads the consumer's `src/styles/global.css` when it
|
|
21
|
+
* exists. Without this hook the scaffold's global.css (which the
|
|
22
|
+
* CLI generates for theme overrides) sits on disk unused, and any
|
|
23
|
+
* custom theme tokens silently degrade to the package defaults —
|
|
24
|
+
* which is the classic "I changed the brand color in global.css
|
|
25
|
+
* and nothing happened" surprise.
|
|
19
26
|
*/
|
|
27
|
+
import { existsSync } from "node:fs";
|
|
20
28
|
import { fileURLToPath } from "node:url";
|
|
21
29
|
import { dirname, resolve } from "node:path";
|
|
22
30
|
export * from "@grove-dev/core";
|
|
@@ -33,16 +41,24 @@ const here = dirname(fileURLToPath(import.meta.url));
|
|
|
33
41
|
const srcRoot = resolve(here, "..", "src");
|
|
34
42
|
const componentsDir = resolve(srcRoot, "components");
|
|
35
43
|
const layoutsDir = resolve(srcRoot, "layouts");
|
|
44
|
+
// Virtual module id used to inject the consumer's `src/styles/global.css`.
|
|
45
|
+
// Resolved by the inline Vite plugin below to the absolute file path
|
|
46
|
+
// when the file exists, or to an empty module when it doesn't.
|
|
47
|
+
const CONSUMER_GLOBAL_CSS_VIRTUAL_ID = "virtual:grove-consumer-global-css";
|
|
48
|
+
const CONSUMER_GLOBAL_CSS_EMPTY_ID = "\0virtual:grove-consumer-global-css:empty";
|
|
36
49
|
export default function groveAstro() {
|
|
37
50
|
return {
|
|
38
51
|
name: "@grove-dev/astro",
|
|
39
52
|
hooks: {
|
|
40
|
-
"astro:config:setup": ({ updateConfig }) => {
|
|
53
|
+
"astro:config:setup": ({ config, updateConfig, injectScript }) => {
|
|
41
54
|
// Alias the components/layouts source directories so
|
|
42
55
|
// consumer builds can import from
|
|
43
56
|
// @grove-dev/astro/components/ItemCard.astro
|
|
44
57
|
// without us shipping a glob-shaped `exports` map that
|
|
45
58
|
// Vite/Rollup does not expand reliably.
|
|
59
|
+
const consumerRoot = fileURLToPath(config.root);
|
|
60
|
+
const globalCssPath = resolve(consumerRoot, "src/styles/global.css");
|
|
61
|
+
const globalCssExists = existsSync(globalCssPath);
|
|
46
62
|
updateConfig({
|
|
47
63
|
vite: {
|
|
48
64
|
resolve: {
|
|
@@ -51,10 +67,37 @@ export default function groveAstro() {
|
|
|
51
67
|
"@grove-dev/astro/layouts": layoutsDir,
|
|
52
68
|
},
|
|
53
69
|
},
|
|
70
|
+
plugins: [
|
|
71
|
+
{
|
|
72
|
+
name: "grove:consumer-global-css-resolver",
|
|
73
|
+
resolveId(id) {
|
|
74
|
+
if (id === CONSUMER_GLOBAL_CSS_VIRTUAL_ID) {
|
|
75
|
+
return globalCssExists
|
|
76
|
+
? globalCssPath
|
|
77
|
+
: CONSUMER_GLOBAL_CSS_EMPTY_ID;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
},
|
|
81
|
+
load(id) {
|
|
82
|
+
if (id === CONSUMER_GLOBAL_CSS_EMPTY_ID) {
|
|
83
|
+
return "";
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
54
89
|
},
|
|
55
90
|
});
|
|
91
|
+
// Always inject the virtual module so the build is symmetric
|
|
92
|
+
// regardless of whether the consumer has a global.css yet.
|
|
93
|
+
// Vite's CSS pipeline (with @tailwindcss/vite) handles the
|
|
94
|
+
// resulting import as a stylesheet — no JS bundle weight.
|
|
95
|
+
injectScript("page-ssr", `import "${CONSUMER_GLOBAL_CSS_VIRTUAL_ID}";`);
|
|
56
96
|
},
|
|
57
97
|
},
|
|
58
98
|
};
|
|
59
99
|
}
|
|
100
|
+
// Re-export the virtual module id so consumers (and tests) can
|
|
101
|
+
// reason about it without copy-pasting the literal string.
|
|
102
|
+
export const CONSUMER_GLOBAL_CSS_ID = CONSUMER_GLOBAL_CSS_VIRTUAL_ID;
|
|
60
103
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAG7C,cAAc,iBAAiB,CAAC;AAEhC,+DAA+D;AAC/D,mEAAmE;AACnE,iEAAiE;AACjE,mCAAmC;AACnC,cAAc,gBAAgB,CAAC;AAE/B,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,+DAA+D;AAC/D,0DAA0D;AAC1D,8DAA8D;AAC9D,sCAAsC;AACtC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3C,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;AACrD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAE/C,2EAA2E;AAC3E,qEAAqE;AACrE,+DAA+D;AAC/D,MAAM,8BAA8B,GAAG,mCAAmC,CAAC;AAC3E,MAAM,4BAA4B,GAChC,2CAA2C,CAAC;AAE9C,MAAM,CAAC,OAAO,UAAU,UAAU;IAChC,OAAO;QACL,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE;YACL,oBAAoB,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,EAAE;gBAC/D,qDAAqD;gBACrD,kCAAkC;gBAClC,+CAA+C;gBAC/C,uDAAuD;gBACvD,wCAAwC;gBACxC,MAAM,YAAY,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAChD,MAAM,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,uBAAuB,CAAC,CAAC;gBACrE,MAAM,eAAe,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC;gBAElD,YAAY,CAAC;oBACX,IAAI,EAAE;wBACJ,OAAO,EAAE;4BACP,KAAK,EAAE;gCACL,6BAA6B,EAAE,aAAa;gCAC5C,0BAA0B,EAAE,UAAU;6BACvC;yBACF;wBACD,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,oCAAoC;gCAC1C,SAAS,CAAC,EAAE;oCACV,IAAI,EAAE,KAAK,8BAA8B,EAAE,CAAC;wCAC1C,OAAO,eAAe;4CACpB,CAAC,CAAC,aAAa;4CACf,CAAC,CAAC,4BAA4B,CAAC;oCACnC,CAAC;oCACD,OAAO,IAAI,CAAC;gCACd,CAAC;gCACD,IAAI,CAAC,EAAE;oCACL,IAAI,EAAE,KAAK,4BAA4B,EAAE,CAAC;wCACxC,OAAO,EAAE,CAAC;oCACZ,CAAC;oCACD,OAAO,IAAI,CAAC;gCACd,CAAC;6BACF;yBACF;qBACF;iBACF,CAAC,CAAC;gBAEH,6DAA6D;gBAC7D,2DAA2D;gBAC3D,2DAA2D;gBAC3D,0DAA0D;gBAC1D,YAAY,CACV,UAAU,EACV,WAAW,8BAA8B,IAAI,CAC9C,CAAC;YACJ,CAAC;SACF;KACF,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,2DAA2D;AAC3D,MAAM,CAAC,MAAM,sBAAsB,GAAG,8BAA8B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.test.d.ts","sourceRoot":"","sources":["../src/theme.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
const astroTheme = readFileSync(resolve(import.meta.dirname, "styles.css"), "utf8");
|
|
5
|
+
const scaffoldTheme = readFileSync(resolve(import.meta.dirname, "../templates/default/src/styles/global.css"), "utf8");
|
|
6
|
+
describe("Astro theme contract", () => {
|
|
7
|
+
it("keeps Starlight's light and dark foundation values", () => {
|
|
8
|
+
expect(astroTheme).toContain("--grove-background: oklch(100% 0 0)");
|
|
9
|
+
expect(astroTheme).toContain("--grove-foreground: var(--color-ink-950)");
|
|
10
|
+
expect(astroTheme).toContain("--grove-background: var(--color-ink-950)");
|
|
11
|
+
expect(astroTheme).toContain("--grove-foreground: var(--color-ink-50)");
|
|
12
|
+
expect(astroTheme).toContain("--color-ink-50: oklch(98.5% 0 0)");
|
|
13
|
+
expect(astroTheme).toContain("--color-ink-950: oklch(14.5% 0 0)");
|
|
14
|
+
});
|
|
15
|
+
it("exposes runtime semantic colors to Tailwind", () => {
|
|
16
|
+
for (const token of [
|
|
17
|
+
"background",
|
|
18
|
+
"foreground",
|
|
19
|
+
"primary",
|
|
20
|
+
"secondary",
|
|
21
|
+
"muted",
|
|
22
|
+
"accent",
|
|
23
|
+
"border",
|
|
24
|
+
"card",
|
|
25
|
+
"popover",
|
|
26
|
+
]) {
|
|
27
|
+
expect(astroTheme).toContain(`--color-${token}: var(--grove-${token})`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
it("uses the same system and mono font stacks as the docs", () => {
|
|
31
|
+
expect(astroTheme).toContain('--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,');
|
|
32
|
+
expect(astroTheme).toContain("--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,");
|
|
33
|
+
expect(astroTheme).toContain("font-family: var(--font-sans)");
|
|
34
|
+
});
|
|
35
|
+
it("does not duplicate package-owned foundation tokens in the scaffold", () => {
|
|
36
|
+
expect(scaffoldTheme).not.toContain("--color-ink-");
|
|
37
|
+
expect(scaffoldTheme).not.toContain("font-family:");
|
|
38
|
+
expect(scaffoldTheme).not.toContain("color-scheme:");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=theme.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.test.js","sourceRoot":"","sources":["../src/theme.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC;AACpF,MAAM,aAAa,GAAG,YAAY,CAChC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,4CAA4C,CAAC,EAC1E,MAAM,CACP,CAAC;AAEF,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,qCAAqC,CAAC,CAAC;QACpE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,0CAA0C,CAAC,CAAC;QACzE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,0CAA0C,CAAC,CAAC;QACzE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,yCAAyC,CAAC,CAAC;QACxE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,kCAAkC,CAAC,CAAC;QACjE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,mCAAmC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,KAAK,MAAM,KAAK,IAAI;YAClB,YAAY;YACZ,YAAY;YACZ,SAAS;YACT,WAAW;YACX,OAAO;YACP,QAAQ;YACR,QAAQ;YACR,MAAM;YACN,SAAS;SACV,EAAE,CAAC;YACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,WAAW,KAAK,iBAAiB,KAAK,GAAG,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAC1B,qEAAqE,CACtE,CAAC;QACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAC1B,qEAAqE,CACtE,CAAC;QACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,+BAA+B,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACpD,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACpD,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -114,9 +114,9 @@ const trustStats: { value: string; label: string }[] = [
|
|
|
114
114
|
class="pointer-events-none absolute inset-0 opacity-60 dark:opacity-40"
|
|
115
115
|
style="
|
|
116
116
|
background-image:
|
|
117
|
-
radial-gradient(60% 50% at 15% 20%,
|
|
118
|
-
radial-gradient(50% 40% at 85% 10%,
|
|
119
|
-
radial-gradient(40% 40% at 50% 90%,
|
|
117
|
+
radial-gradient(60% 50% at 15% 20%, color-mix(in oklab, var(--color-ink-300) 34%, transparent), transparent 60%),
|
|
118
|
+
radial-gradient(50% 40% at 85% 10%, color-mix(in oklab, var(--color-ink-200) 28%, transparent), transparent 60%),
|
|
119
|
+
radial-gradient(40% 40% at 50% 90%, color-mix(in oklab, var(--color-ink-400) 18%, transparent), transparent 60%);
|
|
120
120
|
"
|
|
121
121
|
></div>
|
|
122
122
|
|
|
@@ -185,16 +185,16 @@ const a11y: Record<string, string> = title
|
|
|
185
185
|
align-items: center;
|
|
186
186
|
justify-content: center;
|
|
187
187
|
border-radius: 0.25rem;
|
|
188
|
-
background: var(--color-ink-100,
|
|
189
|
-
color: var(--color-ink-700,
|
|
188
|
+
background: var(--color-ink-100, oklch(97% 0 0));
|
|
189
|
+
color: var(--color-ink-700, oklch(37.1% 0 0));
|
|
190
190
|
font-weight: 600;
|
|
191
191
|
line-height: 1;
|
|
192
192
|
text-transform: uppercase;
|
|
193
193
|
user-select: none;
|
|
194
194
|
}
|
|
195
195
|
:global(.dark) .grove-icon-fallback {
|
|
196
|
-
background: var(--color-ink-800,
|
|
197
|
-
color: var(--color-ink-200,
|
|
196
|
+
background: var(--color-ink-800, oklch(26.9% 0 0));
|
|
197
|
+
color: var(--color-ink-200, oklch(92.2% 0 0));
|
|
198
198
|
}
|
|
199
199
|
</style>
|
|
200
200
|
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,15 @@
|
|
|
16
16
|
* - Re-exports the generic `lib/` helpers (search, lenses, scores,
|
|
17
17
|
* repo, format, display, taxonomy-counts) under a single import
|
|
18
18
|
* path so consumers can `import { ... } from "@grove-dev/astro"`.
|
|
19
|
+
*
|
|
20
|
+
* Also auto-loads the consumer's `src/styles/global.css` when it
|
|
21
|
+
* exists. Without this hook the scaffold's global.css (which the
|
|
22
|
+
* CLI generates for theme overrides) sits on disk unused, and any
|
|
23
|
+
* custom theme tokens silently degrade to the package defaults —
|
|
24
|
+
* which is the classic "I changed the brand color in global.css
|
|
25
|
+
* and nothing happened" surprise.
|
|
19
26
|
*/
|
|
27
|
+
import { existsSync } from "node:fs";
|
|
20
28
|
import { fileURLToPath } from "node:url";
|
|
21
29
|
import { dirname, resolve } from "node:path";
|
|
22
30
|
import type { AstroIntegration } from "astro";
|
|
@@ -38,16 +46,27 @@ const srcRoot = resolve(here, "..", "src");
|
|
|
38
46
|
const componentsDir = resolve(srcRoot, "components");
|
|
39
47
|
const layoutsDir = resolve(srcRoot, "layouts");
|
|
40
48
|
|
|
49
|
+
// Virtual module id used to inject the consumer's `src/styles/global.css`.
|
|
50
|
+
// Resolved by the inline Vite plugin below to the absolute file path
|
|
51
|
+
// when the file exists, or to an empty module when it doesn't.
|
|
52
|
+
const CONSUMER_GLOBAL_CSS_VIRTUAL_ID = "virtual:grove-consumer-global-css";
|
|
53
|
+
const CONSUMER_GLOBAL_CSS_EMPTY_ID =
|
|
54
|
+
"\0virtual:grove-consumer-global-css:empty";
|
|
55
|
+
|
|
41
56
|
export default function groveAstro(): AstroIntegration {
|
|
42
57
|
return {
|
|
43
58
|
name: "@grove-dev/astro",
|
|
44
59
|
hooks: {
|
|
45
|
-
"astro:config:setup": ({ updateConfig }) => {
|
|
60
|
+
"astro:config:setup": ({ config, updateConfig, injectScript }) => {
|
|
46
61
|
// Alias the components/layouts source directories so
|
|
47
62
|
// consumer builds can import from
|
|
48
63
|
// @grove-dev/astro/components/ItemCard.astro
|
|
49
64
|
// without us shipping a glob-shaped `exports` map that
|
|
50
65
|
// Vite/Rollup does not expand reliably.
|
|
66
|
+
const consumerRoot = fileURLToPath(config.root);
|
|
67
|
+
const globalCssPath = resolve(consumerRoot, "src/styles/global.css");
|
|
68
|
+
const globalCssExists = existsSync(globalCssPath);
|
|
69
|
+
|
|
51
70
|
updateConfig({
|
|
52
71
|
vite: {
|
|
53
72
|
resolve: {
|
|
@@ -56,9 +75,41 @@ export default function groveAstro(): AstroIntegration {
|
|
|
56
75
|
"@grove-dev/astro/layouts": layoutsDir,
|
|
57
76
|
},
|
|
58
77
|
},
|
|
78
|
+
plugins: [
|
|
79
|
+
{
|
|
80
|
+
name: "grove:consumer-global-css-resolver",
|
|
81
|
+
resolveId(id) {
|
|
82
|
+
if (id === CONSUMER_GLOBAL_CSS_VIRTUAL_ID) {
|
|
83
|
+
return globalCssExists
|
|
84
|
+
? globalCssPath
|
|
85
|
+
: CONSUMER_GLOBAL_CSS_EMPTY_ID;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
},
|
|
89
|
+
load(id) {
|
|
90
|
+
if (id === CONSUMER_GLOBAL_CSS_EMPTY_ID) {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
],
|
|
59
97
|
},
|
|
60
98
|
});
|
|
99
|
+
|
|
100
|
+
// Always inject the virtual module so the build is symmetric
|
|
101
|
+
// regardless of whether the consumer has a global.css yet.
|
|
102
|
+
// Vite's CSS pipeline (with @tailwindcss/vite) handles the
|
|
103
|
+
// resulting import as a stylesheet — no JS bundle weight.
|
|
104
|
+
injectScript(
|
|
105
|
+
"page-ssr",
|
|
106
|
+
`import "${CONSUMER_GLOBAL_CSS_VIRTUAL_ID}";`,
|
|
107
|
+
);
|
|
61
108
|
},
|
|
62
109
|
},
|
|
63
110
|
};
|
|
64
111
|
}
|
|
112
|
+
|
|
113
|
+
// Re-export the virtual module id so consumers (and tests) can
|
|
114
|
+
// reason about it without copy-pasting the literal string.
|
|
115
|
+
export const CONSUMER_GLOBAL_CSS_ID = CONSUMER_GLOBAL_CSS_VIRTUAL_ID;
|
|
@@ -186,7 +186,7 @@ const favicon =
|
|
|
186
186
|
})();
|
|
187
187
|
</script>
|
|
188
188
|
</head>
|
|
189
|
-
<body class="min-h-screen bg-
|
|
189
|
+
<body class="min-h-screen bg-background text-foreground">
|
|
190
190
|
<slot name="header">
|
|
191
191
|
<Header
|
|
192
192
|
site={site}
|
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
|
+
});
|
|
@@ -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>
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* - RefinePanel (Stack / Platform / Category / License / Sort)
|
|
8
8
|
* - RecordSection rows (full record cards; the `ItemSection` V0
|
|
9
9
|
* name was renamed to `RecordSection` in V1)
|
|
10
|
-
* - ExploreByCategory / ExploreByStack (compact pill lists)
|
|
11
10
|
*
|
|
12
11
|
* Generic: the URL slug (e.g. `/projects/`, `/resources/`,
|
|
13
12
|
* `/entities/`) is derived from the blueprint via `getStaticPaths`,
|
|
@@ -28,14 +27,10 @@ import SmartLensTabs from "@grove-dev/astro/components/SmartLensTabs.astro";
|
|
|
28
27
|
import RefinePanel from "@grove-dev/astro/components/RefinePanel.astro";
|
|
29
28
|
import IndexRow from "@grove-dev/astro/components/IndexRow.astro";
|
|
30
29
|
import Pagination from "@grove-dev/astro/components/Pagination.astro";
|
|
31
|
-
import ExploreByCategory from "@grove-dev/astro/components/ExploreByCategory.astro";
|
|
32
|
-
import ExploreByStack from "@grove-dev/astro/components/ExploreByStack.astro";
|
|
33
30
|
import {
|
|
34
31
|
activeFilterChips,
|
|
35
32
|
applySort,
|
|
36
33
|
buildFacets,
|
|
37
|
-
countByCategory,
|
|
38
|
-
countByStack,
|
|
39
34
|
effectivePage,
|
|
40
35
|
effectiveSort,
|
|
41
36
|
filterRecords,
|
|
@@ -70,16 +65,6 @@ const chips = activeFilterChips(filters);
|
|
|
70
65
|
const title = `Browse ${slug} — ${siteConfig.name}`;
|
|
71
66
|
const description = `A searchable directory of ${itemLabelPlural()} for ${siteConfig.name}.`;
|
|
72
67
|
|
|
73
|
-
// Compute the top categories and stacks for the explore pills
|
|
74
|
-
const categoryEntries = [...countByCategory(items as Parameters<typeof countByCategory>[0]).entries()]
|
|
75
|
-
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
76
|
-
.slice(0, 12)
|
|
77
|
-
.map(([name, count]) => ({ name, slug: name, count }));
|
|
78
|
-
const stackEntries = [...countByStack(items as Parameters<typeof countByStack>[0]).entries()]
|
|
79
|
-
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
80
|
-
.slice(0, 18)
|
|
81
|
-
.map(([name, count]) => ({ name, slug: name, count }));
|
|
82
|
-
|
|
83
68
|
function hrefForPage(target: number): string {
|
|
84
69
|
const next: IndexFilters = { ...filters, page: target };
|
|
85
70
|
const query = searchParamsFromFilters(next).toString();
|
|
@@ -207,20 +192,6 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
|
|
|
207
192
|
</div>
|
|
208
193
|
</section>
|
|
209
194
|
|
|
210
|
-
{categoryEntries.length > 0 && (
|
|
211
|
-
<ExploreByCategory
|
|
212
|
-
categories={categoryEntries}
|
|
213
|
-
pathPrefix={`/${slug}`}
|
|
214
|
-
/>
|
|
215
|
-
)}
|
|
216
|
-
|
|
217
|
-
{stackEntries.length > 0 && (
|
|
218
|
-
<ExploreByStack
|
|
219
|
-
stacks={stackEntries}
|
|
220
|
-
pathPrefix={`/${slug}`}
|
|
221
|
-
/>
|
|
222
|
-
)}
|
|
223
|
-
|
|
224
195
|
<section class="grove-section-tight">
|
|
225
196
|
<div class="grove-container-wide mx-auto px-5 sm:px-7">
|
|
226
197
|
<header class="mb-6">
|
|
@@ -5,26 +5,22 @@
|
|
|
5
5
|
* at scaffold time; we expose the same names as CSS variables so
|
|
6
6
|
* consumers can layer their own brand colors in without forking.
|
|
7
7
|
*
|
|
8
|
-
* The
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* The package owns the Starlight-aligned palette, semantic theme tokens,
|
|
9
|
+
* font stacks, and light/dark base styles. This consumer sheet contains
|
|
10
|
+
* only scaffold-level overrides so those foundations cannot drift when
|
|
11
|
+
* the package theme changes.
|
|
12
|
+
*
|
|
13
|
+
* NOTE: do NOT add `@custom-variant dark` here. `@grove-dev/astro`'s
|
|
14
|
+
* `styles.css` already declares the `.dark`-class variant, and
|
|
15
|
+
* Tailwind v4 only honors one definition per build — adding a
|
|
16
|
+
* second one in this consumer file has been observed to confuse
|
|
17
|
+
* the variant resolution and produce utilities that key off
|
|
18
|
+
* `prefers-color-scheme` instead of the `.dark` class on <html>.
|
|
19
|
+
* The net effect: `<html class="dark">` is set but the page stays
|
|
20
|
+
* in light mode because the `dark:` utilities never match.
|
|
13
21
|
*/
|
|
14
22
|
@theme {
|
|
15
|
-
--color-
|
|
16
|
-
--color-ink-100: #f4f4f5;
|
|
17
|
-
--color-ink-200: #e4e4e7;
|
|
18
|
-
--color-ink-300: #d4d4d8;
|
|
19
|
-
--color-ink-400: #a1a1aa;
|
|
20
|
-
--color-ink-500: #71717a;
|
|
21
|
-
--color-ink-600: #52525b;
|
|
22
|
-
--color-ink-700: #3f3f46;
|
|
23
|
-
--color-ink-800: #27272a;
|
|
24
|
-
--color-ink-900: #18181b;
|
|
25
|
-
--color-ink-950: #09090b;
|
|
26
|
-
|
|
27
|
-
--color-grove-primary: #16a34a;
|
|
23
|
+
--color-grove-primary: var(--color-foreground);
|
|
28
24
|
}
|
|
29
25
|
|
|
30
26
|
@layer base {
|
|
@@ -33,26 +29,5 @@
|
|
|
33
29
|
--grove-radius: 0.5rem;
|
|
34
30
|
--grove-container-width: 72rem;
|
|
35
31
|
|
|
36
|
-
color-scheme: light dark;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
html {
|
|
40
|
-
-webkit-text-size-adjust: 100%;
|
|
41
|
-
-webkit-font-smoothing: antialiased;
|
|
42
|
-
-moz-osx-font-smoothing: grayscale;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
body {
|
|
46
|
-
background: #ffffff;
|
|
47
|
-
color: var(--color-ink-900);
|
|
48
|
-
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
|
|
49
|
-
"Helvetica Neue", Arial, sans-serif;
|
|
50
|
-
font-size: 15px;
|
|
51
|
-
line-height: 1.55;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
body.dark {
|
|
55
|
-
background: var(--color-ink-950);
|
|
56
|
-
color: var(--color-ink-100);
|
|
57
32
|
}
|
|
58
33
|
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/** @type {import('tailwindcss').Config} */
|
|
2
|
-
export default {
|
|
3
|
-
content: ['./src/**/*.{astro,html,js,jsx,ts,tsx,md,mdx}'],
|
|
4
|
-
// Use a `.dark` class on <html> (toggled by the theme script in
|
|
5
|
-
// the document head) rather than the OS media query, so a manual
|
|
6
|
-
// theme switcher can override the user's system preference.
|
|
7
|
-
darkMode: 'class',
|
|
8
|
-
theme: {
|
|
9
|
-
// Disable default palette so we only ship our own neutral / accent
|
|
10
|
-
// tokens. Keeps the bundle small and forces a unified look.
|
|
11
|
-
colors: {
|
|
12
|
-
transparent: 'transparent',
|
|
13
|
-
current: 'currentColor',
|
|
14
|
-
white: '#ffffff',
|
|
15
|
-
black: '#000000',
|
|
16
|
-
ink: {
|
|
17
|
-
50: '#fafafa',
|
|
18
|
-
100: '#f4f4f5',
|
|
19
|
-
200: '#e4e4e7',
|
|
20
|
-
300: '#d4d4d8',
|
|
21
|
-
400: '#a1a1aa',
|
|
22
|
-
500: '#71717a',
|
|
23
|
-
600: '#52525b',
|
|
24
|
-
700: '#3f3f46',
|
|
25
|
-
800: '#27272a',
|
|
26
|
-
900: '#18181b',
|
|
27
|
-
950: '#09090b',
|
|
28
|
-
},
|
|
29
|
-
accent: {
|
|
30
|
-
DEFAULT: '#1f6feb',
|
|
31
|
-
muted: '#388bfd1a',
|
|
32
|
-
},
|
|
33
|
-
emerald: {
|
|
34
|
-
50: '#ecfdf5',
|
|
35
|
-
300: '#6ee7b7',
|
|
36
|
-
400: '#34d399',
|
|
37
|
-
700: '#047857',
|
|
38
|
-
800: '#065f46',
|
|
39
|
-
950: '#022c22',
|
|
40
|
-
},
|
|
41
|
-
orange: {
|
|
42
|
-
50: '#fff7ed',
|
|
43
|
-
300: '#fdba74',
|
|
44
|
-
400: '#fb923c',
|
|
45
|
-
700: '#c2410c',
|
|
46
|
-
800: '#9a3412',
|
|
47
|
-
950: '#431407',
|
|
48
|
-
},
|
|
49
|
-
blue: {
|
|
50
|
-
50: '#eff6ff',
|
|
51
|
-
300: '#93c5fd',
|
|
52
|
-
400: '#60a5fa',
|
|
53
|
-
700: '#1d4ed8',
|
|
54
|
-
800: '#1e40af',
|
|
55
|
-
950: '#172554',
|
|
56
|
-
},
|
|
57
|
-
amber: {
|
|
58
|
-
50: '#fffbeb',
|
|
59
|
-
300: '#fcd34d',
|
|
60
|
-
400: '#fbbf24',
|
|
61
|
-
700: '#b45309',
|
|
62
|
-
800: '#92400e',
|
|
63
|
-
950: '#451a03',
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
extend: {
|
|
67
|
-
fontFamily: {
|
|
68
|
-
sans: [
|
|
69
|
-
'ui-sans-serif',
|
|
70
|
-
'system-ui',
|
|
71
|
-
'-apple-system',
|
|
72
|
-
'BlinkMacSystemFont',
|
|
73
|
-
'"Segoe UI"',
|
|
74
|
-
'Roboto',
|
|
75
|
-
'"Helvetica Neue"',
|
|
76
|
-
'Arial',
|
|
77
|
-
'sans-serif',
|
|
78
|
-
],
|
|
79
|
-
mono: [
|
|
80
|
-
'ui-monospace',
|
|
81
|
-
'SFMono-Regular',
|
|
82
|
-
'Menlo',
|
|
83
|
-
'Monaco',
|
|
84
|
-
'Consolas',
|
|
85
|
-
'"Liberation Mono"',
|
|
86
|
-
'"Courier New"',
|
|
87
|
-
'monospace',
|
|
88
|
-
],
|
|
89
|
-
},
|
|
90
|
-
fontSize: {
|
|
91
|
-
'2xs': ['0.6875rem', { lineHeight: '1rem' }],
|
|
92
|
-
},
|
|
93
|
-
maxWidth: {
|
|
94
|
-
container: '1140px',
|
|
95
|
-
wide: '1400px',
|
|
96
|
-
narrow: '720px',
|
|
97
|
-
},
|
|
98
|
-
borderRadius: {
|
|
99
|
-
DEFAULT: '0.375rem',
|
|
100
|
-
lg: '0.5rem',
|
|
101
|
-
xl: '0.75rem',
|
|
102
|
-
},
|
|
103
|
-
keyframes: {
|
|
104
|
-
'fade-in-up': {
|
|
105
|
-
'0%': { opacity: '0', transform: 'translateY(8px)' },
|
|
106
|
-
'100%': { opacity: '1', transform: 'translateY(0)' },
|
|
107
|
-
},
|
|
108
|
-
'fade-in': {
|
|
109
|
-
'0%': { opacity: '0' },
|
|
110
|
-
'100%': { opacity: '1' },
|
|
111
|
-
},
|
|
112
|
-
'gradient-shift': {
|
|
113
|
-
'0%, 100%': { 'background-position': '0% 50%' },
|
|
114
|
-
'50%': { 'background-position': '100% 50%' },
|
|
115
|
-
},
|
|
116
|
-
'subtle-bob': {
|
|
117
|
-
'0%, 100%': { transform: 'translateY(0)' },
|
|
118
|
-
'50%': { transform: 'translateY(-2px)' },
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
animation: {
|
|
122
|
-
'fade-in-up': 'fade-in-up 0.6s ease-out both',
|
|
123
|
-
'fade-in': 'fade-in 0.8s ease-out both',
|
|
124
|
-
'gradient-shift': 'gradient-shift 8s ease-in-out infinite',
|
|
125
|
-
'subtle-bob': 'subtle-bob 3s ease-in-out infinite',
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
plugins: [],
|
|
130
|
-
};
|