@grove-dev/astro 0.2.18 → 0.2.20

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.
Files changed (48) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +44 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/theme.test.d.ts +2 -0
  6. package/dist/theme.test.d.ts.map +1 -0
  7. package/dist/theme.test.js +55 -0
  8. package/dist/theme.test.js.map +1 -0
  9. package/package.json +1 -1
  10. package/src/components/CategoryGrid.astro +2 -11
  11. package/src/components/ContributorsGrid.astro +2 -11
  12. package/src/components/CurationGrid.astro +1 -1
  13. package/src/components/DecisionRow.astro +1 -1
  14. package/src/components/ExploreByCategory.astro +3 -3
  15. package/src/components/ExploreByStack.astro +3 -3
  16. package/src/components/FilterGroupMenu.astro +5 -5
  17. package/src/components/Hero.astro +11 -11
  18. package/src/components/Icon.astro +6 -33
  19. package/src/components/IndexRow.astro +5 -5
  20. package/src/components/ItemCard.astro +5 -16
  21. package/src/components/MinimalAbout.astro +2 -2
  22. package/src/components/OriginalCollection.astro +4 -4
  23. package/src/components/Pagination.astro +2 -2
  24. package/src/components/RecordSection.astro +1 -1
  25. package/src/components/RefinePanel.astro +1 -1
  26. package/src/components/SmartLensTabs.astro +3 -3
  27. package/src/components/StackGrid.astro +6 -15
  28. package/src/components/WhyThisExists.astro +3 -3
  29. package/src/index.ts +52 -1
  30. package/src/layouts/BaseLayout.astro +1 -1
  31. package/src/layouts/Container.astro +4 -4
  32. package/src/layouts/Footer.astro +3 -3
  33. package/src/layouts/Header.astro +5 -5
  34. package/src/layouts/ThemeToggle.astro +1 -1
  35. package/src/styles.css +217 -408
  36. package/src/theme.test.ts +67 -0
  37. package/templates/default/package.json +1 -1
  38. package/templates/default/public/icons/stacks/rag.svg +7 -0
  39. package/templates/default/public/llms-full.txt +1 -1
  40. package/templates/default/public/sitemap.xml +10 -10
  41. package/templates/default/src/pages/404.astro +5 -5
  42. package/templates/default/src/pages/[slug]/[recordSlug].astro +27 -31
  43. package/templates/default/src/pages/[slug]/index.astro +8 -37
  44. package/templates/default/src/pages/about.astro +9 -9
  45. package/templates/default/src/pages/contributors.astro +8 -17
  46. package/templates/default/src/pages/submit.astro +23 -31
  47. package/templates/default/src/styles/global.css +14 -39
  48. package/templates/default/tailwind.config.mjs +0 -130
@@ -0,0 +1,67 @@
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
+ const tailwindMarkup = [
11
+ "components/ItemCard.astro",
12
+ "layouts/Header.astro",
13
+ "../templates/default/src/pages/submit.astro",
14
+ ].map((file) => readFileSync(resolve(import.meta.dirname, file), "utf8")).join("\n");
15
+
16
+ describe("Astro theme contract", () => {
17
+ it("keeps Starlight's light and dark foundation values", () => {
18
+ expect(astroTheme).toContain("--grove-background: oklch(100% 0 0)");
19
+ expect(astroTheme).toContain("--grove-foreground: var(--color-ink-950)");
20
+ expect(astroTheme).toContain("--grove-background: var(--color-ink-950)");
21
+ expect(astroTheme).toContain("--grove-foreground: var(--color-ink-50)");
22
+ expect(astroTheme).toContain("--color-ink-50: oklch(98.5% 0 0)");
23
+ expect(astroTheme).toContain("--color-ink-950: oklch(14.5% 0 0)");
24
+ });
25
+
26
+ it("exposes runtime semantic colors to Tailwind", () => {
27
+ for (const token of [
28
+ "background",
29
+ "foreground",
30
+ "primary",
31
+ "secondary",
32
+ "muted",
33
+ "accent",
34
+ "border",
35
+ "card",
36
+ "popover",
37
+ ]) {
38
+ expect(astroTheme).toContain(`--color-${token}: var(--grove-${token})`);
39
+ }
40
+ });
41
+
42
+ it("uses the same system and mono font stacks as the docs", () => {
43
+ expect(astroTheme).toContain(
44
+ '--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,',
45
+ );
46
+ expect(astroTheme).toContain(
47
+ "--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,",
48
+ );
49
+ expect(astroTheme).toContain("font-family: var(--font-sans)");
50
+ });
51
+
52
+ it("does not duplicate package-owned foundation tokens in the scaffold", () => {
53
+ expect(scaffoldTheme).not.toContain("--color-ink-");
54
+ expect(scaffoldTheme).not.toContain("font-family:");
55
+ expect(scaffoldTheme).not.toContain("color-scheme:");
56
+ });
57
+
58
+ it("styles components in HTML with Starlight-aligned Tailwind utilities", () => {
59
+ expect(tailwindMarkup).toContain("rounded-[calc(var(--radius)+0.25rem)]");
60
+ expect(tailwindMarkup).toContain("rounded-full");
61
+ expect(tailwindMarkup).toContain("min-h-5");
62
+ expect(tailwindMarkup).toContain("h-8");
63
+ expect(tailwindMarkup).toContain("focus-visible:ring-3");
64
+ expect(astroTheme).not.toContain(".grove-card");
65
+ expect(astroTheme).not.toContain(".grove-btn");
66
+ });
67
+ });
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@astrojs/check": "^0.9.9",
28
- "@grove-dev/astro": "0.2.16",
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",
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
2
+ <rect width="100" height="100" rx="18" fill="#0F172A"/>
3
+ <path d="M29 28h42v13H29zM29 59h42v13H29z" stroke="#38BDF8" stroke-width="4" stroke-linejoin="round"/>
4
+ <path d="M50 41v18M42 50h16" stroke="#A78BFA" stroke-width="4" stroke-linecap="round"/>
5
+ <circle cx="36" cy="34.5" r="2.5" fill="#38BDF8"/>
6
+ <circle cx="36" cy="65.5" r="2.5" fill="#38BDF8"/>
7
+ </svg>
@@ -1,6 +1,6 @@
1
1
  # example.com — full directory
2
2
 
3
- Generated: 2026-06-17T13:40:59.793Z
3
+ Generated: 2026-06-22T16:19:40.464Z
4
4
  Records: 8
5
5
 
6
6
  ---
@@ -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-17T13:40:59.064Z</lastmod>
5
+ <lastmod>2026-06-22T16:19:39.853Z</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-17T13:40:59.064Z</lastmod>
11
+ <lastmod>2026-06-22T16:19:39.853Z</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-17T13:40:59.064Z</lastmod>
17
+ <lastmod>2026-06-22T16:19:39.853Z</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-17T13:40:59.064Z</lastmod>
23
+ <lastmod>2026-06-22T16:19:39.853Z</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-17T13:40:59.064Z</lastmod>
29
+ <lastmod>2026-06-22T16:19:39.853Z</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-17T13:40:59.064Z</lastmod>
35
+ <lastmod>2026-06-22T16:19:39.853Z</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-17T13:40:59.064Z</lastmod>
41
+ <lastmod>2026-06-22T16:19:39.853Z</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-17T13:40:59.064Z</lastmod>
47
+ <lastmod>2026-06-22T16:19:39.853Z</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-17T13:40:59.064Z</lastmod>
53
+ <lastmod>2026-06-22T16:19:39.853Z</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-17T13:40:59.064Z</lastmod>
59
+ <lastmod>2026-06-22T16:19:39.853Z</lastmod>
60
60
  <changefreq>weekly</changefreq>
61
61
  <priority>0.7</priority>
62
62
  </url>
@@ -21,8 +21,8 @@ const description = `That page doesn't exist on ${siteConfig.name}.`;
21
21
  site={siteConfig}
22
22
  noindex
23
23
  >
24
- <section class="grove-section">
25
- <div class="grove-container mx-auto px-5 py-20 sm:px-7 sm:py-28">
24
+ <section class="py-12 sm:py-16">
25
+ <div class="mx-auto w-full max-w-container px-5 py-20 sm:px-7 sm:py-28">
26
26
  <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
27
27
  404
28
28
  </span>
@@ -50,14 +50,14 @@ const description = `That page doesn't exist on ${siteConfig.name}.`;
50
50
  class="w-full rounded-md border border-ink-200 bg-white py-2.5 px-3 text-sm placeholder:text-ink-400 focus:border-ink-900 focus:outline-none focus:ring-2 focus:ring-ink-900/10 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-100"
51
51
  />
52
52
  </label>
53
- <button type="submit" class="grove-btn grove-btn-primary">
53
+ <button type="submit" class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90">
54
54
  Search
55
55
  </button>
56
56
  </form>
57
57
 
58
58
  <div class="mt-6 flex flex-wrap gap-3">
59
- <a href="/" class="grove-btn grove-btn-primary">Back to home</a>
60
- <a href={`/${slug}`} class="grove-btn grove-btn-outline">Browse {slug}</a>
59
+ <a href="/" class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90">Back to home</a>
60
+ <a href={`/${slug}`} class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 border-border bg-secondary text-foreground hover:bg-accent">Browse {slug}</a>
61
61
  </div>
62
62
  </div>
63
63
  </section>
@@ -101,18 +101,14 @@ const tags = record.tags ?? [];
101
101
  const healthStatus = proj?.health?.status;
102
102
  const healthLabel = healthStatus ? statusDisplay(healthStatus) : null;
103
103
 
104
- // ── Badge class lookup ────────────────────────────────────────────
105
- // The previous code interpolated `LabelId` straight into the class
106
- // string (`grove-badge-{l}`), which Astro renders as a literal class
107
- // name — every badge became `grove-badge-{l}` regardless of value.
108
- // Map each known id to its stable CSS class in the package
109
- // stylesheet. Unknown ids (e.g. legacy "featured") fall back to the
110
- // neutral `.grove-badge` look instead of producing broken classes.
104
+ // Map each known label to a static Tailwind class string so every
105
+ // utility is visible to Tailwind's source scanner. Unknown/legacy
106
+ // labels use the neutral secondary badge treatment.
111
107
  const labelClassMap: Record<LabelId, string> = {
112
- new: "grove-badge-new",
113
- hot: "grove-badge-hot",
114
- mature: "grove-badge-mature",
115
- featured: "grove-badge",
108
+ new: "!bg-emerald-50 !text-emerald-700 dark:!bg-emerald-950/80 dark:!text-emerald-300",
109
+ hot: "!bg-orange-50 !text-orange-800 dark:!bg-orange-950/80 dark:!text-orange-300",
110
+ mature: "!bg-blue-50 !text-blue-700 dark:!bg-blue-950/80 dark:!text-blue-300",
111
+ featured: "inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground",
116
112
  };
117
113
 
118
114
  // ── Optional content markdown (record.content is a path under
@@ -248,8 +244,8 @@ if (isProject && proj) {
248
244
  submitHref="/submit"
249
245
  submitLabel={`Submit ${name}`}
250
246
  >
251
- <article class="grove-section">
252
- <div class="grove-container-wide mx-auto px-5 sm:px-7">
247
+ <article class="py-12 sm:py-16">
248
+ <div class="mx-auto w-full max-w-wide px-5 sm:px-7">
253
249
  <nav class="mb-6 text-2xs text-ink-500 dark:text-ink-400" aria-label="Breadcrumb">
254
250
  <a href={`/${slug}`} class="hover:text-ink-900 dark:hover:text-ink-100 capitalize">
255
251
  {slug}
@@ -259,7 +255,7 @@ if (isProject && proj) {
259
255
  </nav>
260
256
 
261
257
  {/* ── AppCard-style header ──────────────────────────────── */}
262
- <header class="grove-card flex flex-col gap-5 p-6 sm:flex-row sm:items-start">
258
+ <header class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors flex flex-col gap-5 p-6 sm:flex-row sm:items-start">
263
259
  <div class="flex-shrink-0">
264
260
  {avatarSrc ? (
265
261
  <img
@@ -292,7 +288,7 @@ if (isProject && proj) {
292
288
  </span>
293
289
  )}
294
290
  {curationLabels.map((l) => (
295
- <span class={`grove-badge ${labelClassMap[l as LabelId] ?? ""}`} title={`Curated: ${labelDisplay(l) ?? l}`}>
291
+ <span class={`inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground ${labelClassMap[l as LabelId] ?? ""}`} title={`Curated: ${labelDisplay(l) ?? l}`}>
296
292
  {labelDisplay(l) ?? prettySlug(l)}
297
293
  </span>
298
294
  ))}
@@ -308,7 +304,7 @@ if (isProject && proj) {
308
304
  </span>
309
305
  )}
310
306
  {healthLabel && (
311
- <span class="grove-badge grove-badge-stale">{healthLabel}</span>
307
+ <span class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground !bg-amber-50 !text-amber-800 dark:!bg-amber-950/80 dark:!text-amber-300">{healthLabel}</span>
312
308
  )}
313
309
  </div>
314
310
 
@@ -350,7 +346,7 @@ if (isProject && proj) {
350
346
  href={repoUrl}
351
347
  target="_blank"
352
348
  rel="noopener noreferrer"
353
- class="grove-btn grove-btn-primary"
349
+ class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90"
354
350
  >
355
351
  View on GitHub →
356
352
  </a>
@@ -360,7 +356,7 @@ if (isProject && proj) {
360
356
  href={homepageUrl}
361
357
  target="_blank"
362
358
  rel="noopener noreferrer"
363
- class="grove-btn grove-btn-outline"
359
+ class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 border-border bg-secondary text-foreground hover:bg-accent"
364
360
  >
365
361
  Homepage
366
362
  </a>
@@ -371,7 +367,7 @@ if (isProject && proj) {
371
367
 
372
368
  {/* ── Score bars ───────────────────────────────────────── */}
373
369
  {proj && (
374
- <section class="mt-8 grove-card p-6" aria-labelledby="scores-heading">
370
+ <section class="mt-8 rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-6" aria-labelledby="scores-heading">
375
371
  <h2 id="scores-heading" class="m-0 text-lg font-semibold tracking-tight text-ink-900 dark:text-ink-100">
376
372
  Scores
377
373
  </h2>
@@ -396,7 +392,7 @@ if (isProject && proj) {
396
392
 
397
393
  {/* ── GitHub signals dl ────────────────────────────────── */}
398
394
  {isProject && github && (
399
- <section class="mt-8 grove-card p-6" aria-labelledby="signals-heading">
395
+ <section class="mt-8 rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-6" aria-labelledby="signals-heading">
400
396
  <h2 id="signals-heading" class="m-0 text-lg font-semibold tracking-tight text-ink-900 dark:text-ink-100">
401
397
  GitHub signals
402
398
  </h2>
@@ -461,8 +457,8 @@ if (isProject && proj) {
461
457
  {/* ── Decision signals (3-col) ──────────────────────────── */}
462
458
  {isProject && github && (
463
459
  <section class="mt-8 grid grid-cols-1 gap-4 lg:grid-cols-3" aria-label="Decision signals">
464
- {/* Repository health 3-col card */}
465
- <div class="grove-card p-5">
460
+ {/* Repository health card */}
461
+ <div class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
466
462
  <h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
467
463
  Repository health
468
464
  </h3>
@@ -501,7 +497,7 @@ if (isProject && proj) {
501
497
  </div>
502
498
 
503
499
  {/* Activity bar chart */}
504
- <div class="grove-card p-5">
500
+ <div class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
505
501
  <h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
506
502
  Activity
507
503
  </h3>
@@ -537,7 +533,7 @@ if (isProject && proj) {
537
533
  </div>
538
534
 
539
535
  {/* Contribution readiness chips */}
540
- <div class="grove-card p-5">
536
+ <div class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
541
537
  <h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
542
538
  Contribution readiness
543
539
  </h3>
@@ -563,7 +559,7 @@ if (isProject && proj) {
563
559
 
564
560
  {/* ── Language mix ─────────────────────────────────────── */}
565
561
  {isProject && languages.length > 0 && (
566
- <section class="mt-4 grove-card p-5">
562
+ <section class="mt-4 rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
567
563
  <h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
568
564
  Language mix
569
565
  </h3>
@@ -597,7 +593,7 @@ if (isProject && proj) {
597
593
  Stack
598
594
  </span>
599
595
  {stacks.map((s) => (
600
- <span class="grove-badge inline-flex items-center gap-1.5">
596
+ <span class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground items-center gap-1.5">
601
597
  <Icon name={s} category="stack" size={14} />
602
598
  {s}
603
599
  </span>
@@ -610,7 +606,7 @@ if (isProject && proj) {
610
606
  Platform
611
607
  </span>
612
608
  {platforms.map((p) => (
613
- <span class="grove-badge inline-flex items-center gap-1.5">
609
+ <span class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground items-center gap-1.5">
614
610
  <Icon name={p} category="platform" size={14} />
615
611
  {p}
616
612
  </span>
@@ -632,7 +628,7 @@ if (isProject && proj) {
632
628
  <li>
633
629
  <a
634
630
  href={`/${slug}?q=${encodeURIComponent(t)}`}
635
- class="grove-badge inline-flex items-center gap-1.5"
631
+ class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground items-center gap-1.5"
636
632
  >
637
633
  {t}
638
634
  </a>
@@ -650,12 +646,12 @@ if (isProject && proj) {
650
646
  </h2>
651
647
  <ul class="mt-4 m-0 flex list-none flex-col gap-2 p-0">
652
648
  {distribution.map((c) => (
653
- <li class="grove-card flex flex-wrap items-baseline justify-between gap-2 p-3">
649
+ <li class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors flex flex-wrap items-baseline justify-between gap-2 p-3">
654
650
  <div class="flex flex-col">
655
651
  <span class="text-sm font-semibold text-ink-900 dark:text-ink-100">
656
652
  {c.label ?? c.type}
657
653
  {c.verified && (
658
- <span class="ml-2 grove-badge grove-badge-new">Verified</span>
654
+ <span class="ml-2 inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground !bg-emerald-50 !text-emerald-700 dark:!bg-emerald-950/80 dark:!text-emerald-300">Verified</span>
659
655
  )}
660
656
  </span>
661
657
  {c.notes && (
@@ -666,7 +662,7 @@ if (isProject && proj) {
666
662
  href={c.url}
667
663
  target="_blank"
668
664
  rel="noopener noreferrer"
669
- class="grove-btn grove-btn-outline"
665
+ class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 border-border bg-secondary text-foreground hover:bg-accent"
670
666
  >
671
667
  {c.platform ?? c.type} →
672
668
  </a>
@@ -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();
@@ -125,7 +110,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
125
110
  submitLabel={`Submit ${itemLabel()}`}
126
111
  >
127
112
  <section class="border-b border-ink-200 bg-white dark:border-ink-800 dark:bg-ink-950">
128
- <div class="grove-container-wide mx-auto px-5 pt-10 sm:px-7 sm:pt-12">
113
+ <div class="mx-auto w-full max-w-wide px-5 pt-10 sm:px-7 sm:pt-12">
129
114
  <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
130
115
  {slug}
131
116
  </span>
@@ -186,7 +171,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
186
171
  <path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" />
187
172
  </svg>
188
173
  </label>
189
- <button type="submit" class="grove-btn grove-btn-primary">Search</button>
174
+ <button type="submit" class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90">Search</button>
190
175
  </form>
191
176
 
192
177
  <div class="mt-4">
@@ -207,22 +192,8 @@ 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
- <section class="grove-section-tight">
225
- <div class="grove-container-wide mx-auto px-5 sm:px-7">
195
+ <section class="py-10 sm:py-12">
196
+ <div class="mx-auto w-full max-w-wide px-5 sm:px-7">
226
197
  <header class="mb-6">
227
198
  <h2 class="m-0 text-[1.5rem] sm:text-[1.625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
228
199
  {`All ${itemLabelPlural()}`}
@@ -249,7 +220,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
249
220
  {chips.map((chip) => (
250
221
  <a
251
222
  href={hrefForRemove(chip.key, chip.value)}
252
- class="grove-badge transition-colors hover:border-ink-400"
223
+ class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground transition-colors hover:border-ink-400"
253
224
  >
254
225
  {chip.label} <span aria-hidden="true">x</span>
255
226
  </a>
@@ -277,7 +248,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
277
248
  <p class="m-0 text-sm font-medium text-ink-700 dark:text-ink-200">
278
249
  {`No ${itemLabel()}s match these filters.`}
279
250
  </p>
280
- <a href={`/${slug}`} class="grove-btn grove-btn-outline mt-4">Clear filters</a>
251
+ <a href={`/${slug}`} class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 border-border bg-secondary text-foreground hover:bg-accent mt-4">Clear filters</a>
281
252
  </div>
282
253
  <nav class="mt-8 flex flex-wrap items-center justify-center gap-1.5" aria-label="Pagination">
283
254
  <Pagination current={page} total={pages} hrefFor={hrefForPage} />
@@ -286,7 +257,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
286
257
  </section>
287
258
 
288
259
  <section class="border-t border-ink-200 dark:border-ink-800">
289
- <div class="grove-container-wide mx-auto px-5 sm:px-7">
260
+ <div class="mx-auto w-full max-w-wide px-5 sm:px-7">
290
261
  <div class="flex flex-col items-center gap-3 py-12 text-center">
291
262
  <h2 class="m-0 text-xl font-semibold tracking-tight text-ink-900 dark:text-ink-100">
292
263
  {`Know a real ${itemLabel()} that belongs here?`}
@@ -296,7 +267,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
296
267
  </p>
297
268
  <a
298
269
  href={siteConfig.submitUrl ?? "/submit"}
299
- class="grove-btn grove-btn-primary mt-2"
270
+ class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90 mt-2"
300
271
  >
301
272
  {`Submit ${itemLabel()}`}
302
273
  </a>
@@ -13,7 +13,7 @@
13
13
  * - New: "A growing community knowledge site. A curated, health-
14
14
  * aware developer directory…"
15
15
  *
16
- * The card utility classes (`grove-card`, `grove-btn-*`) match the
16
+ * The Tailwind card and button utilities match the
17
17
  * rest of the framework, and the data fields it references
18
18
  * (`siteConfig.tagline`, `siteConfig.repoUrl`) all flow from the
19
19
  * site config — no hardcoded brand or copy.
@@ -57,7 +57,7 @@ const item = itemLabel();
57
57
  </p>
58
58
  </header>
59
59
 
60
- <section class="grove-card mt-8 p-5 sm:p-6">
60
+ <section class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors mt-8 p-5 sm:p-6">
61
61
  <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
62
62
  Why
63
63
  </span>
@@ -78,7 +78,7 @@ const item = itemLabel();
78
78
  </p>
79
79
  </section>
80
80
 
81
- <section class="grove-card mt-4 p-5 sm:p-6">
81
+ <section class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors mt-4 p-5 sm:p-6">
82
82
  <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
83
83
  Why real {item}s
84
84
  </span>
@@ -95,7 +95,7 @@ const item = itemLabel();
95
95
  </p>
96
96
  </section>
97
97
 
98
- <section class="grove-card mt-4 p-5 sm:p-6">
98
+ <section class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors mt-4 p-5 sm:p-6">
99
99
  <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
100
100
  What you get
101
101
  </span>
@@ -130,7 +130,7 @@ const item = itemLabel();
130
130
  </ul>
131
131
  </section>
132
132
 
133
- <section class="grove-card mt-4 p-5 sm:p-6">
133
+ <section class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors mt-4 p-5 sm:p-6">
134
134
  <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
135
135
  Generic by design
136
136
  </span>
@@ -145,7 +145,7 @@ const item = itemLabel();
145
145
  </p>
146
146
  </section>
147
147
 
148
- <section class="grove-card mt-4 p-5 sm:p-6">
148
+ <section class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors mt-4 p-5 sm:p-6">
149
149
  <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
150
150
  Free to fork, free to extend
151
151
  </span>
@@ -164,18 +164,18 @@ const item = itemLabel();
164
164
  </section>
165
165
 
166
166
  <section class="mt-8 flex flex-wrap items-center gap-3">
167
- <a href={`/${slug}`} class="grove-btn grove-btn-primary">Browse the directory</a>
167
+ <a href={`/${slug}`} class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90">Browse the directory</a>
168
168
  {githubUrl && (
169
169
  <a
170
170
  href={githubUrl}
171
171
  target="_blank"
172
172
  rel="noopener noreferrer"
173
- class="grove-btn grove-btn-outline"
173
+ class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 border-border bg-secondary text-foreground hover:bg-accent"
174
174
  >
175
175
  View on GitHub
176
176
  </a>
177
177
  )}
178
- <a href="/submit" class="grove-btn grove-btn-ghost">Submit a {item} →</a>
178
+ <a href="/submit" class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground">Submit a {item} →</a>
179
179
  </section>
180
180
  </article>
181
181
  </Container>
@@ -82,8 +82,8 @@ const contributorsGraphHref = repoUrl ? `${repoUrl.replace(/\/$/, "")}/graphs/co
82
82
  ---
83
83
 
84
84
  <BaseLayout title={title} description={description} site={siteConfig}>
85
- <section class="grove-section">
86
- <div class="grove-container-wide mx-auto px-5 py-12 sm:px-7 sm:py-16">
85
+ <section class="py-12 sm:py-16">
86
+ <div class="mx-auto w-full max-w-wide px-5 py-12 sm:px-7 sm:py-16">
87
87
  <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
88
88
  Contributors
89
89
  </span>
@@ -112,9 +112,9 @@ const contributorsGraphHref = repoUrl ? `${repoUrl.replace(/\/$/, "")}/graphs/co
112
112
  </section>
113
113
 
114
114
  {sorted.length === 0 ? (
115
- <section class="grove-section">
116
- <div class="grove-container mx-auto px-5 pb-16 sm:px-7">
117
- <div class="grove-card flex flex-col items-center gap-3 p-8 text-center">
115
+ <section class="py-12 sm:py-16">
116
+ <div class="mx-auto w-full max-w-container px-5 pb-16 sm:px-7">
117
+ <div class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors flex flex-col items-center gap-3 p-8 text-center">
118
118
  <p class="m-0 text-sm text-ink-500 dark:text-ink-400">
119
119
  No contributors synced yet. Once the first sync runs, every
120
120
  contributor avatar lands here.
@@ -123,8 +123,8 @@ const contributorsGraphHref = repoUrl ? `${repoUrl.replace(/\/$/, "")}/graphs/co
123
123
  </div>
124
124
  </section>
125
125
  ) : (
126
- <section class="grove-section" aria-label="Contributors list">
127
- <div class="grove-container-wide mx-auto px-5 pb-16 sm:px-7">
126
+ <section class="py-12 sm:py-16" aria-label="Contributors list">
127
+ <div class="mx-auto w-full max-w-wide px-5 pb-16 sm:px-7">
128
128
  <ul
129
129
  class="m-0 grid list-none grid-cols-2 gap-3 p-0 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6"
130
130
  aria-label="Contributors"
@@ -140,7 +140,7 @@ const contributorsGraphHref = repoUrl ? `${repoUrl.replace(/\/$/, "")}/graphs/co
140
140
  href={href}
141
141
  target="_blank"
142
142
  rel="noopener noreferrer"
143
- class="group grove-card flex flex-col items-center gap-2 p-3 text-center no-underline transition-colors hover:border-ink-300 dark:hover:border-ink-700"
143
+ class="group rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors flex flex-col items-center gap-2 p-3 text-center no-underline hover:border-ink-300 dark:hover:border-ink-700"
144
144
  data-event="contributor_click"
145
145
  data-event-source="contributors_page"
146
146
  >
@@ -173,12 +173,3 @@ const contributorsGraphHref = repoUrl ? `${repoUrl.replace(/\/$/, "")}/graphs/co
173
173
  </section>
174
174
  )}
175
175
  </BaseLayout>
176
-
177
- <style>
178
- .line-clamp-1 {
179
- display: -webkit-box;
180
- -webkit-line-clamp: 1;
181
- -webkit-box-orient: vertical;
182
- overflow: hidden;
183
- }
184
- </style>