@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.
- 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,197 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* RefinePanel — multi-select facet dropdowns.
|
|
4
|
+
*
|
|
5
|
+
* Renders a row of facet dropdowns (Stack, Platform, Category,
|
|
6
|
+
* License) plus a Sort dropdown, all using native <select> for
|
|
7
|
+
* server-render-only behavior. Selection auto-submits the form
|
|
8
|
+
* to the same list page with the chosen values merged into the URL.
|
|
9
|
+
*
|
|
10
|
+
* The form is a GET to the list page base, with each facet as a
|
|
11
|
+
* repeated key. Empty values are dropped, so the URL stays clean.
|
|
12
|
+
*
|
|
13
|
+
* Generic: takes a `facets` map of `{ value, label, count }[]` per
|
|
14
|
+
* dimension and an `initial` `AppsFilters` for preselecting the
|
|
15
|
+
* current URL state.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { AppsFilters } from "../lib/search";
|
|
19
|
+
import { SORT_OPTIONS } from "../lib/search";
|
|
20
|
+
|
|
21
|
+
interface FacetValue {
|
|
22
|
+
value: string;
|
|
23
|
+
label?: string;
|
|
24
|
+
count?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Facets {
|
|
28
|
+
stacks?: FacetValue[];
|
|
29
|
+
platforms?: FacetValue[];
|
|
30
|
+
categories?: FacetValue[];
|
|
31
|
+
labels?: FacetValue[];
|
|
32
|
+
licenses?: FacetValue[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Props {
|
|
36
|
+
facets: Facets;
|
|
37
|
+
initial: AppsFilters;
|
|
38
|
+
pathPrefix?: string;
|
|
39
|
+
class?: string;
|
|
40
|
+
/** Hide specific facets. Default: all shown. */
|
|
41
|
+
hide?: Array<"stack" | "platform" | "category" | "license" | "sort">;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
facets,
|
|
46
|
+
initial,
|
|
47
|
+
pathPrefix = "/",
|
|
48
|
+
class: className = "",
|
|
49
|
+
hide = [],
|
|
50
|
+
} = Astro.props;
|
|
51
|
+
|
|
52
|
+
const baseAction = pathPrefix.replace(/\/$/, "") || "/";
|
|
53
|
+
|
|
54
|
+
const initialStacks = new Set(initial.stacks ?? []);
|
|
55
|
+
const initialPlatforms = new Set(initial.platforms ?? []);
|
|
56
|
+
const initialCategories = new Set(initial.categories ?? []);
|
|
57
|
+
const initialLicenses = new Set(initial.licenses ?? []);
|
|
58
|
+
const initialSort = initial.sort ?? "";
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
<form
|
|
62
|
+
method="get"
|
|
63
|
+
action={baseAction}
|
|
64
|
+
class:list={[
|
|
65
|
+
"grove-refine-panel flex flex-wrap items-end gap-3",
|
|
66
|
+
className,
|
|
67
|
+
]}
|
|
68
|
+
data-grove-refine-panel
|
|
69
|
+
>
|
|
70
|
+
{initial.q && <input type="hidden" name="q" value={initial.q} />}
|
|
71
|
+
{initial.lens && <input type="hidden" name="lens" value={initial.lens} />}
|
|
72
|
+
|
|
73
|
+
{!hide.includes("stack") && (facets.stacks?.length ?? 0) > 0 && (
|
|
74
|
+
<label class="flex flex-col gap-1">
|
|
75
|
+
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
76
|
+
Stack
|
|
77
|
+
</span>
|
|
78
|
+
<select
|
|
79
|
+
name="stack"
|
|
80
|
+
multiple
|
|
81
|
+
size="1"
|
|
82
|
+
onchange="this.form?.requestSubmit()"
|
|
83
|
+
class="grove-select min-w-[10rem] rounded-md border border-ink-200 bg-white py-1.5 pl-2 pr-7 text-sm 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"
|
|
84
|
+
>
|
|
85
|
+
{facets.stacks!.map((f) => (
|
|
86
|
+
<option
|
|
87
|
+
value={f.value}
|
|
88
|
+
selected={initialStacks.has(f.value)}
|
|
89
|
+
>
|
|
90
|
+
{f.label ?? f.value}
|
|
91
|
+
{typeof f.count === "number" ? ` (${f.count})` : ""}
|
|
92
|
+
</option>
|
|
93
|
+
))}
|
|
94
|
+
</select>
|
|
95
|
+
</label>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{!hide.includes("platform") && (facets.platforms?.length ?? 0) > 0 && (
|
|
99
|
+
<label class="flex flex-col gap-1">
|
|
100
|
+
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
101
|
+
Platform
|
|
102
|
+
</span>
|
|
103
|
+
<select
|
|
104
|
+
name="platform"
|
|
105
|
+
multiple
|
|
106
|
+
size="1"
|
|
107
|
+
onchange="this.form?.requestSubmit()"
|
|
108
|
+
class="grove-select min-w-[10rem] rounded-md border border-ink-200 bg-white py-1.5 pl-2 pr-7 text-sm 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"
|
|
109
|
+
>
|
|
110
|
+
{facets.platforms!.map((f) => (
|
|
111
|
+
<option
|
|
112
|
+
value={f.value}
|
|
113
|
+
selected={initialPlatforms.has(f.value)}
|
|
114
|
+
>
|
|
115
|
+
{f.label ?? f.value}
|
|
116
|
+
{typeof f.count === "number" ? ` (${f.count})` : ""}
|
|
117
|
+
</option>
|
|
118
|
+
))}
|
|
119
|
+
</select>
|
|
120
|
+
</label>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
{!hide.includes("category") && (facets.categories?.length ?? 0) > 0 && (
|
|
124
|
+
<label class="flex flex-col gap-1">
|
|
125
|
+
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
126
|
+
Category
|
|
127
|
+
</span>
|
|
128
|
+
<select
|
|
129
|
+
name="category"
|
|
130
|
+
multiple
|
|
131
|
+
size="1"
|
|
132
|
+
onchange="this.form?.requestSubmit()"
|
|
133
|
+
class="grove-select min-w-[10rem] rounded-md border border-ink-200 bg-white py-1.5 pl-2 pr-7 text-sm 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"
|
|
134
|
+
>
|
|
135
|
+
{facets.categories!.map((f) => (
|
|
136
|
+
<option
|
|
137
|
+
value={f.value}
|
|
138
|
+
selected={initialCategories.has(f.value)}
|
|
139
|
+
>
|
|
140
|
+
{f.label ?? f.value}
|
|
141
|
+
{typeof f.count === "number" ? ` (${f.count})` : ""}
|
|
142
|
+
</option>
|
|
143
|
+
))}
|
|
144
|
+
</select>
|
|
145
|
+
</label>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{!hide.includes("license") && (facets.licenses?.length ?? 0) > 0 && (
|
|
149
|
+
<label class="flex flex-col gap-1">
|
|
150
|
+
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
151
|
+
License
|
|
152
|
+
</span>
|
|
153
|
+
<select
|
|
154
|
+
name="license"
|
|
155
|
+
multiple
|
|
156
|
+
size="1"
|
|
157
|
+
onchange="this.form?.requestSubmit()"
|
|
158
|
+
class="grove-select min-w-[10rem] rounded-md border border-ink-200 bg-white py-1.5 pl-2 pr-7 text-sm 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"
|
|
159
|
+
>
|
|
160
|
+
{facets.licenses!.map((f) => (
|
|
161
|
+
<option
|
|
162
|
+
value={f.value}
|
|
163
|
+
selected={initialLicenses.has(f.value)}
|
|
164
|
+
>
|
|
165
|
+
{f.label ?? f.value}
|
|
166
|
+
{typeof f.count === "number" ? ` (${f.count})` : ""}
|
|
167
|
+
</option>
|
|
168
|
+
))}
|
|
169
|
+
</select>
|
|
170
|
+
</label>
|
|
171
|
+
)}
|
|
172
|
+
|
|
173
|
+
{!hide.includes("sort") && (
|
|
174
|
+
<label class="flex flex-col gap-1">
|
|
175
|
+
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
176
|
+
Sort
|
|
177
|
+
</span>
|
|
178
|
+
<select
|
|
179
|
+
name="sort"
|
|
180
|
+
onchange="this.form?.requestSubmit()"
|
|
181
|
+
class="grove-select min-w-[12rem] rounded-md border border-ink-200 bg-white py-1.5 pl-2 pr-7 text-sm 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"
|
|
182
|
+
>
|
|
183
|
+
{SORT_OPTIONS.map((o) => (
|
|
184
|
+
<option value={o.value} selected={initialSort === o.value}>
|
|
185
|
+
{o.label}
|
|
186
|
+
</option>
|
|
187
|
+
))}
|
|
188
|
+
</select>
|
|
189
|
+
</label>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
<noscript>
|
|
193
|
+
<button type="submit" class="grove-btn grove-btn-outline">
|
|
194
|
+
Apply
|
|
195
|
+
</button>
|
|
196
|
+
</noscript>
|
|
197
|
+
</form>
|
|
@@ -1,32 +1,102 @@
|
|
|
1
1
|
---
|
|
2
|
+
/**
|
|
3
|
+
* ScoreBars — 4-bar score visualization.
|
|
4
|
+
*
|
|
5
|
+
* Renders a horizontal 4-cell bar per score dimension. Each cell
|
|
6
|
+
* fills based on the score tier (0..4) computed from a 0-100 value.
|
|
7
|
+
* The 5 dimensions are: activity, maturity, learning, contribution,
|
|
8
|
+
* docs. The "overall" bar is optional and rendered last as a
|
|
9
|
+
* composite when `showOverall` is true.
|
|
10
|
+
*
|
|
11
|
+
* Renders nothing when the record has no score block.
|
|
12
|
+
*/
|
|
13
|
+
|
|
2
14
|
import type { Score } from "@grove-dev/core";
|
|
3
|
-
import {
|
|
15
|
+
import { scoreTier, scoreTierLabel, SCORE_LABELS } from "../lib/scores";
|
|
4
16
|
|
|
5
17
|
interface Props {
|
|
6
|
-
scores
|
|
18
|
+
scores: Score | undefined | null;
|
|
19
|
+
/** Show the "overall" composite bar. Default true. */
|
|
7
20
|
showOverall?: boolean;
|
|
21
|
+
/** Compact mode — no labels next to the bars. */
|
|
8
22
|
compact?: boolean;
|
|
23
|
+
class?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
scores,
|
|
28
|
+
showOverall = true,
|
|
29
|
+
compact = false,
|
|
30
|
+
class: className = "",
|
|
31
|
+
} = Astro.props;
|
|
32
|
+
|
|
33
|
+
const dimensions: (keyof Score)[] = showOverall
|
|
34
|
+
? ["activity", "maturity", "learning", "contribution", "docs", "overall"]
|
|
35
|
+
: ["activity", "maturity", "learning", "contribution", "docs"];
|
|
36
|
+
|
|
37
|
+
function dim(n: number | undefined): number {
|
|
38
|
+
if (typeof n !== "number") return 0;
|
|
39
|
+
return scoreTier(n);
|
|
9
40
|
}
|
|
10
41
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.
|
|
14
|
-
|
|
42
|
+
function label(n: number | undefined): string {
|
|
43
|
+
if (typeof n !== "number") return "—";
|
|
44
|
+
return Math.round(n).toString();
|
|
45
|
+
}
|
|
15
46
|
---
|
|
16
47
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
{scores && (
|
|
49
|
+
<div
|
|
50
|
+
class:list={["grove-score-bars flex flex-col gap-2", className]}
|
|
51
|
+
aria-label="Project scores"
|
|
52
|
+
>
|
|
53
|
+
{dimensions.map((d) => {
|
|
54
|
+
const n = scores[d];
|
|
55
|
+
const tier = dim(n);
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
class:list={[
|
|
59
|
+
"grove-score-row flex items-center gap-2",
|
|
60
|
+
compact ? "text-2xs" : "text-2xs",
|
|
61
|
+
]}
|
|
62
|
+
title={`${SCORE_LABELS[d]}: ${label(n)} (${scoreTierLabel(typeof n === "number" ? n : 0)})`}
|
|
63
|
+
>
|
|
64
|
+
{!compact && (
|
|
65
|
+
<span class="w-20 flex-shrink-0 font-medium text-ink-700 dark:text-ink-300">
|
|
66
|
+
{SCORE_LABELS[d]}
|
|
67
|
+
</span>
|
|
68
|
+
)}
|
|
69
|
+
<span
|
|
70
|
+
class:list={[
|
|
71
|
+
"grove-score-cells flex gap-0.5",
|
|
72
|
+
compact ? "w-20" : "w-24",
|
|
73
|
+
]}
|
|
74
|
+
role="img"
|
|
75
|
+
aria-label={`${SCORE_LABELS[d]}: ${label(n)}`}
|
|
76
|
+
>
|
|
77
|
+
{[0, 1, 2, 3, 4].map((i) => (
|
|
78
|
+
<span
|
|
79
|
+
class:list={[
|
|
80
|
+
"grove-score-cell h-1.5 flex-1 rounded-sm",
|
|
81
|
+
i < tier
|
|
82
|
+
? d === "overall"
|
|
83
|
+
? "bg-ink-900 dark:bg-ink-100"
|
|
84
|
+
: "bg-ink-700 dark:bg-ink-300"
|
|
85
|
+
: "bg-ink-100 dark:bg-ink-900",
|
|
86
|
+
]}
|
|
87
|
+
></span>
|
|
88
|
+
))}
|
|
89
|
+
</span>
|
|
90
|
+
<span
|
|
91
|
+
class:list={[
|
|
92
|
+
"font-mono text-ink-500 dark:text-ink-400",
|
|
93
|
+
compact ? "ml-1" : "ml-2 w-7 text-right",
|
|
94
|
+
]}
|
|
95
|
+
>
|
|
96
|
+
{label(n)}
|
|
97
|
+
</span>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
})}
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* SmartLensTabs — curated single-select lens tabs.
|
|
4
|
+
*
|
|
5
|
+
* Renders the primary lenses (All / Trending / Recently added /
|
|
6
|
+
* Established / Featured / Needs review) as a horizontal tab bar.
|
|
7
|
+
* Clicking a tab navigates to the same list page with the `lens=`
|
|
8
|
+
* query param updated. Other filter params (q, stack, platform,
|
|
9
|
+
* category, sort) are preserved.
|
|
10
|
+
*
|
|
11
|
+
* The current selection is computed from `currentParams`, so the
|
|
12
|
+
* component is server-render-only — no client JS required.
|
|
13
|
+
*
|
|
14
|
+
* Generic: uses the `LENSES` registry from `lib/lenses.ts` and
|
|
15
|
+
* takes the list-page base via `pathPrefix`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { LENSES, hrefForLens, isLensActive } from "../lib/lenses";
|
|
19
|
+
import { lensDisplay } from "../lib/display";
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
currentParams: URLSearchParams;
|
|
23
|
+
pathPrefix?: string;
|
|
24
|
+
class?: string;
|
|
25
|
+
/** Override the visible lens list (defaults to LENSES). */
|
|
26
|
+
lensIds?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
currentParams,
|
|
31
|
+
pathPrefix = "/",
|
|
32
|
+
class: className = "",
|
|
33
|
+
lensIds,
|
|
34
|
+
} = Astro.props;
|
|
35
|
+
|
|
36
|
+
// If `lensIds` is provided, filter LENSES to those ids in that order.
|
|
37
|
+
const lenses =
|
|
38
|
+
lensIds && lensIds.length > 0
|
|
39
|
+
? lensIds
|
|
40
|
+
.map((id) => LENSES.find((l) => l.id === id))
|
|
41
|
+
.filter((l): l is NonNullable<typeof l> => Boolean(l))
|
|
42
|
+
: LENSES;
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
<nav
|
|
46
|
+
class:list={[
|
|
47
|
+
"grove-lens-tabs flex flex-wrap items-center gap-1.5",
|
|
48
|
+
className,
|
|
49
|
+
]}
|
|
50
|
+
aria-label="Curated views"
|
|
51
|
+
>
|
|
52
|
+
{lenses.map((lens) => {
|
|
53
|
+
const active = isLensActive(lens.id, currentParams);
|
|
54
|
+
return (
|
|
55
|
+
<a
|
|
56
|
+
href={hrefForLens(lens.id, currentParams, pathPrefix)}
|
|
57
|
+
class:list={[
|
|
58
|
+
"grove-btn rounded-md px-3 py-1.5 text-2xs font-medium transition-colors",
|
|
59
|
+
active
|
|
60
|
+
? "grove-btn-primary"
|
|
61
|
+
: "grove-btn-ghost border border-ink-200 dark:border-ink-800",
|
|
62
|
+
]}
|
|
63
|
+
aria-current={active ? "page" : undefined}
|
|
64
|
+
data-event="lens_tab_click"
|
|
65
|
+
data-event-source="smart_lens_tabs"
|
|
66
|
+
>
|
|
67
|
+
{lensDisplay(lens.id)}
|
|
68
|
+
</a>
|
|
69
|
+
);
|
|
70
|
+
})}
|
|
71
|
+
</nav>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* StackGrid — browse-by-stack grid for the home page.
|
|
4
|
+
*
|
|
5
|
+
* Renders a card per stack with name, blurb, status pill
|
|
6
|
+
* (live/expanding/planned), and a count. The href is the list
|
|
7
|
+
* page filtered to that stack, so consumers compose the URL via
|
|
8
|
+
* the `hrefFor` prop.
|
|
9
|
+
*
|
|
10
|
+
* Generic: no blueprint-specific terms. A "stack" here is just a
|
|
11
|
+
* slug + display name + status; consumers derive both from their
|
|
12
|
+
* own data.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface StackEntry {
|
|
16
|
+
name: string;
|
|
17
|
+
slug: string;
|
|
18
|
+
blurb?: string;
|
|
19
|
+
status?: "live" | "expanding" | "planned";
|
|
20
|
+
count?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
stacks: StackEntry[];
|
|
25
|
+
/** List page base. */
|
|
26
|
+
pathPrefix?: string;
|
|
27
|
+
/** Stack icon registry — maps slug → icon file name. Optional. */
|
|
28
|
+
iconAliases?: Record<string, string>;
|
|
29
|
+
/** Extra class. */
|
|
30
|
+
class?: string;
|
|
31
|
+
/** Hide the section eyebrow / header entirely. */
|
|
32
|
+
bare?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
stacks,
|
|
37
|
+
pathPrefix = "/",
|
|
38
|
+
iconAliases,
|
|
39
|
+
class: className = "",
|
|
40
|
+
bare = false,
|
|
41
|
+
} = Astro.props;
|
|
42
|
+
|
|
43
|
+
function buildHref(slug: string): string {
|
|
44
|
+
const base = pathPrefix.replace(/\/$/, "");
|
|
45
|
+
return `${base}?stack=${encodeURIComponent(slug)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function statusLabel(s: StackEntry["status"]): string | null {
|
|
49
|
+
switch (s) {
|
|
50
|
+
case "live":
|
|
51
|
+
return "Live";
|
|
52
|
+
case "expanding":
|
|
53
|
+
return "Expanding";
|
|
54
|
+
case "planned":
|
|
55
|
+
return "Planned";
|
|
56
|
+
default:
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
<section
|
|
63
|
+
class:list={["grove-section", className]}
|
|
64
|
+
aria-labelledby={bare ? undefined : "grove-stackgrid-heading"}
|
|
65
|
+
>
|
|
66
|
+
<div class="grove-container-wide mx-auto px-5 sm:px-7">
|
|
67
|
+
{!bare && (
|
|
68
|
+
<header class="mb-6 flex flex-col gap-2">
|
|
69
|
+
<h2
|
|
70
|
+
id="grove-stackgrid-heading"
|
|
71
|
+
class="m-0 text-[1.5rem] sm:text-[1.625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"
|
|
72
|
+
>
|
|
73
|
+
Browse by stack
|
|
74
|
+
</h2>
|
|
75
|
+
<p class="m-0 max-w-[60ch] text-[0.9375rem] text-ink-500 dark:text-ink-400">
|
|
76
|
+
Every record is tagged with the technologies it depends on. Pick a
|
|
77
|
+
stack to see what's shipping in that world.
|
|
78
|
+
</p>
|
|
79
|
+
</header>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
83
|
+
{stacks.map((s) => (
|
|
84
|
+
<a
|
|
85
|
+
href={buildHref(s.slug)}
|
|
86
|
+
class="grove-card grove-card-hover group flex flex-col gap-1.5 p-4 no-underline"
|
|
87
|
+
data-event="stack_grid_click"
|
|
88
|
+
data-event-source="stack_grid"
|
|
89
|
+
>
|
|
90
|
+
<div class="flex items-baseline justify-between gap-2">
|
|
91
|
+
<h3 class="m-0 text-[0.9375rem] font-semibold tracking-tight text-ink-900 group-hover:underline dark:text-ink-100">
|
|
92
|
+
{s.name}
|
|
93
|
+
</h3>
|
|
94
|
+
{typeof s.count === "number" && (
|
|
95
|
+
<span class="font-mono text-2xs text-ink-500 dark:text-ink-400">
|
|
96
|
+
{s.count}
|
|
97
|
+
</span>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
{s.blurb && (
|
|
101
|
+
<p class="m-0 line-clamp-2 text-[0.8125rem] leading-snug text-ink-500 dark:text-ink-400">
|
|
102
|
+
{s.blurb}
|
|
103
|
+
</p>
|
|
104
|
+
)}
|
|
105
|
+
{statusLabel(s.status) && (
|
|
106
|
+
<span
|
|
107
|
+
class:list={[
|
|
108
|
+
"grove-badge mt-1 self-start",
|
|
109
|
+
s.status === "live"
|
|
110
|
+
? "grove-badge-new"
|
|
111
|
+
: s.status === "expanding"
|
|
112
|
+
? "grove-badge-mature"
|
|
113
|
+
: "grove-badge-stale",
|
|
114
|
+
]}
|
|
115
|
+
>
|
|
116
|
+
{statusLabel(s.status)}
|
|
117
|
+
</span>
|
|
118
|
+
)}
|
|
119
|
+
</a>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</section>
|
|
124
|
+
|
|
125
|
+
<style>
|
|
126
|
+
.line-clamp-2 {
|
|
127
|
+
display: -webkit-box;
|
|
128
|
+
-webkit-line-clamp: 2;
|
|
129
|
+
-webkit-box-orient: vertical;
|
|
130
|
+
overflow: hidden;
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* WhyThisExists — the "what is this site for?" section.
|
|
4
|
+
*
|
|
5
|
+
* Renders 3 short, opinionated points (icon + title + body). The
|
|
6
|
+
* icon set is small and generic: "app" (window/grid glyph),
|
|
7
|
+
* "code" (angle brackets), "search" (magnifier). Defaults to
|
|
8
|
+
* the canonical "directory, source, search" trio.
|
|
9
|
+
*
|
|
10
|
+
* Generic: every point comes through props. No blueprint-specific
|
|
11
|
+
* copy. Consumers can pass `points` to swap the order or copy.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface WhyPoint {
|
|
15
|
+
title: string;
|
|
16
|
+
body: string;
|
|
17
|
+
icon: "app" | "code" | "search";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
points?: WhyPoint[];
|
|
22
|
+
title?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
class?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
points,
|
|
29
|
+
title = "Why this directory exists",
|
|
30
|
+
description = "A short note on the point of all this.",
|
|
31
|
+
class: className = "",
|
|
32
|
+
} = Astro.props;
|
|
33
|
+
|
|
34
|
+
const defaultPoints: WhyPoint[] = [
|
|
35
|
+
{
|
|
36
|
+
icon: "app",
|
|
37
|
+
title: "A curated starting point",
|
|
38
|
+
body: "Skip the rabbit holes. Every entry here is something a real developer reached for, vetted for quality and longevity.",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
icon: "code",
|
|
42
|
+
title: "Real, runnable source",
|
|
43
|
+
body: "Every project links back to its source. Skim, fork, learn — no paywalls, no broken download links.",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
icon: "search",
|
|
47
|
+
title: "Search that respects you",
|
|
48
|
+
body: "Filter by stack, platform, license, or status. No SEO spam, no affiliate farms. The directory is the product.",
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const items = points && points.length > 0 ? points : defaultPoints;
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
<section
|
|
56
|
+
class:list={["grove-section", className]}
|
|
57
|
+
aria-labelledby="grove-why-heading"
|
|
58
|
+
>
|
|
59
|
+
<div class="grove-container mx-auto px-5 sm:px-7">
|
|
60
|
+
<header class="mb-8 flex flex-col items-center gap-2 text-center">
|
|
61
|
+
<h2
|
|
62
|
+
id="grove-why-heading"
|
|
63
|
+
class="m-0 text-[1.5rem] sm:text-[1.625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"
|
|
64
|
+
>
|
|
65
|
+
{title}
|
|
66
|
+
</h2>
|
|
67
|
+
{description && (
|
|
68
|
+
<p class="m-0 max-w-2xl text-[0.9375rem] text-ink-500 dark:text-ink-400">
|
|
69
|
+
{description}
|
|
70
|
+
</p>
|
|
71
|
+
)}
|
|
72
|
+
</header>
|
|
73
|
+
|
|
74
|
+
<ol
|
|
75
|
+
class="m-0 grid list-none grid-cols-1 gap-6 p-0 md:grid-cols-3"
|
|
76
|
+
aria-label="Why this directory"
|
|
77
|
+
>
|
|
78
|
+
{items.map((p, i) => (
|
|
79
|
+
<li class="flex flex-col items-center gap-2 text-center">
|
|
80
|
+
<span
|
|
81
|
+
aria-hidden="true"
|
|
82
|
+
class="inline-flex h-10 w-10 items-center justify-center rounded-full border border-ink-200 bg-white text-ink-700 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-200"
|
|
83
|
+
>
|
|
84
|
+
{p.icon === "app" && (
|
|
85
|
+
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
|
86
|
+
<path d="M1.75 3.5a.25.25 0 0 1 .25-.25h12a.25.25 0 0 1 .25.25v8a.25.25 0 0 1-.25.25h-12a.25.25 0 0 1-.25-.25v-8ZM2 4.75v6h12v-6H2ZM2.5 6.5h4v2.5h-4V6.5Z"></path>
|
|
87
|
+
</svg>
|
|
88
|
+
)}
|
|
89
|
+
{p.icon === "code" && (
|
|
90
|
+
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
|
91
|
+
<path d="M4.72 3.22a.75.75 0 0 1 1.06 1.06L2.06 8l3.72 3.72a.75.75 0 1 1-1.06 1.06L.47 8.53a.75.75 0 0 1 0-1.06l4.25-4.25Zm6.56 0a.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.06L13.94 8l-3.72-3.72a.75.75 0 0 1 0-1.06Z"></path>
|
|
92
|
+
</svg>
|
|
93
|
+
)}
|
|
94
|
+
{p.icon === "search" && (
|
|
95
|
+
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
|
96
|
+
<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>
|
|
97
|
+
</svg>
|
|
98
|
+
)}
|
|
99
|
+
</span>
|
|
100
|
+
<span
|
|
101
|
+
aria-hidden="true"
|
|
102
|
+
class="font-mono text-2xs text-ink-400 dark:text-ink-500"
|
|
103
|
+
>
|
|
104
|
+
0{i + 1}
|
|
105
|
+
</span>
|
|
106
|
+
<h3 class="m-0 text-base font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
107
|
+
{p.title}
|
|
108
|
+
</h3>
|
|
109
|
+
<p class="m-0 max-w-xs text-[0.875rem] leading-relaxed text-ink-500 dark:text-ink-400">
|
|
110
|
+
{p.body}
|
|
111
|
+
</p>
|
|
112
|
+
</li>
|
|
113
|
+
))}
|
|
114
|
+
</ol>
|
|
115
|
+
</div>
|
|
116
|
+
</section>
|