@grove-dev/astro 0.2.16 → 0.2.19
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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/display.js +4 -4
- package/dist/lib/display.js.map +1 -1
- package/dist/lib/repo.d.ts.map +1 -1
- package/dist/lib/repo.js +3 -4
- package/dist/lib/repo.js.map +1 -1
- package/dist/lib/search.d.ts +2 -2
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/search.js +17 -6
- package/dist/lib/search.js.map +1 -1
- package/dist/theme.test.d.ts +2 -0
- package/dist/theme.test.d.ts.map +1 -0
- package/dist/theme.test.js +41 -0
- package/dist/theme.test.js.map +1 -0
- package/package.json +1 -1
- package/src/components/FilterGroupMenu.astro +3 -1
- package/src/components/Hero.astro +3 -3
- package/src/components/Icon.astro +4 -4
- package/src/components/IndexRow.astro +15 -2
- package/src/index.ts +52 -1
- package/src/layouts/BaseLayout.astro +12 -11
- package/src/layouts/Seo.astro +12 -4
- package/src/lib/display.ts +4 -4
- package/src/lib/repo.ts +3 -4
- package/src/lib/search.ts +18 -8
- package/src/styles.css +184 -69
- package/src/theme.test.ts +52 -0
- package/templates/default/package.json +2 -1
- package/templates/default/public/llms-full.txt +1 -1
- package/templates/default/public/sitemap.xml +10 -10
- package/templates/default/src/data/records.ts +115 -77
- package/templates/default/src/pages/[slug]/[recordSlug].astro +29 -33
- package/templates/default/src/pages/[slug]/index.astro +20 -296
- package/templates/default/src/styles/global.css +14 -39
- package/templates/default/tailwind.config.mjs +0 -130
|
@@ -7,12 +7,17 @@
|
|
|
7
7
|
* - RefinePanel (Stack / Platform / Category / License / Sort)
|
|
8
8
|
* - RecordSection rows (full record cards; the `ItemSection` V0
|
|
9
9
|
* name was renamed to `RecordSection` in V1)
|
|
10
|
-
* - ExploreByCategory / ExploreByStack (compact pill lists)
|
|
11
10
|
*
|
|
12
11
|
* Generic: the URL slug (e.g. `/projects/`, `/resources/`,
|
|
13
12
|
* `/entities/`) is derived from the blueprint via `getStaticPaths`,
|
|
14
13
|
* so the same page works for every blueprint without per-blueprint
|
|
15
14
|
* forks.
|
|
15
|
+
*
|
|
16
|
+
* Navigation model: full-page render. The form posts via `method="get"`
|
|
17
|
+
* so the browser navigates to `?q=...&sort=...&stack=...`, and the
|
|
18
|
+
* server re-runs `filterRecords` / `applySort` / `paginate` from
|
|
19
|
+
* those query params. No client-side filter shim, no duplicated
|
|
20
|
+
* logic. The URL stays the single source of truth for filter state.
|
|
16
21
|
*/
|
|
17
22
|
import siteConfig from "../../../data/generated/site-config.json";
|
|
18
23
|
import { items, itemLabel, itemLabelPlural } from "../../data/records";
|
|
@@ -22,36 +27,27 @@ import SmartLensTabs from "@grove-dev/astro/components/SmartLensTabs.astro";
|
|
|
22
27
|
import RefinePanel from "@grove-dev/astro/components/RefinePanel.astro";
|
|
23
28
|
import IndexRow from "@grove-dev/astro/components/IndexRow.astro";
|
|
24
29
|
import Pagination from "@grove-dev/astro/components/Pagination.astro";
|
|
25
|
-
import ExploreByCategory from "@grove-dev/astro/components/ExploreByCategory.astro";
|
|
26
|
-
import ExploreByStack from "@grove-dev/astro/components/ExploreByStack.astro";
|
|
27
30
|
import {
|
|
28
31
|
activeFilterChips,
|
|
29
32
|
applySort,
|
|
30
33
|
buildFacets,
|
|
31
|
-
countByCategory,
|
|
32
|
-
countByStack,
|
|
33
34
|
effectivePage,
|
|
34
35
|
effectiveSort,
|
|
35
36
|
filterRecords,
|
|
36
37
|
filtersFromSearchParams,
|
|
37
|
-
paginate,
|
|
38
38
|
searchParamsFromFilters,
|
|
39
39
|
sortDisplay,
|
|
40
40
|
SORT_OPTIONS,
|
|
41
41
|
totalPages,
|
|
42
42
|
} from "@grove-dev/astro";
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
const all = ["projects", "resources", "entities"];
|
|
53
|
-
if (!all.includes(activeSlug)) all.push(activeSlug);
|
|
54
|
-
return all.map((slug) => ({ params: { slug } }));
|
|
44
|
+
// This template ships with the `project-directory` blueprint, so the
|
|
45
|
+
// only directory slug it ever needs is `projects`. While it ships
|
|
46
|
+
// as the project directory, pre-generate the single path the build
|
|
47
|
+
// actually needs. Swapping in a different blueprint is a one-config
|
|
48
|
+
// change that flows through the static import at the top.
|
|
49
|
+
export function getStaticPaths() {
|
|
50
|
+
return [{ params: { slug: "projects" } }];
|
|
55
51
|
}
|
|
56
52
|
|
|
57
53
|
const slug = Astro.params.slug as string;
|
|
@@ -64,50 +60,11 @@ const filtered = filterRecords(items, filters);
|
|
|
64
60
|
const sorted = applySort(filtered, sort);
|
|
65
61
|
const pages = totalPages(sorted.length);
|
|
66
62
|
const page = Math.min(requestedPage, pages);
|
|
67
|
-
const visibleItems = paginate(sorted, page);
|
|
68
63
|
const chips = activeFilterChips(filters);
|
|
69
|
-
const clientItems = items.map((item) => ({
|
|
70
|
-
slug: item.slug,
|
|
71
|
-
name: item.kind === "resource" ? item.title : item.name,
|
|
72
|
-
description: item.description,
|
|
73
|
-
category: item.category,
|
|
74
|
-
tags: item.tags,
|
|
75
|
-
labels: item.curation?.labels ?? [],
|
|
76
|
-
lenses: item.curation?.lenses ?? [],
|
|
77
|
-
reviewed: Boolean(item.curation?.reviewed),
|
|
78
|
-
addedAt: item.curation?.reviewedAt ?? "",
|
|
79
|
-
stacks: item.kind === "project" ? [item.stack, ...item.stacks].filter(Boolean) : [],
|
|
80
|
-
platforms: item.kind === "project" ? item.platforms : [],
|
|
81
|
-
license: item.kind === "project" ? item.github?.license ?? "" : "",
|
|
82
|
-
status: item.kind === "project" ? item.health?.status ?? "" : "",
|
|
83
|
-
stars: item.kind === "project" ? item.github?.stars ?? 0 : 0,
|
|
84
|
-
updatedAt:
|
|
85
|
-
item.kind === "project"
|
|
86
|
-
? item.github?.pushedAt ?? ""
|
|
87
|
-
: item.kind === "resource"
|
|
88
|
-
? item.publishedAt ?? ""
|
|
89
|
-
: "",
|
|
90
|
-
extra:
|
|
91
|
-
item.kind === "project"
|
|
92
|
-
? [item.repoUrl, item.projectType, item.difficulty, item.codebaseSize, ...item.bestFor, ...item.whyListed]
|
|
93
|
-
: item.kind === "resource"
|
|
94
|
-
? [item.type, item.topic, item.author ?? ""]
|
|
95
|
-
: [item.type, item.location ?? "", item.parent ?? ""],
|
|
96
|
-
}));
|
|
97
64
|
|
|
98
65
|
const title = `Browse ${slug} — ${siteConfig.name}`;
|
|
99
66
|
const description = `A searchable directory of ${itemLabelPlural()} for ${siteConfig.name}.`;
|
|
100
67
|
|
|
101
|
-
// Compute the top categories and stacks for the explore pills
|
|
102
|
-
const categoryEntries = [...countByCategory(items as Parameters<typeof countByCategory>[0]).entries()]
|
|
103
|
-
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
104
|
-
.slice(0, 12)
|
|
105
|
-
.map(([name, count]) => ({ name, slug: name, count }));
|
|
106
|
-
const stackEntries = [...countByStack(items as Parameters<typeof countByStack>[0]).entries()]
|
|
107
|
-
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
108
|
-
.slice(0, 18)
|
|
109
|
-
.map(([name, count]) => ({ name, slug: name, count }));
|
|
110
|
-
|
|
111
68
|
function hrefForPage(target: number): string {
|
|
112
69
|
const next: IndexFilters = { ...filters, page: target };
|
|
113
70
|
const query = searchParamsFromFilters(next).toString();
|
|
@@ -235,20 +192,6 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
|
|
|
235
192
|
</div>
|
|
236
193
|
</section>
|
|
237
194
|
|
|
238
|
-
{categoryEntries.length > 0 && (
|
|
239
|
-
<ExploreByCategory
|
|
240
|
-
categories={categoryEntries}
|
|
241
|
-
pathPrefix={`/${slug}`}
|
|
242
|
-
/>
|
|
243
|
-
)}
|
|
244
|
-
|
|
245
|
-
{stackEntries.length > 0 && (
|
|
246
|
-
<ExploreByStack
|
|
247
|
-
stacks={stackEntries}
|
|
248
|
-
pathPrefix={`/${slug}`}
|
|
249
|
-
/>
|
|
250
|
-
)}
|
|
251
|
-
|
|
252
195
|
<section class="grove-section-tight">
|
|
253
196
|
<div class="grove-container-wide mx-auto px-5 sm:px-7">
|
|
254
197
|
<header class="mb-6">
|
|
@@ -291,9 +234,13 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
|
|
|
291
234
|
</div>
|
|
292
235
|
|
|
293
236
|
<ul id="results-list" class="m-0 flex list-none flex-col gap-3 p-0">
|
|
294
|
-
{sorted.map((item) => (
|
|
237
|
+
{sorted.map((item, i) => (
|
|
295
238
|
<li class="m-0 p-0" data-record-slug={item.slug}>
|
|
296
|
-
<IndexRow
|
|
239
|
+
<IndexRow
|
|
240
|
+
record={item as Parameters<typeof IndexRow>[0]["record"]}
|
|
241
|
+
detailHref={`/${slug}/${item.slug}`}
|
|
242
|
+
eager={i < 2}
|
|
243
|
+
/>
|
|
297
244
|
</li>
|
|
298
245
|
))}
|
|
299
246
|
</ul>
|
|
@@ -324,231 +271,8 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
|
|
|
324
271
|
>
|
|
325
272
|
{`Submit ${itemLabel()}`}
|
|
326
273
|
</a>
|
|
274
|
+
|
|
327
275
|
</div>
|
|
328
276
|
</div>
|
|
329
277
|
</section>
|
|
330
|
-
|
|
331
|
-
<script is:inline define:vars={{ clientItems, slug, itemSingular: itemLabel(), itemPlural: itemLabelPlural() }}>
|
|
332
|
-
(function () {
|
|
333
|
-
var PAGE_SIZE = 20;
|
|
334
|
-
var list = document.getElementById("results-list");
|
|
335
|
-
if (!list) return;
|
|
336
|
-
|
|
337
|
-
// Sort dropdown: navigate on change so the server re-renders
|
|
338
|
-
// the sorted list (URL is the source of truth for filters/sort).
|
|
339
|
-
var sortSelect = document.getElementById("sort-select");
|
|
340
|
-
if (sortSelect) {
|
|
341
|
-
sortSelect.addEventListener("change", function () {
|
|
342
|
-
var params = new URLSearchParams(location.search);
|
|
343
|
-
if (sortSelect.value && sortSelect.value !== "recently-updated") {
|
|
344
|
-
params.set("sort", sortSelect.value);
|
|
345
|
-
} else {
|
|
346
|
-
params.delete("sort");
|
|
347
|
-
}
|
|
348
|
-
params.delete("page");
|
|
349
|
-
var qs = params.toString();
|
|
350
|
-
location.assign(qs ? "?" + qs : location.pathname);
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Search: 250ms debounce; pressing Enter still submits
|
|
355
|
-
// immediately via the form's normal submit handler.
|
|
356
|
-
var searchInput = document.getElementById("search-input");
|
|
357
|
-
var searchForm = document.getElementById("search-form");
|
|
358
|
-
if (searchInput && searchForm) {
|
|
359
|
-
var debounceTimer = null;
|
|
360
|
-
var submit = function () {
|
|
361
|
-
var params = new URLSearchParams(location.search);
|
|
362
|
-
var term = searchInput.value.trim();
|
|
363
|
-
if (term) params.set("q", term);
|
|
364
|
-
else params.delete("q");
|
|
365
|
-
params.delete("page");
|
|
366
|
-
var qs = params.toString();
|
|
367
|
-
location.assign(qs ? "?" + qs : location.pathname);
|
|
368
|
-
};
|
|
369
|
-
searchInput.addEventListener("input", function () {
|
|
370
|
-
if (debounceTimer) window.clearTimeout(debounceTimer);
|
|
371
|
-
debounceTimer = window.setTimeout(submit, 250);
|
|
372
|
-
});
|
|
373
|
-
searchForm.addEventListener("submit", function (event) {
|
|
374
|
-
event.preventDefault();
|
|
375
|
-
if (debounceTimer) window.clearTimeout(debounceTimer);
|
|
376
|
-
submit();
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function values(params, key) {
|
|
381
|
-
return params.getAll(key).filter(Boolean);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function display(value) {
|
|
385
|
-
return String(value || "")
|
|
386
|
-
.split(/[-_]+/)
|
|
387
|
-
.filter(Boolean)
|
|
388
|
-
.map(function (part) { return part.charAt(0).toUpperCase() + part.slice(1); })
|
|
389
|
-
.join(" ");
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function sortLabel(value) {
|
|
393
|
-
return {
|
|
394
|
-
"recently-updated": "Recently updated",
|
|
395
|
-
"most-starred": "Most starred",
|
|
396
|
-
"recently-added": "Recently added",
|
|
397
|
-
"best-overall": "Best overall",
|
|
398
|
-
alphabetical: "Alphabetical",
|
|
399
|
-
}[value] || "Recently updated";
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function pageHref(params, page) {
|
|
403
|
-
var next = new URLSearchParams(params);
|
|
404
|
-
if (page <= 1) next.delete("page");
|
|
405
|
-
else next.set("page", String(page));
|
|
406
|
-
var query = next.toString();
|
|
407
|
-
return query ? "/" + slug + "?" + query : "/" + slug;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function apply() {
|
|
411
|
-
var params = new URLSearchParams(location.search);
|
|
412
|
-
var query = (params.get("q") || "").trim().toLowerCase();
|
|
413
|
-
var stacks = values(params, "stack");
|
|
414
|
-
var platforms = values(params, "platform");
|
|
415
|
-
var categories = values(params, "category");
|
|
416
|
-
var labels = values(params, "label");
|
|
417
|
-
var licenses = values(params, "license");
|
|
418
|
-
var statuses = (params.get("status") || "").split(",").filter(Boolean);
|
|
419
|
-
var lens = params.get("lens") || "";
|
|
420
|
-
var sort = params.get("sort") || "recently-updated";
|
|
421
|
-
var page = Math.max(1, Number(params.get("page")) || 1);
|
|
422
|
-
|
|
423
|
-
var filtered = clientItems.filter(function (item) {
|
|
424
|
-
var haystack = [
|
|
425
|
-
item.name,
|
|
426
|
-
item.description,
|
|
427
|
-
item.category,
|
|
428
|
-
item.tags,
|
|
429
|
-
item.stacks,
|
|
430
|
-
item.platforms,
|
|
431
|
-
item.license,
|
|
432
|
-
item.status,
|
|
433
|
-
item.extra,
|
|
434
|
-
].flat(3).join(" ").toLowerCase();
|
|
435
|
-
if (query && !haystack.includes(query)) return false;
|
|
436
|
-
if (stacks.length && !item.stacks.some(function (value) { return stacks.includes(value); })) return false;
|
|
437
|
-
if (platforms.length && !item.platforms.some(function (value) { return platforms.includes(value); })) return false;
|
|
438
|
-
if (categories.length && !categories.includes(item.category)) return false;
|
|
439
|
-
if (labels.length && !item.labels.some(function (value) { return labels.includes(value); })) return false;
|
|
440
|
-
if (licenses.length && !licenses.includes(item.license)) return false;
|
|
441
|
-
if (statuses.length && !statuses.includes(item.status)) return false;
|
|
442
|
-
if (lens && !item.lenses.includes(lens)) return false;
|
|
443
|
-
return true;
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
filtered.sort(function (a, b) {
|
|
447
|
-
if (sort === "most-starred") return b.stars - a.stars;
|
|
448
|
-
if (sort === "recently-added") return Date.parse(b.addedAt || 0) - Date.parse(a.addedAt || 0);
|
|
449
|
-
if (sort === "best-overall") return Number(b.reviewed) - Number(a.reviewed) || b.stars - a.stars;
|
|
450
|
-
if (sort === "alphabetical") return a.name.localeCompare(b.name);
|
|
451
|
-
return Date.parse(b.updatedAt || 0) - Date.parse(a.updatedAt || 0);
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
var totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
|
|
455
|
-
page = Math.min(page, totalPages);
|
|
456
|
-
var visible = new Set(
|
|
457
|
-
filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE).map(function (item) { return item.slug; }),
|
|
458
|
-
);
|
|
459
|
-
var order = new Map(filtered.map(function (item, index) { return [item.slug, index]; }));
|
|
460
|
-
Array.from(list.children)
|
|
461
|
-
.sort(function (a, b) {
|
|
462
|
-
return (order.get(a.dataset.recordSlug) ?? Number.MAX_SAFE_INTEGER) -
|
|
463
|
-
(order.get(b.dataset.recordSlug) ?? Number.MAX_SAFE_INTEGER);
|
|
464
|
-
})
|
|
465
|
-
.forEach(function (node) {
|
|
466
|
-
node.hidden = !visible.has(node.dataset.recordSlug);
|
|
467
|
-
list.appendChild(node);
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
var count = document.getElementById("results-count");
|
|
471
|
-
if (count) count.textContent = filtered.length + " " + (filtered.length === 1 ? itemSingular : itemPlural);
|
|
472
|
-
var sortText = document.getElementById("results-sort");
|
|
473
|
-
if (sortText) sortText.textContent = " · sorted by " + sortLabel(sort);
|
|
474
|
-
var pageText = document.getElementById("results-page");
|
|
475
|
-
if (pageText) pageText.textContent = totalPages > 1 ? " · page " + page + " of " + totalPages : "";
|
|
476
|
-
document.getElementById("empty-state")?.classList.toggle("hidden", filtered.length > 0);
|
|
477
|
-
|
|
478
|
-
var search = document.querySelector('input[name="q"]');
|
|
479
|
-
if (search) search.value = params.get("q") || "";
|
|
480
|
-
document.querySelectorAll("select").forEach(function (select) {
|
|
481
|
-
var selected = values(params, select.name);
|
|
482
|
-
if (select.name === "sort") selected = [sort];
|
|
483
|
-
Array.from(select.options).forEach(function (option) {
|
|
484
|
-
option.selected = selected.includes(option.value);
|
|
485
|
-
});
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
// Pagination is server-rendered (see <Pagination /> in the
|
|
489
|
-
// template) so the static HTML contains the page-number
|
|
490
|
-
// links for SEO. No client-side rendering loop here.
|
|
491
|
-
|
|
492
|
-
var chipHost = document.getElementById("active-filters");
|
|
493
|
-
if (chipHost) {
|
|
494
|
-
chipHost.innerHTML = "";
|
|
495
|
-
var specs = [
|
|
496
|
-
["q", params.get("q") ? "“" + params.get("q") + "”" : ""],
|
|
497
|
-
...stacks.map(function (v) { return ["stack", "Stack: " + v]; }),
|
|
498
|
-
...platforms.map(function (v) { return ["platform", "Platform: " + v]; }),
|
|
499
|
-
...categories.map(function (v) { return ["category", "Category: " + v]; }),
|
|
500
|
-
...labels.map(function (v) { return ["label", display(v)]; }),
|
|
501
|
-
...licenses.map(function (v) { return ["license", "License: " + v]; }),
|
|
502
|
-
...(lens ? [["lens", display(lens)]] : []),
|
|
503
|
-
...statuses.map(function (v) { return ["status", display(v)]; }),
|
|
504
|
-
].filter(function (entry) { return entry[1]; });
|
|
505
|
-
specs.forEach(function (entry) {
|
|
506
|
-
var key = entry[0];
|
|
507
|
-
var chip = document.createElement("a");
|
|
508
|
-
var next = new URLSearchParams(params);
|
|
509
|
-
if (key === "status") {
|
|
510
|
-
var remaining = statuses.filter(function (value) { return display(value) !== entry[1]; });
|
|
511
|
-
next.delete("status");
|
|
512
|
-
if (remaining.length) next.set("status", remaining.join(","));
|
|
513
|
-
} else {
|
|
514
|
-
next.delete(key);
|
|
515
|
-
}
|
|
516
|
-
next.delete("page");
|
|
517
|
-
chip.href = next.toString() ? "/" + slug + "?" + next : "/" + slug;
|
|
518
|
-
chip.className = "grove-badge transition-colors hover:border-ink-400";
|
|
519
|
-
chip.textContent = entry[1] + " x";
|
|
520
|
-
chipHost.appendChild(chip);
|
|
521
|
-
});
|
|
522
|
-
if (specs.length) {
|
|
523
|
-
var clear = document.createElement("a");
|
|
524
|
-
clear.href = "/" + slug;
|
|
525
|
-
clear.className = "text-2xs font-medium text-ink-500 hover:text-ink-900";
|
|
526
|
-
clear.textContent = "Clear all";
|
|
527
|
-
chipHost.appendChild(clear);
|
|
528
|
-
}
|
|
529
|
-
chipHost.classList.toggle("hidden", specs.length === 0);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
document.querySelectorAll("form").forEach(function (form) {
|
|
534
|
-
form.addEventListener("submit", function (event) {
|
|
535
|
-
if (!form.closest("main")) return;
|
|
536
|
-
event.preventDefault();
|
|
537
|
-
var params = new URLSearchParams(location.search);
|
|
538
|
-
form.querySelectorAll("input, select").forEach(function (field) {
|
|
539
|
-
if (!field.name) return;
|
|
540
|
-
params.delete(field.name);
|
|
541
|
-
if (field instanceof HTMLSelectElement && field.multiple) {
|
|
542
|
-
Array.from(field.selectedOptions).forEach(function (option) { params.append(field.name, option.value); });
|
|
543
|
-
} else if (field.value) {
|
|
544
|
-
params.set(field.name, field.value);
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
params.delete("page");
|
|
548
|
-
location.assign(params.toString() ? "?" + params : location.pathname);
|
|
549
|
-
});
|
|
550
|
-
});
|
|
551
|
-
apply();
|
|
552
|
-
})();
|
|
553
|
-
</script>
|
|
554
278
|
</BaseLayout>
|
|
@@ -5,26 +5,22 @@
|
|
|
5
5
|
* at scaffold time; we expose the same names as CSS variables so
|
|
6
6
|
* consumers can layer their own brand colors in without forking.
|
|
7
7
|
*
|
|
8
|
-
* The
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* The package owns the Starlight-aligned palette, semantic theme tokens,
|
|
9
|
+
* font stacks, and light/dark base styles. This consumer sheet contains
|
|
10
|
+
* only scaffold-level overrides so those foundations cannot drift when
|
|
11
|
+
* the package theme changes.
|
|
12
|
+
*
|
|
13
|
+
* NOTE: do NOT add `@custom-variant dark` here. `@grove-dev/astro`'s
|
|
14
|
+
* `styles.css` already declares the `.dark`-class variant, and
|
|
15
|
+
* Tailwind v4 only honors one definition per build — adding a
|
|
16
|
+
* second one in this consumer file has been observed to confuse
|
|
17
|
+
* the variant resolution and produce utilities that key off
|
|
18
|
+
* `prefers-color-scheme` instead of the `.dark` class on <html>.
|
|
19
|
+
* The net effect: `<html class="dark">` is set but the page stays
|
|
20
|
+
* in light mode because the `dark:` utilities never match.
|
|
13
21
|
*/
|
|
14
22
|
@theme {
|
|
15
|
-
--color-
|
|
16
|
-
--color-ink-100: #f4f4f5;
|
|
17
|
-
--color-ink-200: #e4e4e7;
|
|
18
|
-
--color-ink-300: #d4d4d8;
|
|
19
|
-
--color-ink-400: #a1a1aa;
|
|
20
|
-
--color-ink-500: #71717a;
|
|
21
|
-
--color-ink-600: #52525b;
|
|
22
|
-
--color-ink-700: #3f3f46;
|
|
23
|
-
--color-ink-800: #27272a;
|
|
24
|
-
--color-ink-900: #18181b;
|
|
25
|
-
--color-ink-950: #09090b;
|
|
26
|
-
|
|
27
|
-
--color-grove-primary: #16a34a;
|
|
23
|
+
--color-grove-primary: var(--color-foreground);
|
|
28
24
|
}
|
|
29
25
|
|
|
30
26
|
@layer base {
|
|
@@ -33,26 +29,5 @@
|
|
|
33
29
|
--grove-radius: 0.5rem;
|
|
34
30
|
--grove-container-width: 72rem;
|
|
35
31
|
|
|
36
|
-
color-scheme: light dark;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
html {
|
|
40
|
-
-webkit-text-size-adjust: 100%;
|
|
41
|
-
-webkit-font-smoothing: antialiased;
|
|
42
|
-
-moz-osx-font-smoothing: grayscale;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
body {
|
|
46
|
-
background: #ffffff;
|
|
47
|
-
color: var(--color-ink-900);
|
|
48
|
-
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
|
|
49
|
-
"Helvetica Neue", Arial, sans-serif;
|
|
50
|
-
font-size: 15px;
|
|
51
|
-
line-height: 1.55;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
body.dark {
|
|
55
|
-
background: var(--color-ink-950);
|
|
56
|
-
color: var(--color-ink-100);
|
|
57
32
|
}
|
|
58
33
|
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/** @type {import('tailwindcss').Config} */
|
|
2
|
-
export default {
|
|
3
|
-
content: ['./src/**/*.{astro,html,js,jsx,ts,tsx,md,mdx}'],
|
|
4
|
-
// Use a `.dark` class on <html> (toggled by the theme script in
|
|
5
|
-
// the document head) rather than the OS media query, so a manual
|
|
6
|
-
// theme switcher can override the user's system preference.
|
|
7
|
-
darkMode: 'class',
|
|
8
|
-
theme: {
|
|
9
|
-
// Disable default palette so we only ship our own neutral / accent
|
|
10
|
-
// tokens. Keeps the bundle small and forces a unified look.
|
|
11
|
-
colors: {
|
|
12
|
-
transparent: 'transparent',
|
|
13
|
-
current: 'currentColor',
|
|
14
|
-
white: '#ffffff',
|
|
15
|
-
black: '#000000',
|
|
16
|
-
ink: {
|
|
17
|
-
50: '#fafafa',
|
|
18
|
-
100: '#f4f4f5',
|
|
19
|
-
200: '#e4e4e7',
|
|
20
|
-
300: '#d4d4d8',
|
|
21
|
-
400: '#a1a1aa',
|
|
22
|
-
500: '#71717a',
|
|
23
|
-
600: '#52525b',
|
|
24
|
-
700: '#3f3f46',
|
|
25
|
-
800: '#27272a',
|
|
26
|
-
900: '#18181b',
|
|
27
|
-
950: '#09090b',
|
|
28
|
-
},
|
|
29
|
-
accent: {
|
|
30
|
-
DEFAULT: '#1f6feb',
|
|
31
|
-
muted: '#388bfd1a',
|
|
32
|
-
},
|
|
33
|
-
emerald: {
|
|
34
|
-
50: '#ecfdf5',
|
|
35
|
-
300: '#6ee7b7',
|
|
36
|
-
400: '#34d399',
|
|
37
|
-
700: '#047857',
|
|
38
|
-
800: '#065f46',
|
|
39
|
-
950: '#022c22',
|
|
40
|
-
},
|
|
41
|
-
orange: {
|
|
42
|
-
50: '#fff7ed',
|
|
43
|
-
300: '#fdba74',
|
|
44
|
-
400: '#fb923c',
|
|
45
|
-
700: '#c2410c',
|
|
46
|
-
800: '#9a3412',
|
|
47
|
-
950: '#431407',
|
|
48
|
-
},
|
|
49
|
-
blue: {
|
|
50
|
-
50: '#eff6ff',
|
|
51
|
-
300: '#93c5fd',
|
|
52
|
-
400: '#60a5fa',
|
|
53
|
-
700: '#1d4ed8',
|
|
54
|
-
800: '#1e40af',
|
|
55
|
-
950: '#172554',
|
|
56
|
-
},
|
|
57
|
-
amber: {
|
|
58
|
-
50: '#fffbeb',
|
|
59
|
-
300: '#fcd34d',
|
|
60
|
-
400: '#fbbf24',
|
|
61
|
-
700: '#b45309',
|
|
62
|
-
800: '#92400e',
|
|
63
|
-
950: '#451a03',
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
extend: {
|
|
67
|
-
fontFamily: {
|
|
68
|
-
sans: [
|
|
69
|
-
'ui-sans-serif',
|
|
70
|
-
'system-ui',
|
|
71
|
-
'-apple-system',
|
|
72
|
-
'BlinkMacSystemFont',
|
|
73
|
-
'"Segoe UI"',
|
|
74
|
-
'Roboto',
|
|
75
|
-
'"Helvetica Neue"',
|
|
76
|
-
'Arial',
|
|
77
|
-
'sans-serif',
|
|
78
|
-
],
|
|
79
|
-
mono: [
|
|
80
|
-
'ui-monospace',
|
|
81
|
-
'SFMono-Regular',
|
|
82
|
-
'Menlo',
|
|
83
|
-
'Monaco',
|
|
84
|
-
'Consolas',
|
|
85
|
-
'"Liberation Mono"',
|
|
86
|
-
'"Courier New"',
|
|
87
|
-
'monospace',
|
|
88
|
-
],
|
|
89
|
-
},
|
|
90
|
-
fontSize: {
|
|
91
|
-
'2xs': ['0.6875rem', { lineHeight: '1rem' }],
|
|
92
|
-
},
|
|
93
|
-
maxWidth: {
|
|
94
|
-
container: '1140px',
|
|
95
|
-
wide: '1400px',
|
|
96
|
-
narrow: '720px',
|
|
97
|
-
},
|
|
98
|
-
borderRadius: {
|
|
99
|
-
DEFAULT: '0.375rem',
|
|
100
|
-
lg: '0.5rem',
|
|
101
|
-
xl: '0.75rem',
|
|
102
|
-
},
|
|
103
|
-
keyframes: {
|
|
104
|
-
'fade-in-up': {
|
|
105
|
-
'0%': { opacity: '0', transform: 'translateY(8px)' },
|
|
106
|
-
'100%': { opacity: '1', transform: 'translateY(0)' },
|
|
107
|
-
},
|
|
108
|
-
'fade-in': {
|
|
109
|
-
'0%': { opacity: '0' },
|
|
110
|
-
'100%': { opacity: '1' },
|
|
111
|
-
},
|
|
112
|
-
'gradient-shift': {
|
|
113
|
-
'0%, 100%': { 'background-position': '0% 50%' },
|
|
114
|
-
'50%': { 'background-position': '100% 50%' },
|
|
115
|
-
},
|
|
116
|
-
'subtle-bob': {
|
|
117
|
-
'0%, 100%': { transform: 'translateY(0)' },
|
|
118
|
-
'50%': { transform: 'translateY(-2px)' },
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
animation: {
|
|
122
|
-
'fade-in-up': 'fade-in-up 0.6s ease-out both',
|
|
123
|
-
'fade-in': 'fade-in 0.8s ease-out both',
|
|
124
|
-
'gradient-shift': 'gradient-shift 8s ease-in-out infinite',
|
|
125
|
-
'subtle-bob': 'subtle-bob 3s ease-in-out infinite',
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
plugins: [],
|
|
130
|
-
};
|