@grove-dev/astro 0.2.10 → 0.2.14
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/README.md +26 -18
- package/dist/lib/display.d.ts +11 -17
- package/dist/lib/display.d.ts.map +1 -1
- package/dist/lib/display.js +43 -43
- package/dist/lib/display.js.map +1 -1
- package/dist/lib/format.d.ts +6 -7
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +11 -12
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/lenses.d.ts +35 -73
- package/dist/lib/lenses.d.ts.map +1 -1
- package/dist/lib/lenses.js +144 -126
- package/dist/lib/lenses.js.map +1 -1
- package/dist/lib/repo.d.ts +12 -22
- package/dist/lib/repo.d.ts.map +1 -1
- package/dist/lib/repo.js +13 -25
- package/dist/lib/repo.js.map +1 -1
- package/dist/lib/scores.d.ts +15 -16
- package/dist/lib/scores.d.ts.map +1 -1
- package/dist/lib/scores.js +4 -11
- package/dist/lib/scores.js.map +1 -1
- package/dist/lib/search.d.ts +44 -129
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/search.js +192 -180
- package/dist/lib/search.js.map +1 -1
- package/dist/lib/taxonomy-counts.d.ts +7 -21
- package/dist/lib/taxonomy-counts.d.ts.map +1 -1
- package/dist/lib/taxonomy-counts.js +12 -31
- package/dist/lib/taxonomy-counts.js.map +1 -1
- package/package.json +4 -4
- package/src/components/CategoryGrid.astro +43 -31
- package/src/components/ContributorsGrid.astro +3 -3
- package/src/components/CurationGrid.astro +41 -0
- package/src/components/ExploreByStack.astro +6 -0
- package/src/components/FilterGroupMenu.astro +49 -0
- package/src/components/FilterOptions.astro +44 -0
- package/src/components/Hero.astro +198 -147
- package/src/components/Icon.astro +10 -3
- package/src/components/IndexRow.astro +75 -0
- package/src/components/ItemCard.astro +266 -248
- package/src/components/Pagination.astro +63 -0
- package/src/components/RecordSection.astro +70 -34
- package/src/components/RefinePanel.astro +115 -141
- package/src/components/StackGrid.astro +32 -24
- package/src/components/WhyThisExists.astro +32 -32
- package/src/layouts/BaseLayout.astro +104 -49
- package/src/layouts/Container.astro +5 -44
- package/src/layouts/Footer.astro +31 -20
- package/src/layouts/Header.astro +40 -19
- package/src/layouts/SectionHeader.astro +27 -43
- package/src/layouts/Seo.astro +38 -56
- package/src/layouts/ThemeToggle.astro +35 -48
- package/src/lib/display.ts +46 -53
- package/src/lib/format.ts +11 -12
- package/src/lib/lenses.ts +151 -172
- package/src/lib/repo.ts +17 -28
- package/src/lib/scores.ts +15 -19
- package/src/lib/search.ts +218 -291
- package/src/lib/taxonomy-counts.ts +12 -46
- package/src/styles.css +209 -1
- package/templates/default/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/templates/default/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/templates/default/.github/ISSUE_TEMPLATE/record_submission.md +48 -0
- package/templates/default/.github/pull_request_template.md +27 -0
- package/templates/default/.github/workflows/build.yml +55 -0
- package/templates/default/.github/workflows/cleanup-stale-records.yml +48 -0
- package/templates/default/.github/workflows/daily-refresh.yml +53 -0
- package/templates/default/.github/workflows/sync-contributors.yml +53 -0
- package/templates/default/.github/workflows/sync-github-metadata.yml +58 -0
- package/templates/default/.github/workflows/validate-data.yml +35 -0
- package/templates/default/data/records/coolify.yml +46 -0
- package/templates/default/data/records/gitea.yml +46 -0
- package/templates/default/data/records/hoppscotch.yml +47 -0
- package/templates/default/data/records/inventree.yml +47 -0
- package/templates/default/data/records/label-studio.yml +46 -0
- package/templates/default/data/records/logseq.yml +48 -0
- package/templates/default/data/records/private-gpt.yml +47 -0
- package/templates/default/data/records/wakapi.yml +44 -0
- package/templates/default/data/taxonomy/categories.yml +47 -0
- package/templates/default/data/taxonomy/distribution-channels.yml +43 -0
- package/templates/default/data/taxonomy/platforms.yml +27 -0
- package/templates/default/data/taxonomy/stacks.yml +41 -0
- package/templates/default/grove.config.ts +1 -1
- package/templates/default/package.json +5 -4
- package/templates/default/public/icons/stacks/bun.svg +1 -0
- package/templates/default/public/icons/stacks/clojure.svg +1 -0
- package/templates/default/public/icons/stacks/clojurescript.svg +1 -0
- package/templates/default/public/icons/stacks/deno.svg +1 -0
- package/templates/default/public/icons/stacks/django.svg +1 -0
- package/templates/default/public/icons/stacks/docker.svg +1 -0
- package/templates/default/public/icons/stacks/go.svg +1 -0
- package/templates/default/public/icons/stacks/llm.svg +1 -0
- package/templates/default/public/icons/stacks/node.js.svg +1 -0
- package/templates/default/public/icons/stacks/sveltekit.svg +1 -0
- package/templates/default/public/icons/stacks/vue.svg +1 -0
- package/templates/default/public/llms-full.txt +197 -5
- package/templates/default/public/llms.txt +107 -6
- package/templates/default/public/sitemap.xml +63 -0
- package/templates/default/scripts/build-llms.mjs +123 -0
- package/templates/default/scripts/build-records-json.mjs +27 -0
- package/templates/default/scripts/build-sitemap.mjs +20 -0
- package/templates/default/scripts/cleanup-stale-records.mjs +20 -0
- package/templates/default/scripts/enrich-github-metadata.mjs +99 -0
- package/templates/default/scripts/fetch-icons.mjs +137 -0
- package/templates/default/scripts/migrate-legacy-to-schema-v1.mjs +86 -0
- package/templates/default/scripts/parse-legacy-readme.mjs +59 -0
- package/templates/default/scripts/refresh-records-activity.mjs +24 -0
- package/templates/default/scripts/repair-license-format.mjs +72 -0
- package/templates/default/scripts/report-cleanup-candidates.mjs +21 -0
- package/templates/default/scripts/seed-from-github.mjs +62 -0
- package/templates/default/scripts/sync-contributors.mjs +145 -0
- package/templates/default/scripts/sync-github-metadata.mjs +28 -0
- package/templates/default/scripts/validate-records.mjs +27 -0
- package/templates/default/src/data/records.ts +138 -51
- package/templates/default/src/pages/404.astro +64 -0
- package/templates/default/src/pages/{projects/[slug].astro → [slug]/[recordSlug].astro} +236 -66
- package/templates/default/src/pages/[slug]/index.astro +477 -0
- package/templates/default/src/pages/about.astro +165 -82
- package/templates/default/src/pages/apps/[recordSlug].astro +33 -0
- package/templates/default/src/pages/contributors.astro +184 -0
- package/templates/default/src/pages/index.astro +119 -162
- package/templates/default/src/pages/submit.astro +276 -114
- package/templates/default/wrangler.jsonc +28 -0
- package/LICENSE +0 -21
- package/src/components/AppsIndexRow.astro +0 -204
- package/src/components/AppsPagination.astro +0 -126
- package/templates/default/.astro/content.d.ts +0 -155
- package/templates/default/.astro/settings.json +0 -5
- package/templates/default/.astro/types.d.ts +0 -1
- package/templates/default/data/generated/apps.json +0 -1
- package/templates/default/data/generated/records.full.json +0 -8
- package/templates/default/data/generated/records.index.json +0 -7
- package/templates/default/data/generated/records.json +0 -8
- package/templates/default/data/generated/repo-stats.json +0 -1
- package/templates/default/data/generated/site-config.json +0 -31
- package/templates/default/dist/_astro/BaseLayout.Dz_HR-68.css +0 -1
- package/templates/default/dist/about/index.html +0 -165
- package/templates/default/dist/icons/platforms/android.svg +0 -1
- package/templates/default/dist/icons/platforms/apple-dark.svg +0 -1
- package/templates/default/dist/icons/platforms/apple-light.svg +0 -1
- package/templates/default/dist/icons/platforms/chrome.svg +0 -1
- package/templates/default/dist/icons/platforms/chromeos.svg +0 -5
- package/templates/default/dist/icons/platforms/desktop.svg +0 -4
- package/templates/default/dist/icons/platforms/embedded.svg +0 -5
- package/templates/default/dist/icons/platforms/ios.svg +0 -1
- package/templates/default/dist/icons/platforms/linux.svg +0 -1
- package/templates/default/dist/icons/platforms/macos.svg +0 -1
- package/templates/default/dist/icons/platforms/ubuntu.svg +0 -1
- package/templates/default/dist/icons/platforms/web.svg +0 -5
- package/templates/default/dist/icons/platforms/windows.svg +0 -6
- package/templates/default/dist/icons/stacks/android.svg +0 -1
- package/templates/default/dist/icons/stacks/apple-dark.svg +0 -1
- package/templates/default/dist/icons/stacks/apple-light.svg +0 -1
- package/templates/default/dist/icons/stacks/capacitor.svg +0 -14
- package/templates/default/dist/icons/stacks/dart.svg +0 -1
- package/templates/default/dist/icons/stacks/firebase.svg +0 -1
- package/templates/default/dist/icons/stacks/flutter.svg +0 -1
- package/templates/default/dist/icons/stacks/graphql.svg +0 -1
- package/templates/default/dist/icons/stacks/ionic.svg +0 -4
- package/templates/default/dist/icons/stacks/java.svg +0 -1
- package/templates/default/dist/icons/stacks/javascript.svg +0 -1
- package/templates/default/dist/icons/stacks/kotlin.svg +0 -1
- package/templates/default/dist/icons/stacks/mongodb.svg +0 -1
- package/templates/default/dist/icons/stacks/nodejs.svg +0 -1
- package/templates/default/dist/icons/stacks/python.svg +0 -1
- package/templates/default/dist/icons/stacks/react-native.svg +0 -1
- package/templates/default/dist/icons/stacks/react.svg +0 -1
- package/templates/default/dist/icons/stacks/rust.svg +0 -1
- package/templates/default/dist/icons/stacks/solidity.svg +0 -1
- package/templates/default/dist/icons/stacks/swift.svg +0 -1
- package/templates/default/dist/icons/stacks/tensorflow.svg +0 -1
- package/templates/default/dist/icons/stacks/typescript.svg +0 -1
- package/templates/default/dist/index.html +0 -55
- package/templates/default/dist/llms-full.txt +0 -14
- package/templates/default/dist/llms.txt +0 -11
- package/templates/default/dist/og-image.svg +0 -48
- package/templates/default/dist/projects/index.html +0 -61
- package/templates/default/dist/robots.txt +0 -53
- package/templates/default/dist/sitemap.xml +0 -21
- package/templates/default/dist/submit/index.html +0 -69
- package/templates/default/node_modules/.bin/astro +0 -21
- package/templates/default/node_modules/.bin/astro-check +0 -21
- package/templates/default/node_modules/.bin/grove +0 -21
- package/templates/default/node_modules/.bin/jiti +0 -21
- package/templates/default/node_modules/.bin/prettier +0 -21
- package/templates/default/node_modules/.bin/rollup +0 -21
- package/templates/default/node_modules/.bin/tsc +0 -21
- package/templates/default/node_modules/.bin/tsserver +0 -21
- package/templates/default/node_modules/.bin/tsx +0 -21
- package/templates/default/node_modules/.bin/vite +0 -21
- package/templates/default/node_modules/.bin/yaml +0 -21
- package/templates/default/node_modules/.vite/deps/_metadata.json +0 -79
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +0 -433
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js +0 -5
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +0 -5
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +0 -9
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -241
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +0 -1592
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +0 -30
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +0 -186
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +0 -48
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +0 -6779
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +0 -33
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +0 -88
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +0 -72
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +0 -3757
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +0 -54
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/package.json +0 -3
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +0 -213
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +0 -525
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +0 -1020
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +0 -147
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +0 -7
- package/templates/default/src/components/layout/BaseLayout.astro +0 -47
- package/templates/default/src/components/layout/Container.astro +0 -18
- package/templates/default/src/components/layout/Footer.astro +0 -27
- package/templates/default/src/components/layout/Header.astro +0 -43
- package/templates/default/src/lib/markdown.ts +0 -171
- package/templates/default/src/pages/projects/index.astro +0 -193
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* AppsIndexRow — compact table row for the list page.
|
|
4
|
-
*
|
|
5
|
-
* Renders: avatar + name + description + 3 metadata tiles
|
|
6
|
-
* (license, stars, updated) + chip row (stack/platform) + two
|
|
7
|
-
* CTAs (Details, GitHub). Designed for a dense table that the
|
|
8
|
-
* client-side filter script (and screen readers) can scan.
|
|
9
|
-
*
|
|
10
|
-
* `data-*` attributes are emitted so the consumer's filter script
|
|
11
|
-
* can read them and toggle visibility without re-rendering.
|
|
12
|
-
*
|
|
13
|
-
* Generic: takes a `Resource`. For non-project kinds the project
|
|
14
|
-
* fields are simply absent and the row degrades to a minimal
|
|
15
|
-
* name + description layout.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import type { Resource, ProjectRecord } from "@grove-dev/core";
|
|
19
|
-
import { getOwnerAndRepoFromRepoUrl, getOwnerAvatarUrl } from "../lib/repo";
|
|
20
|
-
import { formatRelative, formatStars } from "../lib/format";
|
|
21
|
-
import { statusDisplay, prettySlug } from "../lib/display";
|
|
22
|
-
|
|
23
|
-
interface Props {
|
|
24
|
-
record: Resource;
|
|
25
|
-
detailHref: string;
|
|
26
|
-
/** Whether to show stack/platform chips. Default true. */
|
|
27
|
-
showChips?: boolean;
|
|
28
|
-
class?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const {
|
|
32
|
-
record,
|
|
33
|
-
detailHref,
|
|
34
|
-
showChips = true,
|
|
35
|
-
class: className = "",
|
|
36
|
-
} = Astro.props;
|
|
37
|
-
|
|
38
|
-
const isProject = record.kind === "project";
|
|
39
|
-
const project = isProject ? (record as ProjectRecord) : null;
|
|
40
|
-
|
|
41
|
-
const name =
|
|
42
|
-
record.kind === "resource"
|
|
43
|
-
? (record as { title: string }).title
|
|
44
|
-
: (record as { name: string }).name;
|
|
45
|
-
const description = record.description ?? "";
|
|
46
|
-
const repoUrl = project?.repoUrl;
|
|
47
|
-
const logoUrl = project?.logoUrl;
|
|
48
|
-
const stacks = project?.stacks ?? (project?.stack ? [project.stack] : []);
|
|
49
|
-
const platforms = project?.platforms ?? [];
|
|
50
|
-
const github = project?.github?.repository;
|
|
51
|
-
const stars = github?.stargazers_count;
|
|
52
|
-
const licenseSpdx = github?.license?.spdx_id;
|
|
53
|
-
const pushedAt = github?.pushed_at;
|
|
54
|
-
const healthStatus = project?.health?.status;
|
|
55
|
-
const githubUrl =
|
|
56
|
-
repoUrl ?? (record.links as { github?: string } | undefined)?.github;
|
|
57
|
-
|
|
58
|
-
const ownerRepo = repoUrl ? getOwnerAndRepoFromRepoUrl(repoUrl) : null;
|
|
59
|
-
const avatarSrc =
|
|
60
|
-
logoUrl ?? (ownerRepo ? getOwnerAvatarUrl(ownerRepo.owner, 80) : null);
|
|
61
|
-
const initials = (name || "")
|
|
62
|
-
.replace(/[^a-z0-9]/gi, "")
|
|
63
|
-
.slice(0, 2)
|
|
64
|
-
.toUpperCase();
|
|
65
|
-
|
|
66
|
-
const allTags = [
|
|
67
|
-
...stacks.slice(0, 2).map((s) => `Stack: ${s}`),
|
|
68
|
-
...platforms.slice(0, 1).map((p) => `Platform: ${p}`),
|
|
69
|
-
];
|
|
70
|
-
const starsLabel = formatStars(stars);
|
|
71
|
-
const updatedLabel = formatRelative(pushedAt);
|
|
72
|
-
const healthLabel = healthStatus ? statusDisplay(healthStatus) : null;
|
|
73
|
-
|
|
74
|
-
const tags = record.tags ?? [];
|
|
75
|
-
const labels = record.curation?.labels ?? [];
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
<article
|
|
79
|
-
class:list={[
|
|
80
|
-
"grove-row grove-card grid grid-cols-1 gap-3 p-4 sm:grid-cols-[auto_1fr_auto] sm:items-center",
|
|
81
|
-
className,
|
|
82
|
-
]}
|
|
83
|
-
data-record-slug={record.slug}
|
|
84
|
-
data-record-name={name}
|
|
85
|
-
data-record-kind={record.kind}
|
|
86
|
-
data-record-category={record.category}
|
|
87
|
-
data-record-stacks={stacks.join(",")}
|
|
88
|
-
data-record-platforms={platforms.join(",")}
|
|
89
|
-
data-record-tags={tags.join(",")}
|
|
90
|
-
data-record-labels={labels.join(",")}
|
|
91
|
-
data-record-search={[name, description, ownerRepo?.owner ?? "", ...stacks, ...platforms, ...tags, licenseSpdx ?? ""].join(" ").toLowerCase()}
|
|
92
|
-
>
|
|
93
|
-
<div class="flex items-center gap-3">
|
|
94
|
-
<div class="flex-shrink-0">
|
|
95
|
-
{avatarSrc ? (
|
|
96
|
-
<img
|
|
97
|
-
src={avatarSrc}
|
|
98
|
-
alt=""
|
|
99
|
-
width="32"
|
|
100
|
-
height="32"
|
|
101
|
-
loading="lazy"
|
|
102
|
-
decoding="async"
|
|
103
|
-
class="h-8 w-8 rounded-md border border-ink-200 object-contain bg-white dark:border-ink-800 dark:bg-ink-950"
|
|
104
|
-
/>
|
|
105
|
-
) : (
|
|
106
|
-
<span
|
|
107
|
-
aria-hidden="true"
|
|
108
|
-
class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-ink-200 bg-ink-50 text-xs font-semibold text-ink-700 dark:border-ink-800 dark:bg-ink-900 dark:text-ink-200"
|
|
109
|
-
>
|
|
110
|
-
{initials || "·"}
|
|
111
|
-
</span>
|
|
112
|
-
)}
|
|
113
|
-
</div>
|
|
114
|
-
<div class="min-w-0">
|
|
115
|
-
<h3 class="m-0 text-[0.9375rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
116
|
-
<a href={detailHref} class="text-inherit no-underline hover:underline">
|
|
117
|
-
{name}
|
|
118
|
-
</a>
|
|
119
|
-
</h3>
|
|
120
|
-
{ownerRepo && (
|
|
121
|
-
<p class="m-0 mt-0.5 text-2xs text-ink-500 dark:text-ink-400">
|
|
122
|
-
@{ownerRepo.owner}/{ownerRepo.repo}
|
|
123
|
-
</p>
|
|
124
|
-
)}
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
<div class="min-w-0">
|
|
129
|
-
{description && (
|
|
130
|
-
<p class="m-0 line-clamp-2 text-[0.8125rem] leading-snug text-ink-500 dark:text-ink-400">
|
|
131
|
-
{description}
|
|
132
|
-
</p>
|
|
133
|
-
)}
|
|
134
|
-
{showChips && allTags.length > 0 && (
|
|
135
|
-
<ul class="mt-1.5 flex list-none flex-wrap gap-1.5 p-0">
|
|
136
|
-
{allTags.map((t) => <li class="grove-badge">{t}</li>)}
|
|
137
|
-
</ul>
|
|
138
|
-
)}
|
|
139
|
-
</div>
|
|
140
|
-
|
|
141
|
-
<dl
|
|
142
|
-
class="grid grid-cols-3 gap-x-4 gap-y-1 text-2xs text-ink-500 dark:text-ink-400 sm:flex sm:flex-col sm:items-end sm:gap-y-0.5"
|
|
143
|
-
aria-label="Project signals"
|
|
144
|
-
>
|
|
145
|
-
{starsLabel && (
|
|
146
|
-
<div class="flex items-center gap-1">
|
|
147
|
-
<dt class="sr-only">Stars</dt>
|
|
148
|
-
<dd class="m-0 font-mono text-ink-700 dark:text-ink-200">{starsLabel} ★</dd>
|
|
149
|
-
</div>
|
|
150
|
-
)}
|
|
151
|
-
{licenseSpdx && (
|
|
152
|
-
<div class="flex items-center gap-1">
|
|
153
|
-
<dt class="sr-only">License</dt>
|
|
154
|
-
<dd class="m-0">{licenseSpdx}</dd>
|
|
155
|
-
</div>
|
|
156
|
-
)}
|
|
157
|
-
{updatedLabel && updatedLabel !== "—" && (
|
|
158
|
-
<div class="flex items-center gap-1">
|
|
159
|
-
<dt class="sr-only">Last updated</dt>
|
|
160
|
-
<dd class="m-0">Updated {updatedLabel}</dd>
|
|
161
|
-
</div>
|
|
162
|
-
)}
|
|
163
|
-
{healthLabel && (
|
|
164
|
-
<div class="flex items-center gap-1">
|
|
165
|
-
<dt class="sr-only">Health</dt>
|
|
166
|
-
<dd class="m-0">{healthLabel}</dd>
|
|
167
|
-
</div>
|
|
168
|
-
)}
|
|
169
|
-
</dl>
|
|
170
|
-
|
|
171
|
-
<div class="col-span-full flex flex-wrap items-center gap-2 sm:col-span-1 sm:justify-end">
|
|
172
|
-
<a
|
|
173
|
-
href={detailHref}
|
|
174
|
-
class="grove-btn grove-btn-outline"
|
|
175
|
-
data-event="row_details_click"
|
|
176
|
-
data-event-source="apps_index_row"
|
|
177
|
-
data-event-app={record.slug}
|
|
178
|
-
>
|
|
179
|
-
View details
|
|
180
|
-
</a>
|
|
181
|
-
{githubUrl && (
|
|
182
|
-
<a
|
|
183
|
-
href={githubUrl}
|
|
184
|
-
target="_blank"
|
|
185
|
-
rel="noopener noreferrer"
|
|
186
|
-
class="grove-btn grove-btn-ghost"
|
|
187
|
-
data-event="row_repo_click"
|
|
188
|
-
data-event-source="apps_index_row"
|
|
189
|
-
data-event-app={record.slug}
|
|
190
|
-
>
|
|
191
|
-
GitHub
|
|
192
|
-
</a>
|
|
193
|
-
)}
|
|
194
|
-
</div>
|
|
195
|
-
</article>
|
|
196
|
-
|
|
197
|
-
<style>
|
|
198
|
-
.line-clamp-2 {
|
|
199
|
-
display: -webkit-box;
|
|
200
|
-
-webkit-line-clamp: 2;
|
|
201
|
-
-webkit-box-orient: vertical;
|
|
202
|
-
overflow: hidden;
|
|
203
|
-
}
|
|
204
|
-
</style>
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* AppsPagination — windowed page list.
|
|
4
|
-
*
|
|
5
|
-
* Renders a row of page links with first / prev / numeric / next /
|
|
6
|
-
* last. The numeric window is a 5-page sliding window centered on
|
|
7
|
-
* the current page (clamped at the ends). Pages with no links
|
|
8
|
-
* (gaps) are rendered as "…" to preserve visual rhythm.
|
|
9
|
-
*
|
|
10
|
-
* Generic: takes a `current` and `total` page count and an
|
|
11
|
-
* `hrefFor` builder. The href builder is the only consumer-side
|
|
12
|
-
* knowledge; no siteConfig import.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
interface Props {
|
|
16
|
-
current: number;
|
|
17
|
-
total: number;
|
|
18
|
-
hrefFor: (page: number) => string;
|
|
19
|
-
class?: string;
|
|
20
|
-
/** Window size for the numeric page list. Default 5. */
|
|
21
|
-
windowSize?: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const {
|
|
25
|
-
current,
|
|
26
|
-
total,
|
|
27
|
-
hrefFor,
|
|
28
|
-
class: className = "",
|
|
29
|
-
windowSize = 5,
|
|
30
|
-
} = Astro.props;
|
|
31
|
-
|
|
32
|
-
// Clamp current into [1, total].
|
|
33
|
-
const safeCurrent = Math.max(1, Math.min(total, current));
|
|
34
|
-
const half = Math.floor(windowSize / 2);
|
|
35
|
-
|
|
36
|
-
// Compute the numeric window. Clamp so it never extends past 1 or
|
|
37
|
-
// total, and never narrower than `windowSize` unless total is
|
|
38
|
-
// smaller.
|
|
39
|
-
function buildWindow(): (number | "…")[] {
|
|
40
|
-
if (total <= windowSize + 2) {
|
|
41
|
-
return Array.from({ length: total }, (_, i) => i + 1);
|
|
42
|
-
}
|
|
43
|
-
let start = Math.max(2, safeCurrent - half);
|
|
44
|
-
let end = Math.min(total - 1, safeCurrent + half);
|
|
45
|
-
// Expand the window toward the nearest edge if it shrinks.
|
|
46
|
-
if (end - start + 1 < windowSize) {
|
|
47
|
-
if (safeCurrent <= half + 1) {
|
|
48
|
-
end = Math.min(total - 1, end + (windowSize - (end - start + 1)));
|
|
49
|
-
} else {
|
|
50
|
-
start = Math.max(2, start - (windowSize - (end - start + 1)));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const items: (number | "…")[] = [1];
|
|
54
|
-
if (start > 2) items.push("…");
|
|
55
|
-
for (let i = start; i <= end; i++) items.push(i);
|
|
56
|
-
if (end < total - 1) items.push("…");
|
|
57
|
-
items.push(total);
|
|
58
|
-
return items;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const pages = buildWindow();
|
|
62
|
-
const hasPrev = safeCurrent > 1;
|
|
63
|
-
const hasNext = safeCurrent < total;
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
<nav
|
|
67
|
-
class:list={[
|
|
68
|
-
"grove-pagination flex flex-wrap items-center gap-1",
|
|
69
|
-
className,
|
|
70
|
-
]}
|
|
71
|
-
aria-label="Pagination"
|
|
72
|
-
>
|
|
73
|
-
{hasPrev && (
|
|
74
|
-
<a
|
|
75
|
-
href={hrefFor(safeCurrent - 1)}
|
|
76
|
-
rel="prev"
|
|
77
|
-
class="grove-btn grove-btn-ghost"
|
|
78
|
-
data-event="pagination_click"
|
|
79
|
-
data-event-source="apps_pagination"
|
|
80
|
-
>
|
|
81
|
-
← Prev
|
|
82
|
-
</a>
|
|
83
|
-
)}
|
|
84
|
-
|
|
85
|
-
<ol class="m-0 flex list-none items-center gap-1 p-0">
|
|
86
|
-
{pages.map((p) =>
|
|
87
|
-
p === "…" ? (
|
|
88
|
-
<li aria-hidden="true" class="px-2 text-2xs text-ink-400">
|
|
89
|
-
…
|
|
90
|
-
</li>
|
|
91
|
-
) : (
|
|
92
|
-
<li>
|
|
93
|
-
{p === safeCurrent ? (
|
|
94
|
-
<span
|
|
95
|
-
aria-current="page"
|
|
96
|
-
class="grove-btn grove-btn-primary pointer-events-none"
|
|
97
|
-
>
|
|
98
|
-
{p}
|
|
99
|
-
</span>
|
|
100
|
-
) : (
|
|
101
|
-
<a
|
|
102
|
-
href={hrefFor(p)}
|
|
103
|
-
class="grove-btn grove-btn-ghost"
|
|
104
|
-
data-event="pagination_click"
|
|
105
|
-
data-event-source="apps_pagination"
|
|
106
|
-
>
|
|
107
|
-
{p}
|
|
108
|
-
</a>
|
|
109
|
-
)}
|
|
110
|
-
</li>
|
|
111
|
-
),
|
|
112
|
-
)}
|
|
113
|
-
</ol>
|
|
114
|
-
|
|
115
|
-
{hasNext && (
|
|
116
|
-
<a
|
|
117
|
-
href={hrefFor(safeCurrent + 1)}
|
|
118
|
-
rel="next"
|
|
119
|
-
class="grove-btn grove-btn-ghost"
|
|
120
|
-
data-event="pagination_click"
|
|
121
|
-
data-event-source="apps_pagination"
|
|
122
|
-
>
|
|
123
|
-
Next →
|
|
124
|
-
</a>
|
|
125
|
-
)}
|
|
126
|
-
</nav>
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
declare module 'astro:content' {
|
|
2
|
-
export interface RenderResult {
|
|
3
|
-
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
|
|
4
|
-
headings: import('astro').MarkdownHeading[];
|
|
5
|
-
remarkPluginFrontmatter: Record<string, any>;
|
|
6
|
-
}
|
|
7
|
-
interface Render {
|
|
8
|
-
'.md': Promise<RenderResult>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface RenderedContent {
|
|
12
|
-
html: string;
|
|
13
|
-
metadata?: {
|
|
14
|
-
imagePaths: Array<string>;
|
|
15
|
-
[key: string]: unknown;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
|
|
20
|
-
|
|
21
|
-
export type CollectionKey = keyof DataEntryMap;
|
|
22
|
-
export type CollectionEntry<C extends CollectionKey> = Flatten<DataEntryMap[C]>;
|
|
23
|
-
|
|
24
|
-
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
|
|
25
|
-
|
|
26
|
-
export type ReferenceDataEntry<
|
|
27
|
-
C extends CollectionKey,
|
|
28
|
-
E extends keyof DataEntryMap[C] = string,
|
|
29
|
-
> = {
|
|
30
|
-
collection: C;
|
|
31
|
-
id: E;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
|
|
35
|
-
collection: C;
|
|
36
|
-
id: string;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export function getCollection<C extends keyof DataEntryMap, E extends CollectionEntry<C>>(
|
|
40
|
-
collection: C,
|
|
41
|
-
filter?: (entry: CollectionEntry<C>) => entry is E,
|
|
42
|
-
): Promise<E[]>;
|
|
43
|
-
export function getCollection<C extends keyof DataEntryMap>(
|
|
44
|
-
collection: C,
|
|
45
|
-
filter?: (entry: CollectionEntry<C>) => unknown,
|
|
46
|
-
): Promise<CollectionEntry<C>[]>;
|
|
47
|
-
|
|
48
|
-
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
|
|
49
|
-
collection: C,
|
|
50
|
-
filter?: LiveLoaderCollectionFilterType<C>,
|
|
51
|
-
): Promise<
|
|
52
|
-
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
|
|
53
|
-
>;
|
|
54
|
-
|
|
55
|
-
export function getEntry<
|
|
56
|
-
C extends keyof DataEntryMap,
|
|
57
|
-
E extends keyof DataEntryMap[C] | (string & {}),
|
|
58
|
-
>(
|
|
59
|
-
entry: ReferenceDataEntry<C, E>,
|
|
60
|
-
): E extends keyof DataEntryMap[C]
|
|
61
|
-
? Promise<DataEntryMap[C][E]>
|
|
62
|
-
: Promise<CollectionEntry<C> | undefined>;
|
|
63
|
-
export function getEntry<
|
|
64
|
-
C extends keyof DataEntryMap,
|
|
65
|
-
E extends keyof DataEntryMap[C] | (string & {}),
|
|
66
|
-
>(
|
|
67
|
-
collection: C,
|
|
68
|
-
id: E,
|
|
69
|
-
): E extends keyof DataEntryMap[C]
|
|
70
|
-
? string extends keyof DataEntryMap[C]
|
|
71
|
-
? Promise<DataEntryMap[C][E]> | undefined
|
|
72
|
-
: Promise<DataEntryMap[C][E]>
|
|
73
|
-
: Promise<CollectionEntry<C> | undefined>;
|
|
74
|
-
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
|
|
75
|
-
collection: C,
|
|
76
|
-
filter: string | LiveLoaderEntryFilterType<C>,
|
|
77
|
-
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
|
|
78
|
-
|
|
79
|
-
/** Resolve an array of entry references from the same collection */
|
|
80
|
-
export function getEntries<C extends keyof DataEntryMap>(
|
|
81
|
-
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
|
|
82
|
-
): Promise<CollectionEntry<C>[]>;
|
|
83
|
-
|
|
84
|
-
export function render<C extends keyof DataEntryMap>(
|
|
85
|
-
entry: DataEntryMap[C][string],
|
|
86
|
-
): Promise<RenderResult>;
|
|
87
|
-
|
|
88
|
-
export function reference<
|
|
89
|
-
C extends
|
|
90
|
-
| keyof DataEntryMap
|
|
91
|
-
// Allow generic `string` to avoid excessive type errors in the config
|
|
92
|
-
// if `dev` is not running to update as you edit.
|
|
93
|
-
// Invalid collection names will be caught at build time.
|
|
94
|
-
| (string & {}),
|
|
95
|
-
>(
|
|
96
|
-
collection: C,
|
|
97
|
-
): import('astro/zod').ZodPipe<
|
|
98
|
-
import('astro/zod').ZodString,
|
|
99
|
-
import('astro/zod').ZodTransform<
|
|
100
|
-
C extends keyof DataEntryMap
|
|
101
|
-
? {
|
|
102
|
-
collection: C;
|
|
103
|
-
id: string;
|
|
104
|
-
}
|
|
105
|
-
: never,
|
|
106
|
-
string
|
|
107
|
-
>
|
|
108
|
-
>;
|
|
109
|
-
|
|
110
|
-
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
|
|
111
|
-
type InferEntrySchema<C extends keyof DataEntryMap> = import('astro/zod').infer<
|
|
112
|
-
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
|
|
113
|
-
>;
|
|
114
|
-
type ExtractLoaderConfig<T> = T extends { loader: infer L } ? L : never;
|
|
115
|
-
type InferLoaderSchema<
|
|
116
|
-
C extends keyof DataEntryMap,
|
|
117
|
-
L = ExtractLoaderConfig<ContentConfig['collections'][C]>,
|
|
118
|
-
> = L extends { schema: import('astro/zod').ZodSchema }
|
|
119
|
-
? import('astro/zod').infer<L['schema']>
|
|
120
|
-
: any;
|
|
121
|
-
|
|
122
|
-
type DataEntryMap = {
|
|
123
|
-
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
|
|
127
|
-
infer TData,
|
|
128
|
-
infer TEntryFilter,
|
|
129
|
-
infer TCollectionFilter,
|
|
130
|
-
infer TError
|
|
131
|
-
>
|
|
132
|
-
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
|
|
133
|
-
: { data: never; entryFilter: never; collectionFilter: never; error: never };
|
|
134
|
-
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
|
|
135
|
-
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
|
|
136
|
-
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
|
|
137
|
-
type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
|
|
138
|
-
|
|
139
|
-
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
|
|
140
|
-
LiveContentConfig['collections'][C]['schema'] extends undefined
|
|
141
|
-
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
|
|
142
|
-
: import('astro/zod').infer<
|
|
143
|
-
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
|
|
144
|
-
>;
|
|
145
|
-
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
|
|
146
|
-
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
|
|
147
|
-
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
|
|
148
|
-
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
|
|
149
|
-
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
|
|
150
|
-
LiveContentConfig['collections'][C]['loader']
|
|
151
|
-
>;
|
|
152
|
-
|
|
153
|
-
export type ContentConfig = never;
|
|
154
|
-
export type LiveContentConfig = never;
|
|
155
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/// <reference types="astro/client" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"schemaVersion":1,"generatedAt":"2026-01-01T00:00:00Z","totalApps":0,"visibleApps":0,"apps":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"originalRepo":"https://github.com/example/repo"}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"blueprint": "project-directory",
|
|
3
|
-
"name": "Grove Directory",
|
|
4
|
-
"tagline": "A curated, health-aware developer directory.",
|
|
5
|
-
"description": "A curated, health-aware developer directory powered by Grove.",
|
|
6
|
-
"siteUrl": "https://example.com",
|
|
7
|
-
"repoUrl": "https://github.com/tortuvshin/grove",
|
|
8
|
-
"nav": [
|
|
9
|
-
{
|
|
10
|
-
"label": "Home",
|
|
11
|
-
"href": "/"
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"label": "Browse",
|
|
15
|
-
"href": "/projects"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"label": "About",
|
|
19
|
-
"href": "/about"
|
|
20
|
-
}
|
|
21
|
-
],
|
|
22
|
-
"theme": {
|
|
23
|
-
"primaryColor": "#16a34a",
|
|
24
|
-
"radius": "soft",
|
|
25
|
-
"density": "comfortable",
|
|
26
|
-
"containerWidth": "72rem"
|
|
27
|
-
},
|
|
28
|
-
"integrations": {
|
|
29
|
-
"github": false
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@theme{ --color-ink-50: hsl(224, 19%, 97%); --color-ink-100: hsl(224, 20%, 94%); --color-ink-200: hsl(224, 6%, 77%); --color-ink-300: hsl(224, 6%, 56%); --color-ink-400: hsl(224, 6%, 56%); --color-ink-500: hsl(224, 7%, 36%); --color-ink-600: hsl(224, 10%, 23%); --color-ink-700: hsl(224, 10%, 23%); --color-ink-800: hsl(224, 14%, 16%); --color-ink-900: hsl(224, 10%, 10%); --color-ink-950: hsl(224, 10%, 10%); --color-accent: hsl(234, 90%, 60%); --color-accent-muted: hsl(234, 88%, 90%); --color-emerald-50: hsl(101, 85%, 90%); --color-emerald-300: hsl(101, 82%, 63%); --color-emerald-400: hsl(101, 90%, 46%); --color-emerald-700: hsl(101, 80%, 22%); --color-emerald-800: hsl(101, 80%, 22%); --color-emerald-950: hsl(101, 39%, 22%); --color-orange-50: hsl(41, 90%, 88%); --color-orange-300: hsl(41, 82%, 63%); --color-orange-400: hsl(41, 90%, 60%); --color-orange-700: hsl(41, 80%, 25%); --color-orange-800: hsl(41, 80%, 25%); --color-orange-950: hsl(41, 39%, 22%); --color-blue-50: hsl(234, 88%, 90%); --color-blue-300: hsl(234, 100%, 87%); --color-blue-400: hsl(234, 90%, 60%); --color-blue-700: hsl(234, 80%, 30%); --color-blue-800: hsl(234, 80%, 30%); --color-blue-950: hsl(234, 54%, 20%); --color-amber-50: hsl(41, 90%, 88%); --color-amber-300: hsl(41, 82%, 63%); --color-amber-400: hsl(41, 90%, 60%); --color-amber-700: hsl(41, 80%, 25%); --color-amber-800: hsl(41, 80%, 25%); --color-amber-950: hsl(41, 39%, 22%); --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --text-2xs: .6875rem; --text-2xs--line-height: 1rem; --container-container: 1140px; --container-wide: 1400px; --container-narrow: 720px; --radius: .375rem; --radius-lg: .5rem; --radius-xl: .75rem; --animate-fade-in-up: fade-in-up .6s ease-out both; --animate-fade-in: fade-in .8s ease-out both; --animate-gradient-shift: gradient-shift 8s ease-in-out infinite; --animate-subtle-bob: subtle-bob 3s ease-in-out infinite; }@keyframes fade-in-up{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes gradient-shift{0%,to{background-position:0% 50%}50%{background-position:100% 50%}}@keyframes subtle-bob{0%,to{transform:translateY(0)}50%{transform:translateY(-2px)}}@layer base{:root{--color-ink-50: hsl(224, 19%, 97%);--color-ink-100: hsl(224, 20%, 94%);--color-ink-200: hsl(224, 6%, 77%);--color-ink-300: hsl(224, 6%, 56%);--color-ink-400: hsl(224, 6%, 56%);--color-ink-500: hsl(224, 7%, 36%);--color-ink-600: hsl(224, 10%, 23%);--color-ink-700: hsl(224, 10%, 23%);--color-ink-800: hsl(224, 14%, 16%);--color-ink-900: hsl(224, 10%, 10%);--color-ink-950: hsl(224, 10%, 10%);--color-accent: hsl(234, 90%, 60%);--color-accent-muted: hsl(234, 88%, 90%);--color-emerald-50: hsl(101, 85%, 90%);--color-emerald-300: hsl(101, 82%, 63%);--color-emerald-400: hsl(101, 90%, 46%);--color-emerald-700: hsl(101, 80%, 22%);--color-emerald-800: hsl(101, 80%, 22%);--color-emerald-950: hsl(101, 39%, 22%);--color-orange-50: hsl(41, 90%, 88%);--color-orange-300: hsl(41, 82%, 63%);--color-orange-400: hsl(41, 90%, 60%);--color-orange-700: hsl(41, 80%, 25%);--color-orange-800: hsl(41, 80%, 25%);--color-orange-950: hsl(41, 39%, 22%);--color-blue-50: hsl(234, 88%, 90%);--color-blue-300: hsl(234, 100%, 87%);--color-blue-400: hsl(234, 90%, 60%);--color-blue-700: hsl(234, 80%, 30%);--color-blue-800: hsl(234, 80%, 30%);--color-blue-950: hsl(234, 54%, 20%);--color-amber-50: hsl(41, 90%, 88%);--color-amber-300: hsl(41, 82%, 63%);--color-amber-400: hsl(41, 90%, 60%);--color-amber-700: hsl(41, 80%, 25%);--color-amber-800: hsl(41, 80%, 25%);--color-amber-950: hsl(41, 39%, 22%);--font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--text-2xs: .6875rem;--text-2xs--line-height: 1rem;--radius: .375rem;--radius-lg: .5rem;--radius-xl: .75rem;--sl-color-white: hsl(0, 0%, 100%);--sl-color-gray-1: hsl(224, 20%, 94%);--sl-color-gray-2: hsl(224, 6%, 77%);--sl-color-gray-3: hsl(224, 6%, 56%);--sl-color-gray-4: hsl(224, 7%, 36%);--sl-color-gray-5: hsl(224, 10%, 23%);--sl-color-gray-6: hsl(224, 14%, 16%);--sl-color-black: hsl(224, 10%, 10%);--sl-hue-orange: 41;--sl-hue-green: 101;--sl-hue-blue: 234;--sl-hue-purple: 281;--sl-hue-red: 339;--sl-color-orange-low: hsl(var(--sl-hue-orange), 39%, 22%);--sl-color-orange: hsl(var(--sl-hue-orange), 82%, 63%);--sl-color-orange-high: hsl(var(--sl-hue-orange), 82%, 87%);--sl-color-green-low: hsl(var(--sl-hue-green), 39%, 22%);--sl-color-green: hsl(var(--sl-hue-green), 82%, 63%);--sl-color-green-high: hsl(var(--sl-hue-green), 82%, 80%);--sl-color-blue-low: hsl(var(--sl-hue-blue), 54%, 20%);--sl-color-blue: hsl(var(--sl-hue-blue), 100%, 60%);--sl-color-blue-high: hsl(var(--sl-hue-blue), 100%, 87%);--sl-color-purple-low: hsl(var(--sl-hue-purple), 39%, 22%);--sl-color-purple: hsl(var(--sl-hue-purple), 82%, 63%);--sl-color-purple-high: hsl(var(--sl-hue-purple), 82%, 89%);--sl-color-red-low: hsl(var(--sl-hue-red), 39%, 22%);--sl-color-red: hsl(var(--sl-hue-red), 82%, 63%);--sl-color-red-high: hsl(var(--sl-hue-red), 82%, 87%);--sl-color-accent-low: hsl(224, 54%, 20%);--sl-color-accent: hsl(224, 100%, 60%);--sl-color-accent-high: hsl(224, 100%, 85%);--sl-color-text: var(--sl-color-gray-2);--sl-color-bg: var(--sl-color-black);--sl-color-bg-inline-code: var(--sl-color-gray-5);--sl-color-hairline: var(--sl-color-gray-6);--sl-font-system: var(--font-sans);--sl-font-system-mono: var(--font-mono);color-scheme:light dark}:root:not(.dark){--sl-color-white: hsl(224, 10%, 10%);--sl-color-gray-1: hsl(224, 14%, 16%);--sl-color-gray-2: hsl(224, 10%, 23%);--sl-color-gray-3: hsl(224, 7%, 36%);--sl-color-gray-4: hsl(224, 6%, 56%);--sl-color-gray-5: hsl(224, 6%, 77%);--sl-color-gray-6: hsl(224, 20%, 94%);--sl-color-gray-7: hsl(224, 19%, 97%);--sl-color-black: hsl(0, 0%, 100%);--sl-color-orange-high: hsl(var(--sl-hue-orange), 80%, 25%);--sl-color-orange: hsl(var(--sl-hue-orange), 90%, 60%);--sl-color-orange-low: hsl(var(--sl-hue-orange), 90%, 88%);--sl-color-green-high: hsl(var(--sl-hue-green), 80%, 22%);--sl-color-green: hsl(var(--sl-hue-green), 90%, 46%);--sl-color-green-low: hsl(var(--sl-hue-green), 85%, 90%);--sl-color-blue-high: hsl(var(--sl-hue-blue), 80%, 30%);--sl-color-blue: hsl(var(--sl-hue-blue), 90%, 60%);--sl-color-blue-low: hsl(var(--sl-hue-blue), 88%, 90%);--sl-color-purple-high: hsl(var(--sl-hue-purple), 90%, 30%);--sl-color-purple: hsl(var(--sl-hue-purple), 90%, 60%);--sl-color-purple-low: hsl(var(--sl-hue-purple), 80%, 90%);--sl-color-red-high: hsl(var(--sl-hue-red), 80%, 30%);--sl-color-red: hsl(var(--sl-hue-red), 90%, 60%);--sl-color-red-low: hsl(var(--sl-hue-red), 80%, 90%);--sl-color-accent-high: hsl(234, 80%, 30%);--sl-color-accent: hsl(234, 90%, 60%);--sl-color-accent-low: hsl(234, 88%, 90%);--sl-color-text: var(--sl-color-gray-2);--sl-color-bg: var(--sl-color-black);--sl-color-bg-inline-code: var(--sl-color-gray-6);--sl-color-hairline: var(--sl-color-gray-6)}html{-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;background:var(--sl-color-bg);color:var(--sl-color-text);font-family:var(--sl-font-system);font-size:15px;line-height:1.75}.dark body{background:var(--sl-color-bg);color:var(--sl-color-text)}a{color:inherit;text-decoration:none}::selection{background:var(--color-ink-200);color:var(--color-ink-900)}.dark ::selection{background:var(--color-ink-800);color:var(--color-ink-100)}*{scrollbar-width:thin;scrollbar-color:var(--color-ink-300) transparent}*::-webkit-scrollbar{width:8px;height:8px}*::-webkit-scrollbar-thumb{background:var(--color-ink-200);border-radius:4px}.dark *::-webkit-scrollbar-thumb{background:var(--color-ink-800)}input,select,textarea,button{font:inherit}}@layer components{.grove-section{padding-top:3rem;padding-bottom:3rem}@media(min-width:640px){.grove-section{padding-top:4rem;padding-bottom:4rem}}.grove-section-tight{padding-top:2.5rem;padding-bottom:2.5rem}@media(min-width:640px){.grove-section-tight{padding-top:3rem;padding-bottom:3rem}}.grove-card{border:1px solid var(--color-ink-200);background:var(--sl-color-bg);border-radius:var(--radius-lg);transition:border-color .15s ease,box-shadow .15s ease,background-color .15s ease}.dark .grove-card{border-color:var(--sl-color-hairline);background:var(--sl-color-bg)}.grove-card-hover:hover{border-color:var(--color-ink-300);background:#f4f4f599;box-shadow:0 1px 2px #0000000a,0 4px 12px -2px #0000000f}.dark .grove-card-hover:hover{border-color:var(--color-ink-700);background:#18181b66;box-shadow:0 1px 2px #0006,0 4px 12px -2px #0000004d}.grove-badge{display:inline-flex;align-items:center;gap:.25rem;border-radius:.25rem;border:1px solid var(--color-ink-200);background:var(--color-ink-50);padding:.125rem .375rem;font-size:var(--text-2xs);line-height:var(--text-2xs--line-height);font-weight:500;color:var(--color-ink-700)}.dark .grove-badge{border-color:var(--color-ink-800);background:#18181b99;color:var(--color-ink-300)}.grove-badge-accent{border-color:var(--color-ink-900);background:var(--color-ink-900);color:#fff}.dark .grove-badge-accent{border-color:var(--color-ink-100);background:var(--color-ink-100);color:var(--color-ink-900)}.grove-badge-new{border-color:#6ee7b799;background:var(--color-emerald-50);color:var(--color-emerald-800)}.dark .grove-badge-new{border-color:#065f4699;background:#022c2266;color:var(--color-emerald-300)}.grove-badge-hot{border-color:#fdba7499;background:var(--color-orange-50);color:var(--color-orange-800)}.dark .grove-badge-hot{border-color:#9a341299;background:#43140766;color:var(--color-orange-300)}.grove-badge-mature{border-color:#93c5fd99;background:var(--color-blue-50);color:var(--color-blue-800)}.dark .grove-badge-mature{border-color:#1e40af99;background:#17255466;color:var(--color-blue-300)}.grove-badge-stale{border-color:#fcd34d99;background:var(--color-amber-50);color:var(--color-amber-800)}.dark .grove-badge-stale{border-color:#92400e99;background:#451a0366;color:var(--color-amber-300)}.grove-btn{display:inline-flex;align-items:center;justify-content:center;gap:.375rem;border-radius:var(--radius);border:1px solid transparent;padding:.375rem .75rem;font-size:.875rem;line-height:1.25rem;font-weight:500;transition:color .15s ease,background-color .15s ease,border-color .15s ease;text-decoration:none;cursor:pointer}.grove-btn-ghost{color:var(--color-ink-700)}.grove-btn-ghost:hover{background:var(--color-ink-100)}.dark .grove-btn-ghost{color:var(--color-ink-300)}.dark .grove-btn-ghost:hover{background:var(--color-ink-900)}.grove-btn-outline{border-color:var(--color-ink-200);background:var(--sl-color-bg);color:var(--color-ink-900)}.grove-btn-outline:hover{border-color:var(--color-ink-300);background:var(--color-ink-50)}.dark .grove-btn-outline{border-color:var(--color-ink-800);background:var(--color-ink-950);color:var(--color-ink-100)}.dark .grove-btn-outline:hover{border-color:var(--color-ink-700);background:#18181b66}.grove-btn-primary{border-color:var(--color-ink-900);background:var(--color-ink-900);color:#fff}.grove-btn-primary:hover{background:var(--color-ink-800)}.dark .grove-btn-primary{border-color:var(--color-ink-100);background:var(--color-ink-100);color:var(--color-ink-900)}.dark .grove-btn-primary:hover{background:#fff}}
|