@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,41 +1,300 @@
1
1
  ---
2
- import type { CuratedItem, HealthEntry } from "@grove-dev/core";
3
- import HealthBadge from "./HealthBadge.astro";
2
+ /**
3
+ * ItemCard — generic project/resource/entity card.
4
+ *
5
+ * The most-decorated card in the showcase. Used on home (curated
6
+ * sections), the list page (cards-only views), and the search
7
+ * results. Renders:
8
+ *
9
+ * - Logo/avatar with initials fallback (logoUrl -> GitHub owner
10
+ * avatar -> 2-letter initials).
11
+ * - Owner / repo link (when `repoUrl` is github.com).
12
+ * - Stack + platform chips (first 3, then "+N" overflow).
13
+ * - Optional curated mark (label badge) — e.g. "Featured", "New".
14
+ * - Health badge (Active / Mature / Stale / Archived / ...).
15
+ * - License + star count + last-updated date.
16
+ * - Two CTAs: "View repo" (links.github if present, else repoUrl)
17
+ * and "Details" (the page link).
18
+ *
19
+ * Generic: takes any `Resource` (`kind: "project" | "resource" |
20
+ * "entity"`). For "project" we read the project-style fields
21
+ * (`repoUrl`, `logoUrl`, `stacks`, `platforms`, `github`,
22
+ * `curation`, `health`); for other kinds we degrade gracefully to
23
+ * the minimal view.
24
+ *
25
+ * The component is index-payload-friendly: every field it reads is
26
+ * present in the `toIndexRecord` projection (see
27
+ * `@grove-dev/core/schema.ts`).
28
+ */
29
+ import type { Resource, ProjectRecord, HealthStatus } from "@grove-dev/core";
30
+ import { getOwnerAndRepoFromRepoUrl, getOwnerAvatarUrl } from "../lib/repo";
31
+ import { formatRelative, formatStars } from "../lib/format";
32
+ import { statusDisplay, prettySlug, labelDisplay } from "../lib/display";
4
33
 
5
34
  interface Props {
6
- item: CuratedItem;
7
- health?: HealthEntry;
35
+ record: Resource;
36
+ /** Detail page href. */
37
+ href: string;
38
+ /** When true, show owner/repo below the title. Default true. */
39
+ showOwner?: boolean;
40
+ /** When true, render the stack/platform chip row. Default true. */
41
+ showChips?: boolean;
42
+ /** When true, render the curated label badge row. Default true. */
43
+ showLabel?: boolean;
44
+ /** When true, render the metadata strip (license/stars/updated). */
45
+ showMeta?: boolean;
46
+ /** Extra class on the outer <article>. */
47
+ class?: string;
8
48
  }
9
49
 
10
- const { item, health } = Astro.props;
11
- const href = `/projects/${item.id}`;
12
- const tags = item.taxonomy.tags.slice(0, 4);
50
+ const {
51
+ record,
52
+ href,
53
+ showOwner = true,
54
+ showChips = true,
55
+ showLabel = true,
56
+ showMeta = true,
57
+ class: className = "",
58
+ } = Astro.props;
59
+
60
+ // Project-specific extraction
61
+ const isProject = record.kind === "project";
62
+ const project = isProject ? (record as ProjectRecord) : null;
63
+
64
+ const name =
65
+ record.kind === "resource"
66
+ ? (record as { title: string }).title
67
+ : (record as { name: string }).name;
68
+ const description = record.description ?? "";
69
+ const tags = (record.tags ?? []).slice(0, 4);
70
+ const repoUrl = project?.repoUrl;
71
+ const logoUrl = project?.logoUrl;
72
+ const stacks = project?.stacks ?? (project?.stack ? [project.stack] : []);
73
+ const platforms = project?.platforms ?? [];
74
+ const healthStatus: HealthStatus | undefined = project?.health?.status;
75
+ const labels = record.curation?.labels ?? [];
76
+ const github = project?.github?.repository;
77
+ const stars = github?.stargazers_count;
78
+ const licenseSpdx = github?.license?.spdx_id;
79
+ const pushedAt = github?.pushed_at;
80
+ const githubUrl = repoUrl ?? (record.links as { github?: string } | undefined)?.github;
81
+
82
+ // Logo fallback chain
83
+ const ownerRepo = repoUrl ? getOwnerAndRepoFromRepoUrl(repoUrl) : null;
84
+ const avatarSrc =
85
+ logoUrl ?? (ownerRepo ? getOwnerAvatarUrl(ownerRepo.owner, 80) : null);
86
+ const initials = (name || "")
87
+ .replace(/[^a-z0-9]/gi, "")
88
+ .slice(0, 2)
89
+ .toUpperCase();
90
+
91
+ // Chips: 3 stacks/platforms, then overflow count
92
+ function withOverflow(
93
+ items: string[],
94
+ ): { shown: string[]; overflow: number } {
95
+ const shown = items.filter(Boolean).slice(0, 3);
96
+ const overflow = Math.max(0, items.filter(Boolean).length - shown.length);
97
+ return { shown, overflow };
98
+ }
99
+ const stackChips = withOverflow(stacks);
100
+ const platformChips = withOverflow(platforms);
101
+
102
+ // Curated mark
103
+ const curatedLabel = labels[0] ?? null;
104
+
105
+ // Health class hook (for badge variant)
106
+ function healthBadgeClass(s: HealthStatus | undefined): string {
107
+ switch (s) {
108
+ case "mature":
109
+ case "active":
110
+ return "grove-badge-new";
111
+ case "stale":
112
+ case "inactive":
113
+ return "grove-badge-stale";
114
+ case "archived":
115
+ case "unavailable":
116
+ return "grove-badge-hot";
117
+ default:
118
+ return "";
119
+ }
120
+ }
121
+ const healthLabel = healthStatus ? statusDisplay(healthStatus) : null;
122
+
123
+ const starsLabel = formatStars(stars);
124
+ const updatedLabel = formatRelative(pushedAt);
13
125
  ---
14
126
 
15
- <article class="oc-card">
16
- <div class="oc-card-header">
17
- <div>
18
- <h2 class="oc-card-title">
19
- <a href={href}>{item.name}</a>
20
- </h2>
21
- <p class="oc-card-category">{item.taxonomy.category}</p>
127
+ <article
128
+ class:list={["grove-card grove-card-hover grove-item-card p-4", className]}
129
+ data-record-slug={record.slug}
130
+ data-record-kind={record.kind}
131
+ data-record-category={record.category}
132
+ data-record-stacks={stacks.join(",")}
133
+ data-record-platforms={platforms.join(",")}
134
+ >
135
+ <div class="flex items-start gap-3">
136
+ <div class="grove-item-card__logo flex-shrink-0">
137
+ {avatarSrc ? (
138
+ <img
139
+ src={avatarSrc}
140
+ alt=""
141
+ width="40"
142
+ height="40"
143
+ loading="lazy"
144
+ decoding="async"
145
+ class="h-10 w-10 rounded-md border border-ink-200 object-contain bg-white dark:border-ink-800 dark:bg-ink-950"
146
+ />
147
+ ) : (
148
+ <span
149
+ class="inline-flex h-10 w-10 items-center justify-center rounded-md border border-ink-200 bg-ink-50 text-sm font-semibold text-ink-700 dark:border-ink-800 dark:bg-ink-900 dark:text-ink-200"
150
+ aria-hidden="true"
151
+ >
152
+ {initials || "·"}
153
+ </span>
154
+ )}
155
+ </div>
156
+
157
+ <div class="min-w-0 flex-1">
158
+ <div class="flex flex-wrap items-baseline gap-2">
159
+ <h2 class="m-0 text-[1rem] font-semibold leading-tight tracking-tight text-ink-900 dark:text-ink-100">
160
+ <a href={href} class="text-inherit no-underline hover:underline">
161
+ {name}
162
+ </a>
163
+ </h2>
164
+ {showLabel && curatedLabel && (
165
+ <span
166
+ class:list={["grove-badge", `grove-badge-${curatedLabel}`]}
167
+ title={`Curated: ${labelDisplay(curatedLabel) ?? curatedLabel}`}
168
+ >
169
+ {labelDisplay(curatedLabel) ?? prettySlug(curatedLabel)}
170
+ </span>
171
+ )}
172
+ {healthLabel && (
173
+ <span class:list={["grove-badge", healthBadgeClass(healthStatus)]}>
174
+ {healthLabel}
175
+ </span>
176
+ )}
177
+ </div>
178
+
179
+ {showOwner && ownerRepo && (
180
+ <p class="m-0 mt-0.5 text-2xs text-ink-500 dark:text-ink-400">
181
+ <a
182
+ href={`https://github.com/${ownerRepo.owner}`}
183
+ target="_blank"
184
+ rel="noopener noreferrer"
185
+ class="text-inherit no-underline hover:text-ink-900 dark:hover:text-ink-100"
186
+ >
187
+ @{ownerRepo.owner}
188
+ </a>
189
+ <span class="mx-1 text-ink-300 dark:text-ink-700">/</span>
190
+ <a
191
+ href={repoUrl}
192
+ target="_blank"
193
+ rel="noopener noreferrer"
194
+ class="text-inherit no-underline hover:text-ink-900 dark:hover:text-ink-100"
195
+ >
196
+ {ownerRepo.repo}
197
+ </a>
198
+ </p>
199
+ )}
200
+
201
+ {description && (
202
+ <p class="m-0 mt-1.5 line-clamp-2 text-[0.8125rem] leading-snug text-ink-500 dark:text-ink-400">
203
+ {description}
204
+ </p>
205
+ )}
22
206
  </div>
23
- <HealthBadge status={health?.health.status} />
24
- </div>
25
- <p class="oc-card-description">{item.description}</p>
26
- <div class="oc-card-meta">
27
- {health?.github?.stars != null && <span>{health.github.stars.toLocaleString()} stars</span>}
28
- {health?.github?.license && <span>{health.github.license}</span>}
29
- {health?.github?.language && <span>{health.github.language}</span>}
30
- </div>
31
- <div class="oc-card-actions">
32
- <a href={`/projects/${item.id}`}>Details</a>
33
- {item.links.github && <a href={item.links.github}>GitHub</a>}
34
- {item.links.docs && <a href={item.links.docs}>Docs</a>}
35
207
  </div>
36
- {tags.length > 0 && (
37
- <ul class="oc-tags" aria-label="Tags">
38
- {tags.map((tag) => <li>{tag}</li>)}
39
- </ul>
208
+
209
+ {showChips && (stackChips.shown.length > 0 || platformChips.shown.length > 0) && (
210
+ <div class="mt-3 flex flex-wrap items-center gap-1.5">
211
+ {stackChips.shown.map((s) => (
212
+ <span class="grove-badge" title={`Stack: ${s}`}>{s}</span>
213
+ ))}
214
+ {stackChips.overflow > 0 && (
215
+ <span class="grove-badge" title={`${stackChips.overflow} more stacks`}>
216
+ +{stackChips.overflow}
217
+ </span>
218
+ )}
219
+ {platformChips.shown.map((p) => (
220
+ <span class="grove-badge" title={`Platform: ${p}`}>{p}</span>
221
+ ))}
222
+ {platformChips.overflow > 0 && (
223
+ <span
224
+ class="grove-badge"
225
+ title={`${platformChips.overflow} more platforms`}
226
+ >
227
+ +{platformChips.overflow}
228
+ </span>
229
+ )}
230
+ {tags.length > 0 && tags.map((t) => <span class="grove-badge">{t}</span>)}
231
+ </div>
40
232
  )}
233
+
234
+ {showMeta && (starsLabel || licenseSpdx || updatedLabel !== "—") && (
235
+ <dl
236
+ class="mt-3 flex flex-wrap items-center gap-x-3 gap-y-1 text-2xs text-ink-500 dark:text-ink-400"
237
+ aria-label="Project signals"
238
+ >
239
+ {starsLabel && (
240
+ <div class="flex items-center gap-1">
241
+ <dt class="sr-only">Stars</dt>
242
+ <dd class="m-0 font-mono">{starsLabel}</dd>
243
+ <span aria-hidden="true">★</span>
244
+ </div>
245
+ )}
246
+ {licenseSpdx && (
247
+ <div class="flex items-center gap-1">
248
+ <dt class="sr-only">License</dt>
249
+ <dd class="m-0">{licenseSpdx}</dd>
250
+ </div>
251
+ )}
252
+ {updatedLabel && updatedLabel !== "—" && (
253
+ <div class="flex items-center gap-1">
254
+ <dt class="sr-only">Last updated</dt>
255
+ <dd class="m-0">Updated {updatedLabel}</dd>
256
+ </div>
257
+ )}
258
+ </dl>
259
+ )}
260
+
261
+ <div class="mt-4 flex flex-wrap items-center gap-2">
262
+ <a
263
+ href={href}
264
+ class="grove-btn grove-btn-outline"
265
+ data-event="card_details_click"
266
+ data-event-source="item_card"
267
+ data-event-app={record.slug}
268
+ >
269
+ Details
270
+ </a>
271
+ {githubUrl && (
272
+ <a
273
+ href={githubUrl}
274
+ target="_blank"
275
+ rel="noopener noreferrer"
276
+ class="grove-btn grove-btn-ghost"
277
+ data-event="card_repo_click"
278
+ data-event-source="item_card"
279
+ data-event-app={record.slug}
280
+ >
281
+ View repo
282
+ </a>
283
+ )}
284
+ </div>
41
285
  </article>
286
+
287
+ <style>
288
+ .grove-item-card {
289
+ display: flex;
290
+ flex-direction: column;
291
+ gap: 0.25rem;
292
+ height: 100%;
293
+ }
294
+ .line-clamp-2 {
295
+ display: -webkit-box;
296
+ -webkit-line-clamp: 2;
297
+ -webkit-box-orient: vertical;
298
+ overflow: hidden;
299
+ }
300
+ </style>
@@ -0,0 +1,82 @@
1
+ ---
2
+ /**
3
+ * MinimalAbout — 3 short points + title, used on the about page.
4
+ *
5
+ * A trimmed-down sibling of `WhyThisExists` with a different layout
6
+ * (no icons, no "0X" counter, vertical list) for text-heavy pages.
7
+ *
8
+ * Generic: every point comes through props.
9
+ */
10
+
11
+ interface AboutPoint {
12
+ title: string;
13
+ body: string;
14
+ }
15
+
16
+ interface Props {
17
+ points?: AboutPoint[];
18
+ title?: string;
19
+ description?: string;
20
+ class?: string;
21
+ }
22
+
23
+ const {
24
+ points,
25
+ title = "About this directory",
26
+ description,
27
+ class: className = "",
28
+ } = Astro.props;
29
+
30
+ const defaultPoints: AboutPoint[] = [
31
+ {
32
+ title: "Curated, not scraped",
33
+ body: "Every entry is hand-checked. If something here, it's because someone looked at the source, ran the build, and decided it was worth your time.",
34
+ },
35
+ {
36
+ title: "Plain text all the way down",
37
+ body: "Records live in plain YAML, content in plain Markdown, and the static output in plain HTML. No database, no JavaScript framework, no CDN with sticky sessions.",
38
+ },
39
+ {
40
+ title: "Free to fork, free to extend",
41
+ body: "This whole site is one repo. If you want your own copy, fork it. If you want to add a record, open a PR. The directory is the community.",
42
+ },
43
+ ];
44
+
45
+ const items = points && points.length > 0 ? points : defaultPoints;
46
+ ---
47
+
48
+ <section class:list={["grove-section", className]}>
49
+ <div class="grove-container-narrow mx-auto px-5 sm:px-7">
50
+ <header class="mb-6 flex flex-col gap-2">
51
+ <h2 class="m-0 text-[1.5rem] sm:text-[1.625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
52
+ {title}
53
+ </h2>
54
+ {description && (
55
+ <p class="m-0 text-[0.9375rem] text-ink-500 dark:text-ink-400">
56
+ {description}
57
+ </p>
58
+ )}
59
+ </header>
60
+
61
+ <ol class="m-0 flex list-none flex-col gap-5 p-0">
62
+ {items.map((p, i) => (
63
+ <li class="flex gap-4">
64
+ <span
65
+ aria-hidden="true"
66
+ class="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md border border-ink-200 bg-ink-50 font-mono text-2xs text-ink-500 dark:border-ink-800 dark:bg-ink-900 dark:text-ink-400"
67
+ >
68
+ 0{i + 1}
69
+ </span>
70
+ <div>
71
+ <h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
72
+ {p.title}
73
+ </h3>
74
+ <p class="m-0 mt-1 text-[0.9375rem] leading-relaxed text-ink-500 dark:text-ink-400">
75
+ {p.body}
76
+ </p>
77
+ </div>
78
+ </li>
79
+ ))}
80
+ </ol>
81
+ </div>
82
+ </section>
@@ -0,0 +1,116 @@
1
+ ---
2
+ /**
3
+ * OriginalCollection — legacy lineage card.
4
+ *
5
+ * Renders a single wide card that links out to the project's origin
6
+ * repository, with stars/forks/contributors stats. Used on home and
7
+ * about pages to give visitors a way to find the original curated
8
+ * collection this directory descends from.
9
+ *
10
+ * Generic: takes plain `stats` and a `repoUrl`. No blueprint or
11
+ * site-specific copy.
12
+ */
13
+
14
+ interface OriginStats {
15
+ stars?: number;
16
+ forks?: number;
17
+ contributors?: number;
18
+ description?: string;
19
+ }
20
+
21
+ interface Props {
22
+ repoUrl: string;
23
+ repoLabel?: string;
24
+ stats?: OriginStats;
25
+ title?: string;
26
+ description?: string;
27
+ class?: string;
28
+ }
29
+
30
+ const {
31
+ repoUrl,
32
+ repoLabel = "View on GitHub",
33
+ stats = {},
34
+ title = "A growing list, forked from the original",
35
+ description = "This directory descends from an earlier curated collection. The original still lives on — same shape, same spirit, fresh data.",
36
+ class: className = "",
37
+ } = Astro.props;
38
+
39
+ function fmt(n: number | undefined): string | null {
40
+ if (typeof n !== "number" || !Number.isFinite(n)) return null;
41
+ if (n >= 1000) {
42
+ const k = n / 1000;
43
+ return (k >= 10 ? Math.round(k).toString() : k.toFixed(1)) + "k";
44
+ }
45
+ return n.toString();
46
+ }
47
+
48
+ const hasStats =
49
+ stats.stars !== undefined ||
50
+ stats.forks !== undefined ||
51
+ stats.contributors !== undefined;
52
+ ---
53
+
54
+ <section class:list={["grove-section", className]}>
55
+ <div class="grove-container mx-auto px-5 sm:px-7">
56
+ <div
57
+ class="grove-card flex flex-col gap-4 p-6 sm:flex-row sm:items-center sm:justify-between"
58
+ >
59
+ <div class="flex flex-col gap-2">
60
+ <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
61
+ Origin
62
+ </span>
63
+ <h2 class="m-0 text-[1.25rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
64
+ {title}
65
+ </h2>
66
+ <p class="m-0 max-w-2xl text-[0.875rem] text-ink-500 dark:text-ink-400">
67
+ {description}
68
+ </p>
69
+ {stats.description && (
70
+ <p class="m-0 max-w-2xl text-[0.8125rem] text-ink-500 dark:text-ink-400">
71
+ {stats.description}
72
+ </p>
73
+ )}
74
+ {hasStats && (
75
+ <dl
76
+ class="mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-2xs text-ink-500 dark:text-ink-400"
77
+ aria-label="Original repository signals"
78
+ >
79
+ {stats.stars !== undefined && (
80
+ <div class="flex items-center gap-1">
81
+ <dd class="m-0 font-mono">{fmt(stats.stars) ?? stats.stars}</dd>
82
+ <dt>stars</dt>
83
+ </div>
84
+ )}
85
+ {stats.forks !== undefined && (
86
+ <div class="flex items-center gap-1">
87
+ <dd class="m-0 font-mono">{fmt(stats.forks) ?? stats.forks}</dd>
88
+ <dt>forks</dt>
89
+ </div>
90
+ )}
91
+ {stats.contributors !== undefined && (
92
+ <div class="flex items-center gap-1">
93
+ <dd class="m-0 font-mono">
94
+ {fmt(stats.contributors) ?? stats.contributors}
95
+ </dd>
96
+ <dt>contributors</dt>
97
+ </div>
98
+ )}
99
+ </dl>
100
+ )}
101
+ </div>
102
+ <div class="flex flex-shrink-0 items-center gap-2">
103
+ <a
104
+ href={repoUrl}
105
+ target="_blank"
106
+ rel="noopener noreferrer"
107
+ class="grove-btn grove-btn-primary"
108
+ data-event="original_collection_click"
109
+ data-event-source="original_collection"
110
+ >
111
+ {repoLabel} →
112
+ </a>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </section>