@grove-dev/astro 0.1.5 → 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.
- package/dist/index.d.ts +4 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +56 -22
- package/dist/index.js.map +1 -1
- package/dist/layouts/index.d.ts +25 -0
- package/dist/layouts/index.d.ts.map +1 -0
- package/dist/layouts/index.js +25 -0
- package/dist/layouts/index.js.map +1 -0
- package/dist/lib/display.d.ts +33 -0
- package/dist/lib/display.d.ts.map +1 -0
- package/dist/lib/display.js +104 -0
- package/dist/lib/display.js.map +1 -0
- package/dist/lib/format.d.ts +30 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +73 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/index.d.ts +26 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +26 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/lenses.d.ts +96 -0
- package/dist/lib/lenses.d.ts.map +1 -0
- package/dist/lib/lenses.js +181 -0
- package/dist/lib/lenses.js.map +1 -0
- package/dist/lib/repo.d.ts +30 -0
- package/dist/lib/repo.d.ts.map +1 -0
- package/dist/lib/repo.js +44 -0
- package/dist/lib/repo.js.map +1 -0
- package/dist/lib/scores.d.ts +38 -0
- package/dist/lib/scores.d.ts.map +1 -0
- package/dist/lib/scores.js +83 -0
- package/dist/lib/scores.js.map +1 -0
- package/dist/lib/search.d.ts +199 -0
- package/dist/lib/search.d.ts.map +1 -0
- package/dist/lib/search.js +357 -0
- package/dist/lib/search.js.map +1 -0
- package/dist/lib/taxonomy-counts.d.ts +25 -0
- package/dist/lib/taxonomy-counts.d.ts.map +1 -0
- package/dist/lib/taxonomy-counts.js +41 -0
- package/dist/lib/taxonomy-counts.js.map +1 -0
- package/package.json +7 -7
- package/src/components/AppSection.astro +83 -0
- package/src/components/AppsIndexRow.astro +204 -0
- package/src/components/AppsPagination.astro +126 -0
- package/src/components/CategoryGrid.astro +86 -17
- package/src/components/ContributorsGrid.astro +149 -0
- package/src/components/DecisionRow.astro +89 -0
- package/src/components/ExploreByCategory.astro +67 -0
- package/src/components/ExploreByStack.astro +72 -0
- package/src/components/Hero.astro +246 -0
- package/src/components/Icon.astro +223 -0
- package/src/components/ItemCard.astro +289 -30
- package/src/components/MinimalAbout.astro +82 -0
- package/src/components/OriginalCollection.astro +116 -0
- package/src/components/RefinePanel.astro +197 -0
- package/src/components/ScoreBars.astro +92 -22
- package/src/components/SmartLensTabs.astro +71 -0
- package/src/components/StackGrid.astro +132 -0
- package/src/components/WhyThisExists.astro +116 -0
- package/src/index.ts +61 -14
- package/src/layouts/BaseLayout.astro +227 -15
- package/src/layouts/Container.astro +57 -0
- package/src/layouts/Footer.astro +235 -0
- package/src/layouts/Header.astro +159 -0
- package/src/layouts/SectionHeader.astro +58 -0
- package/{templates/default/src/components/layout → src/layouts}/Seo.astro +62 -38
- package/src/layouts/ThemeToggle.astro +110 -0
- package/src/layouts/astro-shim.d.ts +29 -0
- package/src/layouts/index.ts +24 -0
- package/src/lib/display.ts +118 -0
- package/src/lib/format.ts +65 -0
- package/src/lib/index.ts +26 -0
- package/src/lib/lenses.ts +241 -0
- package/src/lib/repo.ts +44 -0
- package/src/lib/scores.ts +94 -0
- package/src/lib/search.ts +481 -0
- package/src/lib/taxonomy-counts.ts +60 -0
- package/src/styles.css +366 -638
- package/templates/default/.astro/content.d.ts +155 -0
- package/templates/default/.astro/settings.json +5 -0
- package/templates/default/.astro/types.d.ts +2 -0
- package/templates/default/astro.config.mjs +8 -6
- package/templates/default/content/pages/about.md +66 -0
- package/templates/default/content/pages/methodology.md +103 -0
- package/templates/default/data/generated/records.full.json +8 -0
- package/templates/default/data/generated/records.index.json +7 -0
- package/templates/default/data/generated/records.json +8 -0
- package/templates/default/data/generated/site-config.json +31 -0
- package/templates/default/data/records/.gitkeep +0 -0
- package/templates/default/dist/_astro/BaseLayout.DfpwR3jc.css +1 -0
- package/templates/default/dist/about/index.html +165 -0
- package/templates/default/dist/icons/platforms/android.svg +1 -0
- package/templates/default/dist/icons/platforms/apple-dark.svg +1 -0
- package/templates/default/dist/icons/platforms/apple-light.svg +1 -0
- package/templates/default/dist/icons/platforms/chrome.svg +1 -0
- package/templates/default/dist/icons/platforms/chromeos.svg +5 -0
- package/templates/default/dist/icons/platforms/desktop.svg +4 -0
- package/templates/default/dist/icons/platforms/embedded.svg +5 -0
- package/templates/default/dist/icons/platforms/ios.svg +1 -0
- package/templates/default/dist/icons/platforms/linux.svg +1 -0
- package/templates/default/dist/icons/platforms/macos.svg +1 -0
- package/templates/default/dist/icons/platforms/ubuntu.svg +1 -0
- package/templates/default/dist/icons/platforms/web.svg +5 -0
- package/templates/default/dist/icons/platforms/windows.svg +6 -0
- package/templates/default/dist/icons/stacks/android.svg +1 -0
- package/templates/default/dist/icons/stacks/apple-dark.svg +1 -0
- package/templates/default/dist/icons/stacks/apple-light.svg +1 -0
- package/templates/default/dist/icons/stacks/capacitor.svg +14 -0
- package/templates/default/dist/icons/stacks/dart.svg +1 -0
- package/templates/default/dist/icons/stacks/firebase.svg +1 -0
- package/templates/default/dist/icons/stacks/flutter.svg +1 -0
- package/templates/default/dist/icons/stacks/graphql.svg +1 -0
- package/templates/default/dist/icons/stacks/ionic.svg +4 -0
- package/templates/default/dist/icons/stacks/java.svg +1 -0
- package/templates/default/dist/icons/stacks/javascript.svg +1 -0
- package/templates/default/dist/icons/stacks/kotlin.svg +1 -0
- package/templates/default/dist/icons/stacks/mongodb.svg +1 -0
- package/templates/default/dist/icons/stacks/nodejs.svg +1 -0
- package/templates/default/dist/icons/stacks/python.svg +1 -0
- package/templates/default/dist/icons/stacks/react-native.svg +1 -0
- package/templates/default/dist/icons/stacks/react.svg +1 -0
- package/templates/default/dist/icons/stacks/rust.svg +1 -0
- package/templates/default/dist/icons/stacks/solidity.svg +1 -0
- package/templates/default/dist/icons/stacks/swift.svg +1 -0
- package/templates/default/dist/icons/stacks/tensorflow.svg +1 -0
- package/templates/default/dist/icons/stacks/typescript.svg +1 -0
- package/templates/default/dist/index.html +55 -0
- package/templates/default/dist/llms-full.txt +14 -0
- package/templates/default/dist/llms.txt +11 -0
- package/templates/default/dist/og-image.svg +48 -0
- package/templates/default/dist/projects/index.html +61 -0
- package/templates/default/dist/robots.txt +53 -0
- package/templates/default/dist/sitemap.xml +21 -0
- package/templates/default/dist/submit/index.html +69 -0
- package/templates/default/grove.config.ts +36 -0
- package/templates/default/node_modules/.bin/astro +21 -0
- package/templates/default/node_modules/.bin/astro-check +21 -0
- package/templates/default/node_modules/.bin/grove +21 -0
- package/templates/default/node_modules/.bin/jiti +21 -0
- package/templates/default/node_modules/.bin/prettier +21 -0
- package/templates/default/node_modules/.bin/rollup +21 -0
- package/templates/default/node_modules/.bin/tsc +21 -0
- package/templates/default/node_modules/.bin/tsserver +21 -0
- package/templates/default/node_modules/.bin/tsx +21 -0
- package/templates/default/node_modules/.bin/vite +21 -0
- package/templates/default/node_modules/.bin/yaml +21 -0
- package/templates/default/node_modules/.vite/deps/_metadata.json +79 -0
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +433 -0
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js +5 -0
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +5 -0
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +9 -0
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +241 -0
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +1592 -0
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +186 -0
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +48 -0
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +6779 -0
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +33 -0
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +88 -0
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +72 -0
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +3757 -0
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +54 -0
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/package.json +3 -0
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +213 -0
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +525 -0
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +1020 -0
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +147 -0
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +7 -0
- package/templates/default/package.json +6 -5
- package/templates/default/public/llms-full.txt +14 -0
- package/templates/default/public/llms.txt +11 -0
- package/templates/default/src/components/layout/BaseLayout.astro +10 -57
- package/templates/default/src/components/layout/Footer.astro +16 -134
- package/templates/default/src/components/layout/Header.astro +25 -126
- package/templates/default/src/data/records.ts +161 -0
- package/templates/default/src/lib/markdown.ts +171 -0
- package/templates/default/src/pages/about.astro +95 -429
- package/templates/default/src/pages/index.astro +258 -61
- package/templates/default/src/pages/projects/[slug].astro +480 -0
- package/templates/default/src/pages/projects/index.astro +193 -0
- package/templates/default/src/pages/sitemap.xml.ts +73 -61
- package/templates/default/src/pages/submit.astro +122 -411
- package/templates/default/src/styles/global.css +42 -68
- package/src/components/ActiveFilterChips.astro +0 -20
- package/src/components/DirectoryFilters.astro +0 -100
- package/src/components/DirectoryHero.astro +0 -32
- package/src/components/HealthBadge.astro +0 -12
- package/src/components/LensTabs.astro +0 -26
- package/src/components/MethodologyPanel.astro +0 -20
- package/src/components/Pagination.astro +0 -24
- package/src/components/ProjectDetail.astro +0 -166
- package/src/components/SubmitDraft.astro +0 -137
- package/templates/default/src/components/AppCard.astro +0 -248
- package/templates/default/src/components/AppSection.astro +0 -58
- package/templates/default/src/components/AppsIndexRow.astro +0 -230
- package/templates/default/src/components/AppsPagination.astro +0 -147
- package/templates/default/src/components/CategoryGrid.astro +0 -80
- package/templates/default/src/components/ContributorsGrid.astro +0 -105
- package/templates/default/src/components/DecisionRow.astro +0 -219
- package/templates/default/src/components/ExploreByCategory.astro +0 -74
- package/templates/default/src/components/ExploreByStack.astro +0 -102
- package/templates/default/src/components/Hero.astro +0 -176
- package/templates/default/src/components/MinimalAbout.astro +0 -57
- package/templates/default/src/components/OriginalCollection.astro +0 -144
- package/templates/default/src/components/RefinePanel.astro +0 -272
- package/templates/default/src/components/ScoreBars.astro +0 -72
- package/templates/default/src/components/SmartLensTabs.astro +0 -65
- package/templates/default/src/components/StackGrid.astro +0 -95
- package/templates/default/src/components/WhyThisExists.astro +0 -81
- package/templates/default/src/components/icons/Icon.astro +0 -148
- package/templates/default/src/components/layout/SectionHeader.astro +0 -42
- package/templates/default/src/components/layout/ThemeToggle.astro +0 -97
- package/templates/default/src/data/apps.ts +0 -149
- package/templates/default/src/data/categories.ts +0 -27
- package/templates/default/src/data/config.ts +0 -28
- package/templates/default/src/data/contributors.ts +0 -96
- package/templates/default/src/data/stacks.ts +0 -47
- package/templates/default/src/data/stats.ts +0 -65
- package/templates/default/src/data/types.ts +0 -248
- package/templates/default/src/lib/apps-search.ts +0 -228
- package/templates/default/src/lib/format.ts +0 -36
- package/templates/default/src/lib/lenses.ts +0 -98
- package/templates/default/src/lib/repo.ts +0 -12
- package/templates/default/src/lib/scores.ts +0 -51
- package/templates/default/src/lib/taxonomy-counts.ts +0 -31
- package/templates/default/src/pages/apps/[slug].astro +0 -709
- package/templates/default/src/pages/apps/index.astro +0 -669
- package/templates/default/src/pages/contributors.astro +0 -142
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ExploreByCategory — compact, count-only browse-by-category row.
|
|
4
|
+
*
|
|
5
|
+
* Light sibling of CategoryGrid. Pill-style links with a count.
|
|
6
|
+
* Generic: takes `{ name, slug, count }` entries.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface CategoryEntry {
|
|
10
|
+
name: string;
|
|
11
|
+
slug: string;
|
|
12
|
+
count?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
categories: CategoryEntry[];
|
|
17
|
+
pathPrefix?: string;
|
|
18
|
+
title?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
categories,
|
|
25
|
+
pathPrefix = "/",
|
|
26
|
+
title = "Explore by category",
|
|
27
|
+
description,
|
|
28
|
+
class: className = "",
|
|
29
|
+
} = Astro.props;
|
|
30
|
+
|
|
31
|
+
function buildHref(slug: string): string {
|
|
32
|
+
const base = pathPrefix.replace(/\/$/, "");
|
|
33
|
+
return `${base}?category=${encodeURIComponent(slug)}`;
|
|
34
|
+
}
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
<section class:list={["grove-section-tight", className]}>
|
|
38
|
+
<div class="grove-container-wide mx-auto px-5 sm:px-7">
|
|
39
|
+
<header class="mb-3 flex flex-col gap-1">
|
|
40
|
+
<h2 class="m-0 text-[1.125rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
41
|
+
{title}
|
|
42
|
+
</h2>
|
|
43
|
+
{description && (
|
|
44
|
+
<p class="m-0 text-[0.8125rem] text-ink-500 dark:text-ink-400">
|
|
45
|
+
{description}
|
|
46
|
+
</p>
|
|
47
|
+
)}
|
|
48
|
+
</header>
|
|
49
|
+
<ul class="m-0 flex list-none flex-wrap gap-2 p-0">
|
|
50
|
+
{categories.map((c) => (
|
|
51
|
+
<li>
|
|
52
|
+
<a
|
|
53
|
+
href={buildHref(c.slug)}
|
|
54
|
+
class="grove-badge inline-flex items-center gap-1.5 transition-colors hover:border-ink-400 dark:hover:border-ink-600"
|
|
55
|
+
data-event="explore_category_click"
|
|
56
|
+
data-event-source="explore_category"
|
|
57
|
+
>
|
|
58
|
+
<span>{c.name}</span>
|
|
59
|
+
{typeof c.count === "number" && (
|
|
60
|
+
<span class="font-mono text-ink-400">{c.count}</span>
|
|
61
|
+
)}
|
|
62
|
+
</a>
|
|
63
|
+
</li>
|
|
64
|
+
))}
|
|
65
|
+
</ul>
|
|
66
|
+
</div>
|
|
67
|
+
</section>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ExploreByStack — compact, count-only browse-by-stack row.
|
|
4
|
+
*
|
|
5
|
+
* The "explore" companion to StackGrid. Renders a single dense row
|
|
6
|
+
* of pill links (one per stack) with a count next to each. No
|
|
7
|
+
* cards, no blurbs — meant for the section that fits below the
|
|
8
|
+
* primary lens grid.
|
|
9
|
+
*
|
|
10
|
+
* Generic: takes a list of `{ name, slug, count }` entries. No
|
|
11
|
+
* blueprint-specific terms.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface StackEntry {
|
|
15
|
+
name: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
count?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
stacks: StackEntry[];
|
|
22
|
+
pathPrefix?: string;
|
|
23
|
+
title?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
class?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
stacks,
|
|
30
|
+
pathPrefix = "/",
|
|
31
|
+
title = "Explore by stack",
|
|
32
|
+
description,
|
|
33
|
+
class: className = "",
|
|
34
|
+
} = Astro.props;
|
|
35
|
+
|
|
36
|
+
function buildHref(slug: string): string {
|
|
37
|
+
const base = pathPrefix.replace(/\/$/, "");
|
|
38
|
+
return `${base}?stack=${encodeURIComponent(slug)}`;
|
|
39
|
+
}
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
<section class:list={["grove-section-tight", className]}>
|
|
43
|
+
<div class="grove-container-wide mx-auto px-5 sm:px-7">
|
|
44
|
+
<header class="mb-3 flex flex-col gap-1">
|
|
45
|
+
<h2 class="m-0 text-[1.125rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
46
|
+
{title}
|
|
47
|
+
</h2>
|
|
48
|
+
{description && (
|
|
49
|
+
<p class="m-0 text-[0.8125rem] text-ink-500 dark:text-ink-400">
|
|
50
|
+
{description}
|
|
51
|
+
</p>
|
|
52
|
+
)}
|
|
53
|
+
</header>
|
|
54
|
+
<ul class="m-0 flex list-none flex-wrap gap-2 p-0">
|
|
55
|
+
{stacks.map((s) => (
|
|
56
|
+
<li>
|
|
57
|
+
<a
|
|
58
|
+
href={buildHref(s.slug)}
|
|
59
|
+
class="grove-badge inline-flex items-center gap-1.5 transition-colors hover:border-ink-400 dark:hover:border-ink-600"
|
|
60
|
+
data-event="explore_stack_click"
|
|
61
|
+
data-event-source="explore_stack"
|
|
62
|
+
>
|
|
63
|
+
<span>{s.name}</span>
|
|
64
|
+
{typeof s.count === "number" && (
|
|
65
|
+
<span class="font-mono text-ink-400">{s.count}</span>
|
|
66
|
+
)}
|
|
67
|
+
</a>
|
|
68
|
+
</li>
|
|
69
|
+
))}
|
|
70
|
+
</ul>
|
|
71
|
+
</div>
|
|
72
|
+
</section>
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Hero — the home page banner.
|
|
4
|
+
*
|
|
5
|
+
* Renders: trust stats (apps, categories, stacks, contributors, stars),
|
|
6
|
+
* a search form, an animated gradient backdrop, and up to 2 CTAs.
|
|
7
|
+
*
|
|
8
|
+
* The search form is a static GET form pointing at `searchHref` (the
|
|
9
|
+
* list page) with a `q` query param. JS enhancement is a consumer
|
|
10
|
+
* concern (e.g. the showcase wires the home hero's input into the
|
|
11
|
+
* list page filter via a custom event).
|
|
12
|
+
*
|
|
13
|
+
* Generic: every value comes through props — site name, tagline,
|
|
14
|
+
* stats, search target. No siteConfig import.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface CTA {
|
|
18
|
+
label: string;
|
|
19
|
+
href: string;
|
|
20
|
+
variant?: "primary" | "outline" | "ghost";
|
|
21
|
+
external?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface HeroStats {
|
|
25
|
+
apps?: number;
|
|
26
|
+
categories?: number;
|
|
27
|
+
stacks?: number;
|
|
28
|
+
contributors?: number;
|
|
29
|
+
stars?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Props {
|
|
33
|
+
/** Site/brand name — rendered as the heading. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Short tagline displayed under the heading. */
|
|
36
|
+
tagline: string;
|
|
37
|
+
/** Longer description, rendered below the tagline. */
|
|
38
|
+
description?: string;
|
|
39
|
+
/** Trust stats row. */
|
|
40
|
+
stats?: HeroStats;
|
|
41
|
+
/** Placeholder for the search input. */
|
|
42
|
+
searchPlaceholder?: string;
|
|
43
|
+
/** Form action — the list page URL (e.g. "/projects"). */
|
|
44
|
+
searchHref?: string;
|
|
45
|
+
/** Optional CTAs rendered next to the search form. */
|
|
46
|
+
ctas?: CTA[];
|
|
47
|
+
/** Extra class for the outer <section>. */
|
|
48
|
+
class?: string;
|
|
49
|
+
/** Eyebrow text rendered above the title. */
|
|
50
|
+
eyebrow?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const {
|
|
54
|
+
name,
|
|
55
|
+
tagline,
|
|
56
|
+
description,
|
|
57
|
+
stats = {},
|
|
58
|
+
searchPlaceholder = "Search the directory...",
|
|
59
|
+
searchHref = "/",
|
|
60
|
+
ctas = [],
|
|
61
|
+
class: className = "",
|
|
62
|
+
eyebrow,
|
|
63
|
+
} = Astro.props;
|
|
64
|
+
|
|
65
|
+
function fmt(n: number | undefined): string {
|
|
66
|
+
if (typeof n !== "number" || !Number.isFinite(n)) return "—";
|
|
67
|
+
if (n >= 1000) {
|
|
68
|
+
const k = n / 1000;
|
|
69
|
+
return (k >= 10 ? Math.round(k).toString() : k.toFixed(1)) + "k";
|
|
70
|
+
}
|
|
71
|
+
return n.toString();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const hasStats =
|
|
75
|
+
stats.apps !== undefined ||
|
|
76
|
+
stats.categories !== undefined ||
|
|
77
|
+
stats.stacks !== undefined ||
|
|
78
|
+
stats.contributors !== undefined ||
|
|
79
|
+
stats.stars !== undefined;
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
<section
|
|
83
|
+
class:list={[
|
|
84
|
+
"grove-hero relative overflow-hidden border-b border-ink-200 dark:border-ink-800",
|
|
85
|
+
className,
|
|
86
|
+
]}
|
|
87
|
+
aria-labelledby="grove-hero-heading"
|
|
88
|
+
>
|
|
89
|
+
<div
|
|
90
|
+
aria-hidden="true"
|
|
91
|
+
class="grove-hero__gradient pointer-events-none absolute inset-0 -z-10"
|
|
92
|
+
></div>
|
|
93
|
+
|
|
94
|
+
<div class="grove-container-wide mx-auto px-5 py-16 sm:px-7 sm:py-20">
|
|
95
|
+
<div class="max-w-3xl">
|
|
96
|
+
{eyebrow && (
|
|
97
|
+
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
98
|
+
{eyebrow}
|
|
99
|
+
</span>
|
|
100
|
+
)}
|
|
101
|
+
<h1
|
|
102
|
+
id="grove-hero-heading"
|
|
103
|
+
class="m-0 mt-2 text-[2.25rem] font-semibold leading-[1.05] tracking-tight text-ink-900 sm:text-[3rem] dark:text-ink-100"
|
|
104
|
+
>
|
|
105
|
+
{name}
|
|
106
|
+
</h1>
|
|
107
|
+
<p class="m-0 mt-4 max-w-2xl text-lg leading-relaxed text-ink-700 dark:text-ink-200">
|
|
108
|
+
{tagline}
|
|
109
|
+
</p>
|
|
110
|
+
{description && (
|
|
111
|
+
<p class="m-0 mt-2 max-w-2xl text-[0.9375rem] leading-relaxed text-ink-500 dark:text-ink-400">
|
|
112
|
+
{description}
|
|
113
|
+
</p>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
<form
|
|
117
|
+
class="mt-8 flex flex-col gap-2 sm:flex-row sm:items-center"
|
|
118
|
+
role="search"
|
|
119
|
+
action={searchHref}
|
|
120
|
+
method="get"
|
|
121
|
+
data-grove-hero-search
|
|
122
|
+
>
|
|
123
|
+
<label class="relative flex-1">
|
|
124
|
+
<span class="sr-only">Search</span>
|
|
125
|
+
<svg
|
|
126
|
+
aria-hidden="true"
|
|
127
|
+
viewBox="0 0 16 16"
|
|
128
|
+
width="14"
|
|
129
|
+
height="14"
|
|
130
|
+
class="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-ink-400"
|
|
131
|
+
fill="currentColor"
|
|
132
|
+
>
|
|
133
|
+
<path d="M10.68 11.74a6 6 0 1 1 1.06-1.06l3.04 3.04a.75.75 0 1 1-1.06 1.06l-3.04-3.04ZM11.5 7a4.5 4.5 0 1 0-9 0 4.5 4.5 0 0 0 9 0Z"></path>
|
|
134
|
+
</svg>
|
|
135
|
+
<input
|
|
136
|
+
type="search"
|
|
137
|
+
name="q"
|
|
138
|
+
placeholder={searchPlaceholder}
|
|
139
|
+
autocomplete="off"
|
|
140
|
+
class="w-full rounded-md border border-ink-200 bg-white py-2.5 pl-9 pr-3 text-sm placeholder:text-ink-400 focus:border-ink-900 focus:outline-none focus:ring-2 focus:ring-ink-900/10 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-100"
|
|
141
|
+
/>
|
|
142
|
+
</label>
|
|
143
|
+
<button type="submit" class="grove-btn grove-btn-primary">
|
|
144
|
+
Search
|
|
145
|
+
</button>
|
|
146
|
+
</form>
|
|
147
|
+
|
|
148
|
+
{ctas.length > 0 && (
|
|
149
|
+
<div class="mt-4 flex flex-wrap items-center gap-2">
|
|
150
|
+
{ctas.map((cta) => (
|
|
151
|
+
<a
|
|
152
|
+
href={cta.href}
|
|
153
|
+
class:list={[
|
|
154
|
+
"grove-btn",
|
|
155
|
+
cta.variant === "primary"
|
|
156
|
+
? "grove-btn-primary"
|
|
157
|
+
: cta.variant === "ghost"
|
|
158
|
+
? "grove-btn-ghost"
|
|
159
|
+
: "grove-btn-outline",
|
|
160
|
+
]}
|
|
161
|
+
{...(cta.external
|
|
162
|
+
? { target: "_blank", rel: "noopener noreferrer" }
|
|
163
|
+
: {})}
|
|
164
|
+
data-event="hero_cta_click"
|
|
165
|
+
data-event-source="hero"
|
|
166
|
+
>
|
|
167
|
+
{cta.label}
|
|
168
|
+
</a>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
|
|
173
|
+
{hasStats && (
|
|
174
|
+
<dl
|
|
175
|
+
class="mt-10 flex flex-wrap items-center gap-x-6 gap-y-3 text-2xs text-ink-500 dark:text-ink-400"
|
|
176
|
+
aria-label="Directory statistics"
|
|
177
|
+
>
|
|
178
|
+
{stats.apps !== undefined && (
|
|
179
|
+
<div class="flex items-baseline gap-1.5">
|
|
180
|
+
<dd class="m-0 font-mono text-base font-semibold text-ink-900 dark:text-ink-100">
|
|
181
|
+
{fmt(stats.apps)}
|
|
182
|
+
</dd>
|
|
183
|
+
<dt>apps</dt>
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
{stats.categories !== undefined && (
|
|
187
|
+
<div class="flex items-baseline gap-1.5">
|
|
188
|
+
<dd class="m-0 font-mono text-base font-semibold text-ink-900 dark:text-ink-100">
|
|
189
|
+
{fmt(stats.categories)}
|
|
190
|
+
</dd>
|
|
191
|
+
<dt>categories</dt>
|
|
192
|
+
</div>
|
|
193
|
+
)}
|
|
194
|
+
{stats.stacks !== undefined && (
|
|
195
|
+
<div class="flex items-baseline gap-1.5">
|
|
196
|
+
<dd class="m-0 font-mono text-base font-semibold text-ink-900 dark:text-ink-100">
|
|
197
|
+
{fmt(stats.stacks)}
|
|
198
|
+
</dd>
|
|
199
|
+
<dt>stacks</dt>
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
{stats.contributors !== undefined && (
|
|
203
|
+
<div class="flex items-baseline gap-1.5">
|
|
204
|
+
<dd class="m-0 font-mono text-base font-semibold text-ink-900 dark:text-ink-100">
|
|
205
|
+
{fmt(stats.contributors)}
|
|
206
|
+
</dd>
|
|
207
|
+
<dt>contributors</dt>
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
{stats.stars !== undefined && (
|
|
211
|
+
<div class="flex items-baseline gap-1.5">
|
|
212
|
+
<dd class="m-0 font-mono text-base font-semibold text-ink-900 dark:text-ink-100">
|
|
213
|
+
{fmt(stats.stars)}
|
|
214
|
+
</dd>
|
|
215
|
+
<dt>total stars</dt>
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
</dl>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</section>
|
|
223
|
+
|
|
224
|
+
<style>
|
|
225
|
+
.grove-hero__gradient {
|
|
226
|
+
background: linear-gradient(
|
|
227
|
+
120deg,
|
|
228
|
+
rgba(31, 111, 235, 0.08) 0%,
|
|
229
|
+
rgba(110, 231, 183, 0.06) 35%,
|
|
230
|
+
rgba(253, 186, 116, 0.06) 65%,
|
|
231
|
+
rgba(147, 197, 253, 0.08) 100%
|
|
232
|
+
);
|
|
233
|
+
background-size: 200% 200%;
|
|
234
|
+
animation: var(--animate-gradient-shift, gradient-shift 8s ease-in-out infinite);
|
|
235
|
+
}
|
|
236
|
+
:global(.dark) .grove-hero__gradient {
|
|
237
|
+
background: linear-gradient(
|
|
238
|
+
120deg,
|
|
239
|
+
rgba(31, 111, 235, 0.18) 0%,
|
|
240
|
+
rgba(6, 95, 70, 0.12) 35%,
|
|
241
|
+
rgba(67, 20, 7, 0.12) 65%,
|
|
242
|
+
rgba(30, 64, 175, 0.18) 100%
|
|
243
|
+
);
|
|
244
|
+
background-size: 200% 200%;
|
|
245
|
+
}
|
|
246
|
+
</style>
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Icon — brand icon registry.
|
|
4
|
+
*
|
|
5
|
+
* Renders an `<img src="/icons/{stacks|platforms|brands}/{name}.svg" />`
|
|
6
|
+
* for a given registry name. The Astro integration ships the default
|
|
7
|
+
* `public/icons/{stacks,platforms}/` assets inside the `default`
|
|
8
|
+
* template's `public/` directory; consumers can ship their own SVGs
|
|
9
|
+
* under the same path (or override the `base` prop) without forking
|
|
10
|
+
* this component.
|
|
11
|
+
*
|
|
12
|
+
* The component is **generic** — it does not import any consumer-side
|
|
13
|
+
* `siteConfig`, `app`, or blueprint-specific module. Every consumer
|
|
14
|
+
* supplies its own icon list (and optionally an `aliases` map for
|
|
15
|
+
* friendly ids) via props, and any `name` not in the registry
|
|
16
|
+
* falls back to the initials placeholder.
|
|
17
|
+
*
|
|
18
|
+
* `category` selects the subfolder:
|
|
19
|
+
*
|
|
20
|
+
* - "stack" → `/icons/stacks/{name}.svg`
|
|
21
|
+
* - "platform" → `/icons/platforms/{name}.svg`
|
|
22
|
+
* - "brand" → `/icons/brands/{name}.svg` (consumer-supplied)
|
|
23
|
+
*
|
|
24
|
+
* Theme-aware: when the consumer sets `mode: "auto"` the component
|
|
25
|
+
* looks for `{name}-light.svg` and `{name}-dark.svg` siblings in
|
|
26
|
+
* addition to the default `{name}.svg` and switches the `src` based
|
|
27
|
+
* on the resolved theme via the inline `data-grove-icon` script.
|
|
28
|
+
* For static (non-themed) icons this is a no-op — set `mode: "static"`
|
|
29
|
+
* (the default) to keep the markup simple and avoid hydration cost.
|
|
30
|
+
*
|
|
31
|
+
* Aliases: a tiny `aliases` map (e.g. `rn → react-native`) lets the
|
|
32
|
+
* registry accept multiple ids for the same asset without a renames
|
|
33
|
+
* pass. The lookup falls through the aliases map before failing over
|
|
34
|
+
* to the initials placeholder.
|
|
35
|
+
*/
|
|
36
|
+
import { getOwnerAndRepoFromRepoUrl } from "../lib/repo";
|
|
37
|
+
|
|
38
|
+
export type IconCategory = "stack" | "platform" | "brand";
|
|
39
|
+
export type IconMode = "static" | "auto";
|
|
40
|
+
|
|
41
|
+
interface Props {
|
|
42
|
+
/**
|
|
43
|
+
* Registry id. Resolved through `aliases` first, then matched
|
|
44
|
+
* against the SVG file name (case-insensitive, slug-normalized).
|
|
45
|
+
*/
|
|
46
|
+
name: string;
|
|
47
|
+
/** Subfolder under `/icons/`. */
|
|
48
|
+
category: IconCategory;
|
|
49
|
+
/** Pixel size (square). Defaults to 16. */
|
|
50
|
+
size?: number;
|
|
51
|
+
/** Extra class for the outer wrapper. */
|
|
52
|
+
class?: string;
|
|
53
|
+
/** Tooltip / aria-label. */
|
|
54
|
+
title?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Static (single SVG) or auto (light/dark variants via inline
|
|
57
|
+
* script). Default "static".
|
|
58
|
+
*/
|
|
59
|
+
mode?: IconMode;
|
|
60
|
+
/**
|
|
61
|
+
* Optional id → id remap. Lookup goes `name → aliases[name] →
|
|
62
|
+
* {name}.svg` (case-insensitive).
|
|
63
|
+
*/
|
|
64
|
+
aliases?: Record<string, string>;
|
|
65
|
+
/**
|
|
66
|
+
* Initials to render when the icon file is missing. Defaults to
|
|
67
|
+
* the first 1–2 characters of `name`.
|
|
68
|
+
*/
|
|
69
|
+
fallbackInitials?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Base path for icon files. Defaults to `/icons` (matches the
|
|
72
|
+
* default template's `public/icons/`).
|
|
73
|
+
*/
|
|
74
|
+
base?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const {
|
|
78
|
+
name,
|
|
79
|
+
category,
|
|
80
|
+
size = 16,
|
|
81
|
+
class: className = "",
|
|
82
|
+
title,
|
|
83
|
+
mode = "static",
|
|
84
|
+
aliases,
|
|
85
|
+
fallbackInitials,
|
|
86
|
+
base = "/icons",
|
|
87
|
+
} = Astro.props;
|
|
88
|
+
|
|
89
|
+
// Normalize: lowercase + replace spaces/underscores with dashes.
|
|
90
|
+
function slugify(s: string): string {
|
|
91
|
+
return s.toLowerCase().trim().replace(/[\s_]+/g, "-");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Resolve `name` through the aliases map.
|
|
95
|
+
function resolveName(raw: string): string {
|
|
96
|
+
if (!raw) return "";
|
|
97
|
+
if (aliases) {
|
|
98
|
+
const lower = raw.toLowerCase();
|
|
99
|
+
for (const [k, v] of Object.entries(aliases)) {
|
|
100
|
+
if (k.toLowerCase() === lower) return slugify(v);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return slugify(raw);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const resolved = resolveName(name);
|
|
107
|
+
const folder =
|
|
108
|
+
category === "stack" ? "stacks" : category === "platform" ? "platforms" : "brands";
|
|
109
|
+
const baseUrl = `${base.replace(/\/$/, "")}/${folder}/${resolved}`;
|
|
110
|
+
|
|
111
|
+
// Static: just point at the canonical svg.
|
|
112
|
+
const staticSrc = `${baseUrl}.svg`;
|
|
113
|
+
|
|
114
|
+
// Initials fallback: first 1–2 alphanumerics, uppercased.
|
|
115
|
+
const init = (fallbackInitials ?? name ?? "")
|
|
116
|
+
.replace(/[^a-z0-9]/gi, "")
|
|
117
|
+
.slice(0, 2)
|
|
118
|
+
.toUpperCase();
|
|
119
|
+
const showFallback = !resolved;
|
|
120
|
+
|
|
121
|
+
// Build aria-label.
|
|
122
|
+
const ariaLabel = title ?? name;
|
|
123
|
+
const a11y: Record<string, string> = title
|
|
124
|
+
? { "aria-label": ariaLabel, role: "img" }
|
|
125
|
+
: { "aria-hidden": "true", "focusable": "false" };
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
showFallback ? (
|
|
130
|
+
<span
|
|
131
|
+
class:list={["grove-icon grove-icon-fallback", className]}
|
|
132
|
+
style={`width:${size}px;height:${size}px;font-size:${Math.max(10, size - 6)}px;`}
|
|
133
|
+
{...a11y}
|
|
134
|
+
>
|
|
135
|
+
{init || "·"}
|
|
136
|
+
</span>
|
|
137
|
+
) : mode === "auto" ? (
|
|
138
|
+
<img
|
|
139
|
+
src={staticSrc}
|
|
140
|
+
data-grove-icon
|
|
141
|
+
data-grove-icon-light={`${baseUrl}-light.svg`}
|
|
142
|
+
data-grove-icon-dark={`${baseUrl}-dark.svg`}
|
|
143
|
+
data-grove-icon-fallback={staticSrc}
|
|
144
|
+
width={size}
|
|
145
|
+
height={size}
|
|
146
|
+
alt=""
|
|
147
|
+
class:list={["grove-icon grove-icon-themed", className]}
|
|
148
|
+
{...a11y}
|
|
149
|
+
/>
|
|
150
|
+
) : (
|
|
151
|
+
<img
|
|
152
|
+
src={staticSrc}
|
|
153
|
+
width={size}
|
|
154
|
+
height={size}
|
|
155
|
+
alt=""
|
|
156
|
+
class:list={["grove-icon", className]}
|
|
157
|
+
loading="lazy"
|
|
158
|
+
decoding="async"
|
|
159
|
+
{...a11y}
|
|
160
|
+
/>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
<style>
|
|
165
|
+
.grove-icon {
|
|
166
|
+
display: inline-block;
|
|
167
|
+
vertical-align: middle;
|
|
168
|
+
flex-shrink: 0;
|
|
169
|
+
}
|
|
170
|
+
.grove-icon-themed {
|
|
171
|
+
/* Theme-aware: dark/light variants selected by the inline script
|
|
172
|
+
below based on `data-resolved-theme` set by BaseLayout's
|
|
173
|
+
THEME_INIT. Default to the light variant so the markup looks
|
|
174
|
+
correct even before the script runs. */
|
|
175
|
+
}
|
|
176
|
+
.grove-icon-fallback {
|
|
177
|
+
display: inline-flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
justify-content: center;
|
|
180
|
+
border-radius: 0.25rem;
|
|
181
|
+
background: var(--color-ink-100, #f4f4f5);
|
|
182
|
+
color: var(--color-ink-700, #3f3f46);
|
|
183
|
+
font-weight: 600;
|
|
184
|
+
line-height: 1;
|
|
185
|
+
text-transform: uppercase;
|
|
186
|
+
user-select: none;
|
|
187
|
+
}
|
|
188
|
+
:global(.dark) .grove-icon-fallback {
|
|
189
|
+
background: var(--color-ink-800, #27272a);
|
|
190
|
+
color: var(--color-ink-200, #e4e4e7);
|
|
191
|
+
}
|
|
192
|
+
</style>
|
|
193
|
+
|
|
194
|
+
{mode === "auto" && (
|
|
195
|
+
<script is:inline>
|
|
196
|
+
// Theme-aware icon swap. Runs after THEME_INIT (BaseLayout) so
|
|
197
|
+
// `data-resolved-theme` is already populated. Idempotent — the
|
|
198
|
+
// MutationObserver picks up theme changes from ThemeToggle.
|
|
199
|
+
(function () {
|
|
200
|
+
function swap() {
|
|
201
|
+
var theme =
|
|
202
|
+
document.documentElement.dataset.resolvedTheme || "light";
|
|
203
|
+
var nodes = document.querySelectorAll(
|
|
204
|
+
"img[data-grove-icon]:not([data-grove-icon-bound])",
|
|
205
|
+
);
|
|
206
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
207
|
+
var img = nodes[i];
|
|
208
|
+
img.setAttribute("data-grove-icon-bound", "1");
|
|
209
|
+
var next =
|
|
210
|
+
theme === "dark"
|
|
211
|
+
? img.getAttribute("data-grove-icon-dark")
|
|
212
|
+
: img.getAttribute("data-grove-icon-light");
|
|
213
|
+
if (next) img.src = next;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
swap();
|
|
217
|
+
new MutationObserver(swap).observe(document.documentElement, {
|
|
218
|
+
attributes: true,
|
|
219
|
+
attributeFilter: ["data-resolved-theme"],
|
|
220
|
+
});
|
|
221
|
+
})();
|
|
222
|
+
</script>
|
|
223
|
+
)}
|