@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,80 +0,0 @@
1
- ---
2
- import type { Category } from "../data/categories";
3
- import SectionHeader from "./layout/SectionHeader.astro";
4
- import Container from "./layout/Container.astro";
5
- import { countByCategory } from "../lib/taxonomy-counts";
6
-
7
- interface Props {
8
- categories: Category[];
9
- }
10
-
11
- const { categories } = Astro.props;
12
- const counts = countByCategory();
13
-
14
- // Hide categories that have zero apps in the directory. We don't lie about
15
- // coverage. The taxonomy is a "future state" map; the rendered list is the
16
- // intersection of the taxonomy and what actually exists in data/apps/.
17
- const visible = categories
18
- .map((c) => ({ ...c, count: counts.get(c.slug) ?? 0 }))
19
- .filter((c) => c.count > 0)
20
- .sort((a, b) => b.count - a.count);
21
- ---
22
-
23
- <section
24
- id="categories"
25
- class="section scroll-mt-20 border-b border-ink-200 dark:border-ink-800"
26
- >
27
- <Container>
28
- <SectionHeader
29
- eyebrow="Categories"
30
- title="Browse by category"
31
- description="Find apps by what they do, not by which stack they use."
32
- />
33
-
34
- <div
35
- class="overflow-hidden rounded-lg border border-ink-200 dark:border-ink-800"
36
- >
37
- <ul
38
- class="m-0 grid list-none grid-cols-1 divide-y divide-ink-200 p-0 sm:grid-cols-2 sm:divide-x sm:divide-y-0 lg:grid-cols-3 dark:divide-ink-800"
39
- >
40
- {
41
- visible.map((cat) => (
42
- <li class="divide-x divide-ink-200 dark:divide-ink-800">
43
- <a
44
- href={`/apps?category=${encodeURIComponent(cat.name)}`}
45
- class="group flex h-full items-start gap-3 p-4 no-underline transition-colors hover:bg-ink-50/60 dark:hover:bg-ink-900/40"
46
- >
47
- <div class="flex min-w-0 flex-1 flex-col gap-0.5">
48
- <div class="flex items-center gap-2">
49
- <h3 class="m-0 text-[0.9375rem] font-semibold tracking-tight text-ink-900 group-hover:underline dark:text-ink-100">
50
- {cat.name}
51
- </h3>
52
- <span
53
- class="inline-flex items-center rounded-md border border-ink-200 bg-ink-50 px-1.5 py-0.5 font-mono text-[10px] font-medium text-ink-600 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-400"
54
- title={`${cat.count} apps in this category`}
55
- >
56
- {cat.count} apps
57
- </span>
58
- </div>
59
- <p class="m-0 text-2xs leading-relaxed text-ink-500 dark:text-ink-400">
60
- {cat.blurb}
61
- </p>
62
- </div>
63
- <svg
64
- viewBox="0 0 16 16"
65
- width="12"
66
- height="12"
67
- aria-hidden="true"
68
- class="mt-1 shrink-0 text-ink-300 transition-colors group-hover:text-ink-500 dark:text-ink-700 dark:group-hover:text-ink-400"
69
- fill="currentColor"
70
- >
71
- <path d="M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z" />
72
- </svg>
73
- </a>
74
- </li>
75
- ))
76
- }
77
- </ul>
78
- </div>
79
- </Container>
80
- </section>
@@ -1,105 +0,0 @@
1
- ---
2
- // SPDX-License-Identifier: MIT
3
-
4
- /**
5
- * Contributors grid for the home page. Renders all contributors at a
6
- * uniform avatar size (no big/small distinction — the previous
7
- * "top 10 + rest" layout implied a hierarchy that doesn't exist).
8
- *
9
- * When the contributor count is large enough that the homepage
10
- * layout would feel cluttered, a "View all" link points to the
11
- * dedicated `/contributors` page where the full list lives.
12
- */
13
- import type { Contributor } from "../data/contributors";
14
- import SectionHeader from "./layout/SectionHeader.astro";
15
- import Container from "./layout/Container.astro";
16
-
17
- interface Props {
18
- contributors: Contributor[];
19
- }
20
-
21
- const { contributors } = Astro.props;
22
- const total = contributors.length;
23
- // Threshold above which we link out to the full /contributors page
24
- // instead of inlining every avatar on the homepage.
25
- const INLINE_LIMIT = 100;
26
- const isCapped = total > INLINE_LIMIT;
27
- const visible = isCapped ? contributors.slice(0, INLINE_LIMIT) : contributors;
28
- const more = Math.max(0, total - INLINE_LIMIT);
29
- ---
30
-
31
- <section
32
- id="contributors"
33
- class="section scroll-mt-20 border-b border-ink-200 dark:border-ink-800"
34
- >
35
- <Container>
36
- <SectionHeader
37
- eyebrow="Community"
38
- title="Thanks to every contributor."
39
- description="This directory exists because people keep discovering, submitting, reviewing, and maintaining open-source app references."
40
- />
41
-
42
- <ul
43
- class="m-0 grid list-none grid-cols-[repeat(auto-fill,minmax(72px,1fr))] gap-x-3 gap-y-5 p-0"
44
- >
45
- {
46
- visible.map((c) => (
47
- <li class="flex flex-col items-center gap-1.5 text-center">
48
- <a
49
- href={c.profileUrl}
50
- target="_blank"
51
- rel="noopener noreferrer"
52
- aria-label={`${c.name ?? c.username} on GitHub`}
53
- title={`${c.name ?? c.username}${typeof c.contributions === "number" ? ` · ${c.contributions} contributions` : ""}`}
54
- class="block transition-opacity hover:opacity-80"
55
- >
56
- <img
57
- src={c.avatarUrl}
58
- alt={`${c.name ?? c.username} avatar`}
59
- loading="lazy"
60
- width="40"
61
- height="40"
62
- class="h-10 w-10 rounded-full border border-ink-200 bg-white object-cover dark:border-ink-800 dark:bg-ink-900"
63
- />
64
- </a>
65
- <a
66
- href={c.profileUrl}
67
- target="_blank"
68
- rel="noopener noreferrer"
69
- title={c.name ?? c.username}
70
- class="block max-w-full truncate text-2xs text-ink-500 transition-colors hover:text-ink-900 dark:text-ink-400 dark:hover:text-ink-100"
71
- >
72
- {c.username}
73
- </a>
74
- </li>
75
- ))
76
- }
77
- </ul>
78
-
79
- <div
80
- class="mt-6 flex flex-wrap items-center gap-x-3 gap-y-2 text-2xs text-ink-500 dark:text-ink-400"
81
- >
82
- <span>
83
- {isCapped
84
- ? <>Showing {visible.length} of {total} contributors on the homepage.</>
85
- : <>Showing all {total} contributors.</>}
86
- </span>
87
- <a
88
- href="/contributors"
89
- class="inline-flex items-center gap-1 font-medium text-ink-700 hover:text-ink-900 dark:text-ink-300 dark:hover:text-ink-100"
90
- >
91
- View all
92
- {more > 0 ? ` (${more} more)` : ""} →
93
- </a>
94
- <span aria-hidden="true">·</span>
95
- <a
96
- href="https://github.com/tortuvshin/open-apps/graphs/contributors"
97
- target="_blank"
98
- rel="noopener noreferrer"
99
- class="text-ink-700 hover:text-ink-900 dark:text-ink-300 dark:hover:text-ink-100"
100
- >
101
- GitHub contributors graph
102
- </a>
103
- </div>
104
- </Container>
105
- </section>
@@ -1,219 +0,0 @@
1
- ---
2
- import type { OpenSourceApp } from "../data/types";
3
- import Icon from "./icons/Icon.astro";
4
- import {
5
- getOwnerAndRepoFromRepoUrl,
6
- getOwnerAvatarUrl,
7
- } from "../lib/repo";
8
- import { formatStars } from "../lib/format";
9
-
10
- interface Props {
11
- app: OpenSourceApp;
12
- }
13
-
14
- const { app } = Astro.props;
15
-
16
- const { owner, repo } = getOwnerAndRepoFromRepoUrl(app.repoUrl);
17
- const avatar = app.logoUrl ?? getOwnerAvatarUrl(owner, 80);
18
-
19
- const label =
20
- app.labels?.find((l) => l === "new" || l === "hot" || l === "mature") ??
21
- app.labels?.[0];
22
-
23
- const allStacks = [app.stack, ...(app.stacks ?? [])].filter(Boolean);
24
- const visibleStacks = allStacks.slice(0, 5);
25
- const stackOverflow = allStacks.length - visibleStacks.length;
26
-
27
- const visiblePlatforms = app.platforms.slice(0, 5);
28
- const platformOverflow = app.platforms.length - visiblePlatforms.length;
29
-
30
- const detailHref = `/apps/${app.slug}`;
31
-
32
- const isCurated = !!app.curation;
33
-
34
- const projectTypeLabel = (() => {
35
- switch (app.projectType) {
36
- case "real-app": return "Real app";
37
- case "production": return "Production";
38
- case "reference": return "Reference";
39
- case "demo": return "Demo";
40
- case "template": return "Template";
41
- default: return null;
42
- }
43
- })();
44
-
45
- const starsLabel = formatStars(app.stars);
46
- ---
47
-
48
- <article
49
- class="decision-row group relative flex flex-col gap-3 rounded-lg border border-ink-200 bg-white p-4 transition-colors hover:border-ink-300 hover:shadow-[0_1px_2px_rgba(0,0,0,0.04),0_4px_12px_-2px_rgba(0,0,0,0.06)] dark:border-ink-800 dark:bg-ink-950 dark:hover:border-ink-700 dark:hover:shadow-[0_1px_2px_rgba(0,0,0,0.4),0_4px_12px_-2px_rgba(0,0,0,0.3)]"
50
- data-slug={app.slug}
51
- >
52
- {/* Avatar + name + badges */}
53
- <header class="flex items-start gap-3">
54
- <a
55
- href={detailHref}
56
- class="relative z-10 flex shrink-0"
57
- aria-hidden="true"
58
- tabindex="-1"
59
- >
60
- {avatar ? (
61
- <img
62
- src={avatar}
63
- alt={`${app.name} avatar`}
64
- width="44"
65
- height="44"
66
- loading="lazy"
67
- decoding="async"
68
- class="h-11 w-11 rounded-md border border-ink-200 bg-white object-cover dark:border-ink-800 dark:bg-ink-900"
69
- />
70
- ) : (
71
- <span
72
- aria-hidden="true"
73
- class="flex h-11 w-11 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/60 dark:text-ink-300"
74
- >
75
- {app.name.slice(0, 2).toUpperCase()}
76
- </span>
77
- )}
78
- </a>
79
-
80
- <div class="flex min-w-0 flex-1 flex-col gap-0.5">
81
- <div class="flex flex-wrap items-baseline gap-x-2">
82
- <h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
83
- <a
84
- href={detailHref}
85
- class="relative z-10 text-inherit no-underline hover:underline"
86
- >
87
- {app.name}
88
- </a>
89
- </h3>
90
- {projectTypeLabel && (
91
- <span class="inline-flex items-center rounded border border-ink-200 bg-ink-50 px-1.5 py-0 text-[10px] font-medium uppercase tracking-wider text-ink-700 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-300">
92
- {projectTypeLabel}
93
- </span>
94
- )}
95
- {label && (
96
- <span
97
- class={`inline-flex items-center rounded border px-1.5 py-0 text-[10px] font-medium uppercase tracking-wider ${
98
- label === "new"
99
- ? "border-emerald-300/60 bg-emerald-50 text-emerald-800 dark:border-emerald-800/60 dark:bg-emerald-950/40 dark:text-emerald-300"
100
- : label === "hot"
101
- ? "border-orange-300/60 bg-orange-50 text-orange-800 dark:border-orange-800/60 dark:bg-orange-950/40 dark:text-orange-300"
102
- : "border-blue-300/60 bg-blue-50 text-blue-800 dark:border-blue-800/60 dark:bg-blue-950/40 dark:text-blue-300"
103
- }`}
104
- >
105
- {label}
106
- </span>
107
- )}
108
- {isCurated && !app.curation?.reviewed && (
109
- <span class="inline-flex items-center gap-1 rounded border border-amber-300/60 bg-amber-50 px-1.5 py-0 text-[10px] font-medium text-amber-800 dark:border-amber-800/60 dark:bg-amber-950/40 dark:text-amber-300">
110
- <svg viewBox="0 0 16 16" width="9" height="9" aria-hidden="true" fill="currentColor">
111
- <path d="M8 1.5a.75.75 0 0 1 .65.376l5.7 9.788A.75.75 0 0 1 13.7 12.75H2.3a.75.75 0 0 1-.65-1.086l5.7-9.788A.75.75 0 0 1 8 1.5ZM8 6a.75.75 0 0 1 .75.75v3a.75.75 0 0 1-1.5 0v-3A.75.75 0 0 1 8 6Zm0 6.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"/>
112
- </svg>
113
- Auto-curated · Needs review
114
- </span>
115
- )}
116
- {isCurated && app.curation?.reviewed && (
117
- <span class="inline-flex items-center gap-1 rounded border border-emerald-300/60 bg-emerald-50 px-1.5 py-0 text-[10px] font-medium text-emerald-800 dark:border-emerald-800/60 dark:bg-emerald-950/40 dark:text-emerald-300">
118
- <svg viewBox="0 0 16 16" width="9" height="9" aria-hidden="true" fill="currentColor">
119
- <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"/>
120
- </svg>
121
- Curator reviewed
122
- </span>
123
- )}
124
- </div>
125
- <div class="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-2xs text-ink-500 dark:text-ink-400">
126
- {owner && (
127
- <a
128
- href={app.repoUrl}
129
- target="_blank"
130
- rel="noopener noreferrer"
131
- class="relative z-10 font-mono hover:text-ink-900 dark:hover:text-ink-100"
132
- >
133
- <span class="text-ink-400 dark:text-ink-500">@</span>{owner}
134
- {repo && <span class="text-ink-400 dark:text-ink-500">/{repo}</span>}
135
- </a>
136
- )}
137
- <span class="text-ink-300 dark:text-ink-700">·</span>
138
- <span>{app.category}</span>
139
- {app.status && (
140
- <>
141
- <span class="text-ink-300 dark:text-ink-700">·</span>
142
- <span class="capitalize">{app.status}</span>
143
- </>
144
- )}
145
- {starsLabel && (
146
- <>
147
- <span class="text-ink-300 dark:text-ink-700">·</span>
148
- <span class="inline-flex items-center gap-0.5 font-mono">
149
- <svg viewBox="0 0 16 16" width="10" height="10" aria-hidden="true" fill="currentColor">
150
- <path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"/>
151
- </svg>
152
- {starsLabel}
153
- </span>
154
- </>
155
- )}
156
- </div>
157
- </div>
158
-
159
- <a
160
- href={detailHref}
161
- class="relative z-10 hidden shrink-0 self-start text-2xs font-medium text-ink-500 hover:text-ink-900 sm:inline dark:text-ink-400 dark:hover:text-ink-100"
162
- >
163
- View details →
164
- </a>
165
- </header>
166
-
167
- {/* One-line summary */}
168
- <p class="m-0 line-clamp-2 text-[0.8125rem] leading-relaxed text-ink-600 dark:text-ink-300">
169
- {app.description}
170
- </p>
171
-
172
- {/* Metadata grid: stack, platforms, license — compact one-liner each */}
173
- <dl class="grid grid-cols-1 gap-x-4 gap-y-1 text-2xs sm:grid-cols-3">
174
- <div class="flex items-baseline gap-1.5">
175
- <dt class="font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">Stack</dt>
176
- <dd class="flex flex-wrap items-center gap-1 text-ink-700 dark:text-ink-300">
177
- {visibleStacks.map((s) => (
178
- <span class="inline-flex items-center gap-0.5">
179
- <Icon name={s} category="stack" size={10} class="h-2.5 w-2.5" />
180
- <span>{s}</span>
181
- </span>
182
- ))}
183
- {stackOverflow > 0 && <span class="text-ink-400 dark:text-ink-500">+{stackOverflow}</span>}
184
- </dd>
185
- </div>
186
- <div class="flex items-baseline gap-1.5">
187
- <dt class="font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">Platforms</dt>
188
- <dd class="flex flex-wrap items-center gap-1 text-ink-700 dark:text-ink-300">
189
- {visiblePlatforms.map((p) => (
190
- <span class="inline-flex items-center gap-0.5">
191
- <Icon name={p} category="platform" size={10} class="h-2.5 w-2.5" />
192
- <span>{p}</span>
193
- </span>
194
- ))}
195
- {platformOverflow > 0 && <span class="text-ink-400 dark:text-ink-500">+{platformOverflow}</span>}
196
- </dd>
197
- </div>
198
- <div class="flex items-baseline gap-1.5">
199
- <dt class="font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">License</dt>
200
- <dd class="font-mono text-ink-700 dark:text-ink-300">
201
- {app.license ?? "—"}
202
- {app.codebaseSize && (
203
- <>
204
- <span class="ml-2 text-ink-300 dark:text-ink-700">·</span>
205
- <span class="ml-1 text-ink-500 dark:text-ink-400">{app.codebaseSize}</span>
206
- </>
207
- )}
208
- {app.difficulty && (
209
- <>
210
- <span class="ml-2 text-ink-300 dark:text-ink-700">·</span>
211
- <span class="ml-1 text-ink-500 dark:text-ink-400">{app.difficulty}</span>
212
- </>
213
- )}
214
- </dd>
215
- </div>
216
- </dl>
217
-
218
- {/* Scores row — kept off the list to avoid bloat. See detail page. */}
219
- </article>
@@ -1,74 +0,0 @@
1
- ---
2
- import type { Category } from "../data/categories";
3
- import Container from "./layout/Container.astro";
4
- import { countByCategory } from "../lib/taxonomy-counts";
5
-
6
- interface Props {
7
- categories: Category[];
8
- class?: string;
9
- }
10
-
11
- const { categories, class: className = "" } = Astro.props;
12
- const counts = countByCategory();
13
-
14
- // Sort by count desc, drop empty (already done in CategoryGrid, but
15
- // re-apply here for safety in case the consumer rendered a raw list).
16
- const visible = categories
17
- .map((c) => ({ ...c, count: counts.get(c.name) ?? 0 }))
18
- .filter((c) => c.count > 0)
19
- .sort((a, b) => b.count - a.count)
20
- .slice(0, 12);
21
- ---
22
-
23
- <section
24
- id="explore-categories"
25
- class={`section scroll-mt-20 border-t border-ink-200 dark:border-ink-800 ${className}`}
26
- >
27
- <Container>
28
- <div class="flex flex-col gap-1.5">
29
- <span
30
- class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400"
31
- >Explore</span
32
- >
33
- <h2
34
- class="m-0 text-[1.5rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"
35
- >
36
- Browse by category
37
- </h2>
38
- <p
39
- class="m-0 text-[0.875rem] text-ink-500 dark:text-ink-400"
40
- >
41
- Jump straight to a category — links take you to a filtered list,
42
- not a static browse page.
43
- </p>
44
- </div>
45
-
46
- <ul class="mt-5 grid list-none grid-cols-1 gap-2 p-0 sm:grid-cols-2 lg:grid-cols-3">
47
- {
48
- visible.map((c) => (
49
- <li>
50
- <a
51
- href={`/apps?category=${encodeURIComponent(c.name)}`}
52
- class="card card-hover group flex h-full items-start gap-3 p-3.5 no-underline"
53
- >
54
- <div class="flex min-w-0 flex-1 flex-col gap-0.5">
55
- <h3 class="m-0 text-[0.875rem] font-semibold tracking-tight text-ink-900 group-hover:underline dark:text-ink-100">
56
- {c.name}
57
- </h3>
58
- <p class="m-0 truncate text-2xs text-ink-500 dark:text-ink-400">
59
- {c.blurb}
60
- </p>
61
- </div>
62
- <span
63
- class="shrink-0 inline-flex items-center rounded-md border border-ink-200 bg-ink-50 px-1.5 py-0.5 font-mono text-[10px] font-medium text-ink-600 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-400"
64
- title={`${c.count} apps in this category`}
65
- >
66
- {c.count}
67
- </span>
68
- </a>
69
- </li>
70
- ))
71
- }
72
- </ul>
73
- </Container>
74
- </section>
@@ -1,102 +0,0 @@
1
- ---
2
- import type { Stack } from "../data/stacks";
3
- import Container from "./layout/Container.astro";
4
- import Icon from "./icons/Icon.astro";
5
- import { countByStack } from "../lib/taxonomy-counts";
6
-
7
- interface Props {
8
- stacks: Stack[];
9
- class?: string;
10
- }
11
-
12
- const { stacks, class: className = "" } = Astro.props;
13
- const counts = countByStack();
14
-
15
- const statusLabel: Record<Stack["status"], string> = {
16
- live: "Available now",
17
- expanding: "Expanding",
18
- planned: "Planned",
19
- };
20
-
21
- const statusBadge: Record<Stack["status"], string> = {
22
- live: "border-emerald-300/60 bg-emerald-50 text-emerald-800 dark:border-emerald-800/60 dark:bg-emerald-950/40 dark:text-emerald-300",
23
- expanding: "border-ink-200 bg-ink-50 text-ink-700 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-300",
24
- planned: "border-ink-200 bg-white text-ink-500 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-500",
25
- };
26
-
27
- const enriched = stacks
28
- .map((s) => ({ ...s, count: counts.get(s.name) ?? 0 }))
29
- .sort((a, b) => b.count - a.count);
30
- ---
31
-
32
- <section
33
- id="explore-stacks"
34
- class={`section scroll-mt-20 border-t border-ink-200 dark:border-ink-800 ${className}`}
35
- >
36
- <Container>
37
- <div class="flex flex-col gap-1.5">
38
- <span
39
- class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400"
40
- >Explore</span
41
- >
42
- <h2
43
- class="m-0 text-[1.5rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"
44
- >
45
- Browse by stack
46
- </h2>
47
- <p
48
- class="m-0 text-[0.875rem] text-ink-500 dark:text-ink-400"
49
- >
50
- Pick a stack to see every curated app built with it. Live stacks
51
- have apps right now; planned ones are on the roadmap.
52
- </p>
53
- </div>
54
-
55
- <div class="mt-5 grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
56
- {
57
- enriched.map((stack) => (
58
- <a
59
- href={`/apps?stack=${encodeURIComponent(stack.name)}`}
60
- class="card card-hover group flex h-full items-center gap-3 p-3.5 no-underline"
61
- >
62
- <div
63
- class="flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-ink-200 bg-white dark:border-ink-800 dark:bg-ink-950"
64
- aria-hidden="true"
65
- >
66
- <Icon
67
- name={stack.name}
68
- category="stack"
69
- size={18}
70
- class="h-[18px] w-[18px]"
71
- />
72
- </div>
73
-
74
- <div class="flex min-w-0 flex-1 flex-col gap-0.5">
75
- <div class="flex items-center gap-2">
76
- <h3 class="m-0 text-[0.875rem] font-semibold tracking-tight text-ink-900 group-hover:underline dark:text-ink-100">
77
- {stack.name}
78
- </h3>
79
- <span
80
- class="inline-flex items-center rounded-md border border-ink-200 bg-ink-50 px-1.5 py-0.5 font-mono text-[10px] font-medium text-ink-600 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-400"
81
- title={`${stack.count} apps in this stack`}
82
- >
83
- {stack.count} apps
84
- </span>
85
- </div>
86
- <p class="m-0 truncate text-2xs text-ink-500 dark:text-ink-400">
87
- {stack.blurb}
88
- </p>
89
- </div>
90
-
91
- <span
92
- class={`shrink-0 inline-flex items-center rounded-md border px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wider ${statusBadge[stack.status]}`}
93
- aria-label={`Status: ${statusLabel[stack.status]}`}
94
- >
95
- {statusLabel[stack.status]}
96
- </span>
97
- </a>
98
- ))
99
- }
100
- </div>
101
- </Container>
102
- </section>