@grove-dev/astro 0.1.4 → 0.2.2

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 (246) hide show
  1. package/dist/index.d.ts +4 -25
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +56 -22
  4. package/dist/index.js.map +1 -1
  5. package/dist/layouts/index.d.ts +25 -0
  6. package/dist/layouts/index.d.ts.map +1 -0
  7. package/dist/layouts/index.js +25 -0
  8. package/dist/layouts/index.js.map +1 -0
  9. package/dist/lib/display.d.ts +33 -0
  10. package/dist/lib/display.d.ts.map +1 -0
  11. package/dist/lib/display.js +104 -0
  12. package/dist/lib/display.js.map +1 -0
  13. package/dist/lib/format.d.ts +30 -0
  14. package/dist/lib/format.d.ts.map +1 -0
  15. package/dist/lib/format.js +73 -0
  16. package/dist/lib/format.js.map +1 -0
  17. package/dist/lib/index.d.ts +26 -0
  18. package/dist/lib/index.d.ts.map +1 -0
  19. package/dist/lib/index.js +26 -0
  20. package/dist/lib/index.js.map +1 -0
  21. package/dist/lib/lenses.d.ts +96 -0
  22. package/dist/lib/lenses.d.ts.map +1 -0
  23. package/dist/lib/lenses.js +181 -0
  24. package/dist/lib/lenses.js.map +1 -0
  25. package/dist/lib/repo.d.ts +30 -0
  26. package/dist/lib/repo.d.ts.map +1 -0
  27. package/dist/lib/repo.js +44 -0
  28. package/dist/lib/repo.js.map +1 -0
  29. package/dist/lib/scores.d.ts +38 -0
  30. package/dist/lib/scores.d.ts.map +1 -0
  31. package/dist/lib/scores.js +83 -0
  32. package/dist/lib/scores.js.map +1 -0
  33. package/dist/lib/search.d.ts +199 -0
  34. package/dist/lib/search.d.ts.map +1 -0
  35. package/dist/lib/search.js +357 -0
  36. package/dist/lib/search.js.map +1 -0
  37. package/dist/lib/taxonomy-counts.d.ts +25 -0
  38. package/dist/lib/taxonomy-counts.d.ts.map +1 -0
  39. package/dist/lib/taxonomy-counts.js +41 -0
  40. package/dist/lib/taxonomy-counts.js.map +1 -0
  41. package/package.json +7 -7
  42. package/src/components/AppSection.astro +83 -0
  43. package/src/components/AppsIndexRow.astro +204 -0
  44. package/src/components/AppsPagination.astro +126 -0
  45. package/src/components/CategoryGrid.astro +86 -17
  46. package/src/components/ContributorsGrid.astro +149 -0
  47. package/src/components/DecisionRow.astro +89 -0
  48. package/src/components/ExploreByCategory.astro +67 -0
  49. package/src/components/ExploreByStack.astro +72 -0
  50. package/src/components/Hero.astro +246 -0
  51. package/src/components/Icon.astro +223 -0
  52. package/src/components/ItemCard.astro +289 -30
  53. package/src/components/MinimalAbout.astro +82 -0
  54. package/src/components/OriginalCollection.astro +116 -0
  55. package/src/components/RefinePanel.astro +197 -0
  56. package/src/components/ScoreBars.astro +92 -22
  57. package/src/components/SmartLensTabs.astro +71 -0
  58. package/src/components/StackGrid.astro +132 -0
  59. package/src/components/WhyThisExists.astro +116 -0
  60. package/src/index.ts +61 -14
  61. package/src/layouts/BaseLayout.astro +227 -15
  62. package/src/layouts/Container.astro +57 -0
  63. package/src/layouts/Footer.astro +235 -0
  64. package/src/layouts/Header.astro +159 -0
  65. package/src/layouts/SectionHeader.astro +58 -0
  66. package/{templates/default/src/components/layout → src/layouts}/Seo.astro +62 -38
  67. package/src/layouts/ThemeToggle.astro +110 -0
  68. package/src/layouts/astro-shim.d.ts +29 -0
  69. package/src/layouts/index.ts +24 -0
  70. package/src/lib/display.ts +118 -0
  71. package/src/lib/format.ts +65 -0
  72. package/src/lib/index.ts +26 -0
  73. package/src/lib/lenses.ts +241 -0
  74. package/src/lib/repo.ts +44 -0
  75. package/src/lib/scores.ts +94 -0
  76. package/src/lib/search.ts +481 -0
  77. package/src/lib/taxonomy-counts.ts +60 -0
  78. package/src/styles.css +366 -638
  79. package/templates/default/.astro/content.d.ts +155 -0
  80. package/templates/default/.astro/settings.json +5 -0
  81. package/templates/default/.astro/types.d.ts +2 -0
  82. package/templates/default/astro.config.mjs +8 -6
  83. package/templates/default/content/pages/about.md +66 -0
  84. package/templates/default/content/pages/methodology.md +103 -0
  85. package/templates/default/data/generated/records.full.json +8 -0
  86. package/templates/default/data/generated/records.index.json +7 -0
  87. package/templates/default/data/generated/records.json +8 -0
  88. package/templates/default/data/generated/site-config.json +31 -0
  89. package/templates/default/data/records/.gitkeep +0 -0
  90. package/templates/default/dist/_astro/BaseLayout.DfpwR3jc.css +1 -0
  91. package/templates/default/dist/about/index.html +165 -0
  92. package/templates/default/dist/icons/platforms/android.svg +1 -0
  93. package/templates/default/dist/icons/platforms/apple-dark.svg +1 -0
  94. package/templates/default/dist/icons/platforms/apple-light.svg +1 -0
  95. package/templates/default/dist/icons/platforms/chrome.svg +1 -0
  96. package/templates/default/dist/icons/platforms/chromeos.svg +5 -0
  97. package/templates/default/dist/icons/platforms/desktop.svg +4 -0
  98. package/templates/default/dist/icons/platforms/embedded.svg +5 -0
  99. package/templates/default/dist/icons/platforms/ios.svg +1 -0
  100. package/templates/default/dist/icons/platforms/linux.svg +1 -0
  101. package/templates/default/dist/icons/platforms/macos.svg +1 -0
  102. package/templates/default/dist/icons/platforms/ubuntu.svg +1 -0
  103. package/templates/default/dist/icons/platforms/web.svg +5 -0
  104. package/templates/default/dist/icons/platforms/windows.svg +6 -0
  105. package/templates/default/dist/icons/stacks/android.svg +1 -0
  106. package/templates/default/dist/icons/stacks/apple-dark.svg +1 -0
  107. package/templates/default/dist/icons/stacks/apple-light.svg +1 -0
  108. package/templates/default/dist/icons/stacks/capacitor.svg +14 -0
  109. package/templates/default/dist/icons/stacks/dart.svg +1 -0
  110. package/templates/default/dist/icons/stacks/firebase.svg +1 -0
  111. package/templates/default/dist/icons/stacks/flutter.svg +1 -0
  112. package/templates/default/dist/icons/stacks/graphql.svg +1 -0
  113. package/templates/default/dist/icons/stacks/ionic.svg +4 -0
  114. package/templates/default/dist/icons/stacks/java.svg +1 -0
  115. package/templates/default/dist/icons/stacks/javascript.svg +1 -0
  116. package/templates/default/dist/icons/stacks/kotlin.svg +1 -0
  117. package/templates/default/dist/icons/stacks/mongodb.svg +1 -0
  118. package/templates/default/dist/icons/stacks/nodejs.svg +1 -0
  119. package/templates/default/dist/icons/stacks/python.svg +1 -0
  120. package/templates/default/dist/icons/stacks/react-native.svg +1 -0
  121. package/templates/default/dist/icons/stacks/react.svg +1 -0
  122. package/templates/default/dist/icons/stacks/rust.svg +1 -0
  123. package/templates/default/dist/icons/stacks/solidity.svg +1 -0
  124. package/templates/default/dist/icons/stacks/swift.svg +1 -0
  125. package/templates/default/dist/icons/stacks/tensorflow.svg +1 -0
  126. package/templates/default/dist/icons/stacks/typescript.svg +1 -0
  127. package/templates/default/dist/index.html +55 -0
  128. package/templates/default/dist/llms-full.txt +14 -0
  129. package/templates/default/dist/llms.txt +11 -0
  130. package/templates/default/dist/og-image.svg +48 -0
  131. package/templates/default/dist/projects/index.html +61 -0
  132. package/templates/default/dist/robots.txt +53 -0
  133. package/templates/default/dist/sitemap.xml +21 -0
  134. package/templates/default/dist/submit/index.html +69 -0
  135. package/templates/default/grove.config.ts +36 -0
  136. package/templates/default/node_modules/.bin/astro +21 -0
  137. package/templates/default/node_modules/.bin/astro-check +21 -0
  138. package/templates/default/node_modules/.bin/grove +21 -0
  139. package/templates/default/node_modules/.bin/jiti +21 -0
  140. package/templates/default/node_modules/.bin/prettier +21 -0
  141. package/templates/default/node_modules/.bin/rollup +21 -0
  142. package/templates/default/node_modules/.bin/tsc +21 -0
  143. package/templates/default/node_modules/.bin/tsserver +21 -0
  144. package/templates/default/node_modules/.bin/tsx +21 -0
  145. package/templates/default/node_modules/.bin/vite +21 -0
  146. package/templates/default/node_modules/.bin/yaml +21 -0
  147. package/templates/default/node_modules/.vite/deps/_metadata.json +79 -0
  148. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +433 -0
  149. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +7 -0
  150. package/templates/default/node_modules/.vite/deps/astro___aria-query.js +5 -0
  151. package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
  152. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +5 -0
  153. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
  154. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +9 -0
  155. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +7 -0
  156. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +241 -0
  157. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +7 -0
  158. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +1592 -0
  159. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +7 -0
  160. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
  161. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
  162. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +186 -0
  163. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +7 -0
  164. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +48 -0
  165. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +7 -0
  166. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +6779 -0
  167. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +7 -0
  168. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +33 -0
  169. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +7 -0
  170. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +88 -0
  171. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +7 -0
  172. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +72 -0
  173. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +7 -0
  174. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +3757 -0
  175. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +7 -0
  176. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +54 -0
  177. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +7 -0
  178. package/templates/default/node_modules/.vite/deps/package.json +3 -0
  179. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +213 -0
  180. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +7 -0
  181. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +525 -0
  182. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +7 -0
  183. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +1020 -0
  184. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +7 -0
  185. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +147 -0
  186. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +7 -0
  187. package/templates/default/package.json +6 -5
  188. package/templates/default/public/llms-full.txt +14 -0
  189. package/templates/default/public/llms.txt +11 -0
  190. package/templates/default/src/components/layout/BaseLayout.astro +10 -57
  191. package/templates/default/src/components/layout/Footer.astro +16 -134
  192. package/templates/default/src/components/layout/Header.astro +25 -126
  193. package/templates/default/src/data/records.ts +161 -0
  194. package/templates/default/src/lib/markdown.ts +171 -0
  195. package/templates/default/src/pages/about.astro +95 -429
  196. package/templates/default/src/pages/index.astro +258 -61
  197. package/templates/default/src/pages/projects/[slug].astro +480 -0
  198. package/templates/default/src/pages/projects/index.astro +193 -0
  199. package/templates/default/src/pages/sitemap.xml.ts +73 -61
  200. package/templates/default/src/pages/submit.astro +122 -411
  201. package/templates/default/src/styles/global.css +42 -68
  202. package/src/components/ActiveFilterChips.astro +0 -20
  203. package/src/components/DirectoryFilters.astro +0 -100
  204. package/src/components/DirectoryHero.astro +0 -32
  205. package/src/components/HealthBadge.astro +0 -12
  206. package/src/components/LensTabs.astro +0 -26
  207. package/src/components/MethodologyPanel.astro +0 -20
  208. package/src/components/Pagination.astro +0 -24
  209. package/src/components/ProjectDetail.astro +0 -166
  210. package/src/components/SubmitDraft.astro +0 -137
  211. package/templates/default/src/components/AppCard.astro +0 -248
  212. package/templates/default/src/components/AppSection.astro +0 -58
  213. package/templates/default/src/components/AppsIndexRow.astro +0 -230
  214. package/templates/default/src/components/AppsPagination.astro +0 -147
  215. package/templates/default/src/components/CategoryGrid.astro +0 -80
  216. package/templates/default/src/components/ContributorsGrid.astro +0 -105
  217. package/templates/default/src/components/DecisionRow.astro +0 -219
  218. package/templates/default/src/components/ExploreByCategory.astro +0 -74
  219. package/templates/default/src/components/ExploreByStack.astro +0 -102
  220. package/templates/default/src/components/Hero.astro +0 -176
  221. package/templates/default/src/components/MinimalAbout.astro +0 -57
  222. package/templates/default/src/components/OriginalCollection.astro +0 -144
  223. package/templates/default/src/components/RefinePanel.astro +0 -272
  224. package/templates/default/src/components/ScoreBars.astro +0 -72
  225. package/templates/default/src/components/SmartLensTabs.astro +0 -65
  226. package/templates/default/src/components/StackGrid.astro +0 -95
  227. package/templates/default/src/components/WhyThisExists.astro +0 -81
  228. package/templates/default/src/components/icons/Icon.astro +0 -148
  229. package/templates/default/src/components/layout/SectionHeader.astro +0 -42
  230. package/templates/default/src/components/layout/ThemeToggle.astro +0 -97
  231. package/templates/default/src/data/apps.ts +0 -149
  232. package/templates/default/src/data/categories.ts +0 -27
  233. package/templates/default/src/data/config.ts +0 -28
  234. package/templates/default/src/data/contributors.ts +0 -96
  235. package/templates/default/src/data/stacks.ts +0 -47
  236. package/templates/default/src/data/stats.ts +0 -65
  237. package/templates/default/src/data/types.ts +0 -248
  238. package/templates/default/src/lib/apps-search.ts +0 -228
  239. package/templates/default/src/lib/format.ts +0 -36
  240. package/templates/default/src/lib/lenses.ts +0 -98
  241. package/templates/default/src/lib/repo.ts +0 -12
  242. package/templates/default/src/lib/scores.ts +0 -51
  243. package/templates/default/src/lib/taxonomy-counts.ts +0 -31
  244. package/templates/default/src/pages/apps/[slug].astro +0 -709
  245. package/templates/default/src/pages/apps/index.astro +0 -669
  246. package/templates/default/src/pages/contributors.astro +0 -142
@@ -1,66 +1,263 @@
1
1
  ---
2
- import { siteConfig } from "../data/config";
3
- import BaseLayout from "../components/layout/BaseLayout.astro";
4
- import Hero from "../components/Hero.astro";
5
- import WhyThisExists from "../components/WhyThisExists.astro";
6
- import AppSection from "../components/AppSection.astro";
7
- import StackGrid from "../components/StackGrid.astro";
8
- import CategoryGrid from "../components/CategoryGrid.astro";
9
- import ContributorsGrid from "../components/ContributorsGrid.astro";
10
- import OriginalCollection from "../components/OriginalCollection.astro";
11
-
12
- import { newApps, hotApps, matureApps } from "../data/apps";
13
- import { stacks } from "../data/stacks";
14
- import { categories } from "../data/categories";
15
- import { contributors } from "../data/contributors";
16
- import { stats } from "../data/stats";
17
-
18
- const hotList = hotApps().slice(0, 6);
19
- const newList = newApps().slice(0, 6);
20
- const matureList = matureApps().slice(0, 6);
21
-
22
- const title = `${siteConfig.name} — A directory of real open-source applications`;
2
+ /**
3
+ * Home page — the showcase's front door.
4
+ *
5
+ * Sections (in order):
6
+ * 1. Hero — brand + search + stats row
7
+ * 2. WhyThisExists — 3 short value-prop points
8
+ * 3. AppSection "Trending" — hot lens (stars >= 1000 OR pushed <= 30d)
9
+ * 4. AppSection "Recently added"— new lens (pushed <= 7d OR curated "new")
10
+ * 5. AppSection "Established" — mature lens (tier=curated, pushed > 365d)
11
+ * 6. StackGrid — browse by stack
12
+ * 7. CategoryGrid — browse by category
13
+ * 8. ContributorsGrid — top contributors (when available)
14
+ * 9. OriginalCollection — origin-card link to the source repo
15
+ *
16
+ * Every section is defensive: with 0 records (fresh scaffold) it
17
+ * renders a graceful empty state instead of throwing.
18
+ */
19
+ import siteConfig from "../../data/generated/site-config.json";
20
+ import { records, fullRecords, projects, indexSlug, itemLabel } from "../data/records";
21
+ import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
22
+ import Hero from "@grove-dev/astro/components/Hero.astro";
23
+ import WhyThisExists from "@grove-dev/astro/components/WhyThisExists.astro";
24
+ import AppSection from "@grove-dev/astro/components/AppSection.astro";
25
+ import StackGrid from "@grove-dev/astro/components/StackGrid.astro";
26
+ import CategoryGrid from "@grove-dev/astro/components/CategoryGrid.astro";
27
+ import ContributorsGrid from "@grove-dev/astro/components/ContributorsGrid.astro";
28
+ import OriginalCollection from "@grove-dev/astro/components/OriginalCollection.astro";
29
+ import { countByCategory, countByStack } from "@grove-dev/astro";
30
+ import { existsSync, readFileSync } from "node:fs";
31
+ import { fileURLToPath } from "node:url";
32
+ import { dirname, resolve } from "node:path";
33
+ import type { ProjectRecord } from "@grove-dev/core";
34
+
35
+ const slug = indexSlug();
36
+ const projectKind = projects;
37
+
38
+ // ── Hero stats ──────────────────────────────────────────────────
39
+ const totalApps = fullRecords.filter((r) => r.kind === "project").length;
40
+ const totalCategories = new Set(projectKind.map((r) => r.category).filter(Boolean))
41
+ .size;
42
+ const totalStacks = new Set(
43
+ projectKind.flatMap((r) => [r.stack, ...(r.stacks ?? [])]).filter(Boolean),
44
+ ).size;
45
+ const totalContributors = new Set(
46
+ projectKind
47
+ .map((r) => {
48
+ const url = r.repoUrl ?? (r.links as { github?: string } | undefined)?.github ?? "";
49
+ const m = /github\.com\/([^/]+)\//.exec(url);
50
+ return m?.[1];
51
+ })
52
+ .filter(Boolean),
53
+ ).size;
54
+ const totalStars = projectKind.reduce(
55
+ (acc, r) => acc + ((r as ProjectRecord).github?.stars ?? 0),
56
+ 0,
57
+ );
58
+
59
+ // ── Lens sectioning (Hot / New / Mature) ────────────────────────
60
+ const DAY_MS = 86_400_000;
61
+ function daysSince(iso: string | null | undefined): number {
62
+ if (!iso) return Infinity;
63
+ const d = new Date(iso);
64
+ if (Number.isNaN(d.valueOf())) return Infinity;
65
+ return (Date.now() - d.valueOf()) / DAY_MS;
66
+ }
67
+
68
+ const hot = projectKind
69
+ .filter((r) => {
70
+ const stars = (r as ProjectRecord).github?.stars ?? 0;
71
+ const pushedAt = (r as ProjectRecord).github?.pushedAt ?? null;
72
+ const pushed = daysSince(pushedAt);
73
+ const labelledHot = r.curation?.labels?.includes("hot") ?? false;
74
+ return labelledHot || stars >= 1000 || pushed <= 30;
75
+ })
76
+ .slice(0, 6);
77
+
78
+ const newItems = projectKind
79
+ .filter((r) => {
80
+ const pushedAt = (r as ProjectRecord).github?.pushedAt ?? null;
81
+ const pushed = daysSince(pushedAt);
82
+ const labelledNew = r.curation?.labels?.includes("new") ?? false;
83
+ return pushed <= 7 || labelledNew;
84
+ })
85
+ .slice(0, 6);
86
+
87
+ const mature = projectKind
88
+ .filter((r) => {
89
+ const tier = r.health?.tier;
90
+ const pushedAt = (r as ProjectRecord).github?.pushedAt ?? null;
91
+ const pushed = daysSince(pushedAt);
92
+ return tier === "curated" && pushed > 365;
93
+ })
94
+ .slice(0, 6);
95
+
96
+ // ── Stack / category aggregates ─────────────────────────────────
97
+ const stackCounts = countByStack(projectKind);
98
+ const stacks = [...stackCounts.entries()]
99
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
100
+ .slice(0, 12)
101
+ .map(([name, count]) => ({ name, slug: name, count }));
102
+
103
+ const categoryCounts = countByCategory(projectKind);
104
+ const categories = [...categoryCounts.entries()]
105
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
106
+ .map(([name, count]) => ({ name, slug: name, count }));
107
+
108
+ // ── Contributors (best-effort, from records) ────────────────────
109
+ const here = dirname(fileURLToPath(import.meta.url));
110
+ const contributorsPath = [
111
+ resolve(here, "..", "..", "data", "generated", "contributors.json"),
112
+ resolve(process.cwd(), "data", "generated", "contributors.json"),
113
+ ].find((p) => existsSync(p));
114
+
115
+ interface ContributorsFile {
116
+ contributors?: Array<{
117
+ username: string;
118
+ name?: string;
119
+ avatarUrl?: string;
120
+ profileUrl?: string;
121
+ contributions?: number;
122
+ }>;
123
+ }
124
+
125
+ let contributors: ContributorsFile["contributors"] = [];
126
+ if (contributorsPath) {
127
+ try {
128
+ const parsed = JSON.parse(readFileSync(contributorsPath, "utf8")) as ContributorsFile;
129
+ contributors = parsed.contributors ?? [];
130
+ } catch {
131
+ contributors = [];
132
+ }
133
+ }
134
+ const hasContributors = contributors && contributors.length > 0;
135
+
136
+ // ── OriginalCollection (repo-stats.json if present) ─────────────
137
+ interface RepoStats {
138
+ originalRepo?: string;
139
+ stars?: number;
140
+ forks?: number;
141
+ contributors?: number;
142
+ description?: string;
143
+ }
144
+ let repoStats: RepoStats = {};
145
+ const repoStatsPath = [
146
+ resolve(here, "..", "..", "data", "generated", "repo-stats.json"),
147
+ resolve(process.cwd(), "data", "generated", "repo-stats.json"),
148
+ ].find((p) => existsSync(p));
149
+ if (repoStatsPath) {
150
+ try {
151
+ repoStats = JSON.parse(readFileSync(repoStatsPath, "utf8")) as RepoStats;
152
+ } catch {
153
+ repoStats = {};
154
+ }
155
+ }
156
+
157
+ const title = `${siteConfig.name} — ${siteConfig.tagline}`;
23
158
  const description =
24
- "A searchable directory of real open-source applications organized by stack, category, platform, license, activity, and maturity.";
159
+ siteConfig.description ?? `A growing community knowledge site powered by Grove.`;
160
+
161
+ // ── Render-time helpers ─────────────────────────────────────────
162
+ function detailHrefFor(r: (typeof records)[number]): string {
163
+ return `/${slug}/${r.slug}`;
164
+ }
25
165
  ---
26
166
 
27
- <BaseLayout title={title} description={description}>
28
- <Hero />
29
-
30
- <WhyThisExists />
31
-
32
- <AppSection
33
- id="hot"
34
- title="Hot open-source apps"
35
- description="Projects gaining stars, activity, or attention recently."
36
- apps={hotList}
37
- viewAllHref="/apps?label=hot"
38
- viewAllLabel="View hot apps"
39
- />
40
-
41
- <AppSection
42
- id="new"
43
- title="New open-source apps"
44
- description="Recently added apps from the community."
45
- apps={newList}
46
- viewAllHref="/apps?label=new"
47
- viewAllLabel="View all new"
48
- />
49
-
50
- <AppSection
51
- id="mature"
52
- title="Mature open-source apps"
53
- description="Established apps with strong history, active maintainers, and real-world usage."
54
- apps={matureList}
55
- viewAllHref="/apps?label=mature"
56
- viewAllLabel="View mature apps"
57
- />
58
-
59
- <StackGrid stacks={stacks} />
60
-
61
- <CategoryGrid categories={categories} />
62
-
63
- <ContributorsGrid contributors={contributors} />
64
-
65
- <OriginalCollection stats={stats} />
167
+ <BaseLayout
168
+ title={title}
169
+ description={description}
170
+ site={siteConfig}
171
+ submitHref="/submit"
172
+ submitLabel={`Submit ${itemLabel()}`}
173
+ >
174
+ <Hero
175
+ name={siteConfig.name}
176
+ tagline={siteConfig.tagline}
177
+ description={description}
178
+ eyebrow="Curated directory"
179
+ searchPlaceholder={`Search ${slug}...`}
180
+ searchHref={`/${slug}`}
181
+ stats={{
182
+ apps: totalApps,
183
+ categories: totalCategories,
184
+ stacks: totalStacks,
185
+ contributors: totalContributors || undefined,
186
+ stars: totalStars || undefined,
187
+ }}
188
+ ctas={[
189
+ { label: `Browse ${slug}`, href: `/${slug}`, variant: "primary" },
190
+ { label: "About", href: "/about", variant: "outline" },
191
+ ]}
192
+ />
193
+
194
+ <WhyThisExists />
195
+
196
+ <AppSection
197
+ id="hot"
198
+ title="Trending"
199
+ description="The projects getting attention right now — high-star repos and recently active codebases."
200
+ items={hot}
201
+ hrefFor={detailHrefFor}
202
+ viewAllHref={`/${slug}?lens=hot`}
203
+ eyebrow="Hot"
204
+ emptyLabel="No trending projects yet."
205
+ />
206
+
207
+ <AppSection
208
+ id="new"
209
+ title="Recently added"
210
+ description="Fresh entries — pushed within the last week or carrying the curated 'new' label."
211
+ items={newItems}
212
+ hrefFor={detailHrefFor}
213
+ viewAllHref={`/${slug}?lens=new`}
214
+ eyebrow="New"
215
+ emptyLabel="No new projects yet."
216
+ />
217
+
218
+ <AppSection
219
+ id="mature"
220
+ title="Established"
221
+ description="Long-running curated projects with deep history and stable architecture."
222
+ items={mature}
223
+ hrefFor={detailHrefFor}
224
+ viewAllHref={`/${slug}?lens=mature`}
225
+ eyebrow="Mature"
226
+ emptyLabel="No established projects yet."
227
+ />
228
+
229
+ {stacks.length > 0 && (
230
+ <StackGrid
231
+ stacks={stacks}
232
+ pathPrefix={`/${slug}`}
233
+ />
234
+ )}
235
+
236
+ {categories.length > 0 && (
237
+ <CategoryGrid
238
+ categories={categories}
239
+ pathPrefix={`/${slug}`}
240
+ />
241
+ )}
242
+
243
+ {hasContributors && (
244
+ <ContributorsGrid
245
+ contributors={contributors!}
246
+ total={contributors!.length}
247
+ viewAllHref={siteConfig.repoUrl || undefined}
248
+ viewAllLabel="View on GitHub"
249
+ />
250
+ )}
251
+
252
+ {repoStats.originalRepo && (
253
+ <OriginalCollection
254
+ repoUrl={repoStats.originalRepo}
255
+ stats={{
256
+ stars: repoStats.stars,
257
+ forks: repoStats.forks,
258
+ contributors: repoStats.contributors,
259
+ description: repoStats.description,
260
+ }}
261
+ />
262
+ )}
66
263
  </BaseLayout>