@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.
Files changed (237) hide show
  1. package/README.md +26 -18
  2. package/dist/lib/display.d.ts +11 -17
  3. package/dist/lib/display.d.ts.map +1 -1
  4. package/dist/lib/display.js +43 -43
  5. package/dist/lib/display.js.map +1 -1
  6. package/dist/lib/format.d.ts +6 -7
  7. package/dist/lib/format.d.ts.map +1 -1
  8. package/dist/lib/format.js +11 -12
  9. package/dist/lib/format.js.map +1 -1
  10. package/dist/lib/lenses.d.ts +35 -73
  11. package/dist/lib/lenses.d.ts.map +1 -1
  12. package/dist/lib/lenses.js +144 -126
  13. package/dist/lib/lenses.js.map +1 -1
  14. package/dist/lib/repo.d.ts +12 -22
  15. package/dist/lib/repo.d.ts.map +1 -1
  16. package/dist/lib/repo.js +13 -25
  17. package/dist/lib/repo.js.map +1 -1
  18. package/dist/lib/scores.d.ts +15 -16
  19. package/dist/lib/scores.d.ts.map +1 -1
  20. package/dist/lib/scores.js +4 -11
  21. package/dist/lib/scores.js.map +1 -1
  22. package/dist/lib/search.d.ts +44 -129
  23. package/dist/lib/search.d.ts.map +1 -1
  24. package/dist/lib/search.js +192 -180
  25. package/dist/lib/search.js.map +1 -1
  26. package/dist/lib/taxonomy-counts.d.ts +7 -21
  27. package/dist/lib/taxonomy-counts.d.ts.map +1 -1
  28. package/dist/lib/taxonomy-counts.js +12 -31
  29. package/dist/lib/taxonomy-counts.js.map +1 -1
  30. package/package.json +4 -4
  31. package/src/components/CategoryGrid.astro +43 -31
  32. package/src/components/ContributorsGrid.astro +3 -3
  33. package/src/components/CurationGrid.astro +41 -0
  34. package/src/components/ExploreByStack.astro +6 -0
  35. package/src/components/FilterGroupMenu.astro +49 -0
  36. package/src/components/FilterOptions.astro +44 -0
  37. package/src/components/Hero.astro +198 -147
  38. package/src/components/Icon.astro +10 -3
  39. package/src/components/IndexRow.astro +75 -0
  40. package/src/components/ItemCard.astro +266 -248
  41. package/src/components/Pagination.astro +63 -0
  42. package/src/components/RecordSection.astro +70 -34
  43. package/src/components/RefinePanel.astro +115 -141
  44. package/src/components/StackGrid.astro +32 -24
  45. package/src/components/WhyThisExists.astro +32 -32
  46. package/src/layouts/BaseLayout.astro +104 -49
  47. package/src/layouts/Container.astro +5 -44
  48. package/src/layouts/Footer.astro +31 -20
  49. package/src/layouts/Header.astro +40 -19
  50. package/src/layouts/SectionHeader.astro +27 -43
  51. package/src/layouts/Seo.astro +38 -56
  52. package/src/layouts/ThemeToggle.astro +35 -48
  53. package/src/lib/display.ts +46 -53
  54. package/src/lib/format.ts +11 -12
  55. package/src/lib/lenses.ts +151 -172
  56. package/src/lib/repo.ts +17 -28
  57. package/src/lib/scores.ts +15 -19
  58. package/src/lib/search.ts +218 -291
  59. package/src/lib/taxonomy-counts.ts +12 -46
  60. package/src/styles.css +209 -1
  61. package/templates/default/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
  62. package/templates/default/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  63. package/templates/default/.github/ISSUE_TEMPLATE/record_submission.md +48 -0
  64. package/templates/default/.github/pull_request_template.md +27 -0
  65. package/templates/default/.github/workflows/build.yml +55 -0
  66. package/templates/default/.github/workflows/cleanup-stale-records.yml +48 -0
  67. package/templates/default/.github/workflows/daily-refresh.yml +53 -0
  68. package/templates/default/.github/workflows/sync-contributors.yml +53 -0
  69. package/templates/default/.github/workflows/sync-github-metadata.yml +58 -0
  70. package/templates/default/.github/workflows/validate-data.yml +35 -0
  71. package/templates/default/data/records/coolify.yml +46 -0
  72. package/templates/default/data/records/gitea.yml +46 -0
  73. package/templates/default/data/records/hoppscotch.yml +47 -0
  74. package/templates/default/data/records/inventree.yml +47 -0
  75. package/templates/default/data/records/label-studio.yml +46 -0
  76. package/templates/default/data/records/logseq.yml +48 -0
  77. package/templates/default/data/records/private-gpt.yml +47 -0
  78. package/templates/default/data/records/wakapi.yml +44 -0
  79. package/templates/default/data/taxonomy/categories.yml +47 -0
  80. package/templates/default/data/taxonomy/distribution-channels.yml +43 -0
  81. package/templates/default/data/taxonomy/platforms.yml +27 -0
  82. package/templates/default/data/taxonomy/stacks.yml +41 -0
  83. package/templates/default/grove.config.ts +1 -1
  84. package/templates/default/package.json +5 -4
  85. package/templates/default/public/icons/stacks/bun.svg +1 -0
  86. package/templates/default/public/icons/stacks/clojure.svg +1 -0
  87. package/templates/default/public/icons/stacks/clojurescript.svg +1 -0
  88. package/templates/default/public/icons/stacks/deno.svg +1 -0
  89. package/templates/default/public/icons/stacks/django.svg +1 -0
  90. package/templates/default/public/icons/stacks/docker.svg +1 -0
  91. package/templates/default/public/icons/stacks/go.svg +1 -0
  92. package/templates/default/public/icons/stacks/llm.svg +1 -0
  93. package/templates/default/public/icons/stacks/node.js.svg +1 -0
  94. package/templates/default/public/icons/stacks/sveltekit.svg +1 -0
  95. package/templates/default/public/icons/stacks/vue.svg +1 -0
  96. package/templates/default/public/llms-full.txt +197 -5
  97. package/templates/default/public/llms.txt +107 -6
  98. package/templates/default/public/sitemap.xml +63 -0
  99. package/templates/default/scripts/build-llms.mjs +123 -0
  100. package/templates/default/scripts/build-records-json.mjs +27 -0
  101. package/templates/default/scripts/build-sitemap.mjs +20 -0
  102. package/templates/default/scripts/cleanup-stale-records.mjs +20 -0
  103. package/templates/default/scripts/enrich-github-metadata.mjs +99 -0
  104. package/templates/default/scripts/fetch-icons.mjs +137 -0
  105. package/templates/default/scripts/migrate-legacy-to-schema-v1.mjs +86 -0
  106. package/templates/default/scripts/parse-legacy-readme.mjs +59 -0
  107. package/templates/default/scripts/refresh-records-activity.mjs +24 -0
  108. package/templates/default/scripts/repair-license-format.mjs +72 -0
  109. package/templates/default/scripts/report-cleanup-candidates.mjs +21 -0
  110. package/templates/default/scripts/seed-from-github.mjs +62 -0
  111. package/templates/default/scripts/sync-contributors.mjs +145 -0
  112. package/templates/default/scripts/sync-github-metadata.mjs +28 -0
  113. package/templates/default/scripts/validate-records.mjs +27 -0
  114. package/templates/default/src/data/records.ts +138 -51
  115. package/templates/default/src/pages/404.astro +64 -0
  116. package/templates/default/src/pages/{projects/[slug].astro → [slug]/[recordSlug].astro} +236 -66
  117. package/templates/default/src/pages/[slug]/index.astro +477 -0
  118. package/templates/default/src/pages/about.astro +165 -82
  119. package/templates/default/src/pages/apps/[recordSlug].astro +33 -0
  120. package/templates/default/src/pages/contributors.astro +184 -0
  121. package/templates/default/src/pages/index.astro +119 -162
  122. package/templates/default/src/pages/submit.astro +276 -114
  123. package/templates/default/wrangler.jsonc +28 -0
  124. package/LICENSE +0 -21
  125. package/src/components/AppsIndexRow.astro +0 -204
  126. package/src/components/AppsPagination.astro +0 -126
  127. package/templates/default/.astro/content.d.ts +0 -155
  128. package/templates/default/.astro/settings.json +0 -5
  129. package/templates/default/.astro/types.d.ts +0 -1
  130. package/templates/default/data/generated/apps.json +0 -1
  131. package/templates/default/data/generated/records.full.json +0 -8
  132. package/templates/default/data/generated/records.index.json +0 -7
  133. package/templates/default/data/generated/records.json +0 -8
  134. package/templates/default/data/generated/repo-stats.json +0 -1
  135. package/templates/default/data/generated/site-config.json +0 -31
  136. package/templates/default/dist/_astro/BaseLayout.Dz_HR-68.css +0 -1
  137. package/templates/default/dist/about/index.html +0 -165
  138. package/templates/default/dist/icons/platforms/android.svg +0 -1
  139. package/templates/default/dist/icons/platforms/apple-dark.svg +0 -1
  140. package/templates/default/dist/icons/platforms/apple-light.svg +0 -1
  141. package/templates/default/dist/icons/platforms/chrome.svg +0 -1
  142. package/templates/default/dist/icons/platforms/chromeos.svg +0 -5
  143. package/templates/default/dist/icons/platforms/desktop.svg +0 -4
  144. package/templates/default/dist/icons/platforms/embedded.svg +0 -5
  145. package/templates/default/dist/icons/platforms/ios.svg +0 -1
  146. package/templates/default/dist/icons/platforms/linux.svg +0 -1
  147. package/templates/default/dist/icons/platforms/macos.svg +0 -1
  148. package/templates/default/dist/icons/platforms/ubuntu.svg +0 -1
  149. package/templates/default/dist/icons/platforms/web.svg +0 -5
  150. package/templates/default/dist/icons/platforms/windows.svg +0 -6
  151. package/templates/default/dist/icons/stacks/android.svg +0 -1
  152. package/templates/default/dist/icons/stacks/apple-dark.svg +0 -1
  153. package/templates/default/dist/icons/stacks/apple-light.svg +0 -1
  154. package/templates/default/dist/icons/stacks/capacitor.svg +0 -14
  155. package/templates/default/dist/icons/stacks/dart.svg +0 -1
  156. package/templates/default/dist/icons/stacks/firebase.svg +0 -1
  157. package/templates/default/dist/icons/stacks/flutter.svg +0 -1
  158. package/templates/default/dist/icons/stacks/graphql.svg +0 -1
  159. package/templates/default/dist/icons/stacks/ionic.svg +0 -4
  160. package/templates/default/dist/icons/stacks/java.svg +0 -1
  161. package/templates/default/dist/icons/stacks/javascript.svg +0 -1
  162. package/templates/default/dist/icons/stacks/kotlin.svg +0 -1
  163. package/templates/default/dist/icons/stacks/mongodb.svg +0 -1
  164. package/templates/default/dist/icons/stacks/nodejs.svg +0 -1
  165. package/templates/default/dist/icons/stacks/python.svg +0 -1
  166. package/templates/default/dist/icons/stacks/react-native.svg +0 -1
  167. package/templates/default/dist/icons/stacks/react.svg +0 -1
  168. package/templates/default/dist/icons/stacks/rust.svg +0 -1
  169. package/templates/default/dist/icons/stacks/solidity.svg +0 -1
  170. package/templates/default/dist/icons/stacks/swift.svg +0 -1
  171. package/templates/default/dist/icons/stacks/tensorflow.svg +0 -1
  172. package/templates/default/dist/icons/stacks/typescript.svg +0 -1
  173. package/templates/default/dist/index.html +0 -55
  174. package/templates/default/dist/llms-full.txt +0 -14
  175. package/templates/default/dist/llms.txt +0 -11
  176. package/templates/default/dist/og-image.svg +0 -48
  177. package/templates/default/dist/projects/index.html +0 -61
  178. package/templates/default/dist/robots.txt +0 -53
  179. package/templates/default/dist/sitemap.xml +0 -21
  180. package/templates/default/dist/submit/index.html +0 -69
  181. package/templates/default/node_modules/.bin/astro +0 -21
  182. package/templates/default/node_modules/.bin/astro-check +0 -21
  183. package/templates/default/node_modules/.bin/grove +0 -21
  184. package/templates/default/node_modules/.bin/jiti +0 -21
  185. package/templates/default/node_modules/.bin/prettier +0 -21
  186. package/templates/default/node_modules/.bin/rollup +0 -21
  187. package/templates/default/node_modules/.bin/tsc +0 -21
  188. package/templates/default/node_modules/.bin/tsserver +0 -21
  189. package/templates/default/node_modules/.bin/tsx +0 -21
  190. package/templates/default/node_modules/.bin/vite +0 -21
  191. package/templates/default/node_modules/.bin/yaml +0 -21
  192. package/templates/default/node_modules/.vite/deps/_metadata.json +0 -79
  193. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +0 -433
  194. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +0 -7
  195. package/templates/default/node_modules/.vite/deps/astro___aria-query.js +0 -5
  196. package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
  197. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +0 -5
  198. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
  199. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +0 -9
  200. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
  201. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -241
  202. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -7
  203. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +0 -1592
  204. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +0 -7
  205. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +0 -30
  206. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +0 -7
  207. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +0 -186
  208. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +0 -7
  209. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +0 -48
  210. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +0 -7
  211. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +0 -6779
  212. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +0 -7
  213. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +0 -33
  214. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +0 -7
  215. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +0 -88
  216. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +0 -7
  217. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +0 -72
  218. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +0 -7
  219. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +0 -3757
  220. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +0 -7
  221. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +0 -54
  222. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +0 -7
  223. package/templates/default/node_modules/.vite/deps/package.json +0 -3
  224. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +0 -213
  225. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +0 -7
  226. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +0 -525
  227. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +0 -7
  228. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +0 -1020
  229. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +0 -7
  230. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +0 -147
  231. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +0 -7
  232. package/templates/default/src/components/layout/BaseLayout.astro +0 -47
  233. package/templates/default/src/components/layout/Container.astro +0 -18
  234. package/templates/default/src/components/layout/Footer.astro +0 -27
  235. package/templates/default/src/components/layout/Header.astro +0 -43
  236. package/templates/default/src/lib/markdown.ts +0 -171
  237. package/templates/default/src/pages/projects/index.astro +0 -193
@@ -1,181 +1,199 @@
1
1
  /**
2
- * Curated lenses for the list / discovery page.
2
+ * Curated lenses for the list page.
3
3
  *
4
- * A lens is a single-select view that translates into a (possibly
5
- * empty) URL search-param filter. The 6 lenses below cover the
6
- * "what should I look at next?" mental shortcut:
4
+ * Two flavors:
5
+ * - "label-based": maps to a single `labels` value (e.g. new / hot / mature).
6
+ * No extra curation needed every item already has a label.
7
+ * - "curator-assigned": maps to a `lenses` value on the item. The 5 anchor
8
+ * items get these populated. Other items will simply not match.
7
9
  *
8
- * - "all" — no filter
9
- * - "hot" — recent attention: stars 1000 OR pushed within 30d
10
- * - "new" — fresh entries: pushed within 7d OR curated "new" label
11
- * - "mature" — long-running projects: curated tier AND pushed > 365d ago
12
- * - "featured" — curated "featured" label
13
- * - "needs-review" — cleanup candidate (stale / archived / etc.)
10
+ * The lens id is what shows up in the URL as `?lens=...`. For
11
+ * label-based lenses, the lens id is the same as the label.
14
12
  *
15
- * The lens id is what shows up in the URL as `?lens=...`. Filter
16
- * resolution happens in `lib/search.ts` (`filterApps`).
17
- *
18
- * Keep this list in sync with `lib/search.ts` and any UI tabs.
13
+ * The 6 lenses in `PRIMARY_LENSES` are rendered as top-row tabs. The rest
14
+ * remain in the union type for URL deep-linking and future use, but are
15
+ * not shown in the UI by default.
19
16
  */
20
- const DAY_MS = 86_400_000;
21
- function daysSince(iso) {
22
- if (!iso)
23
- return Infinity;
24
- const d = new Date(iso);
25
- if (Number.isNaN(d.valueOf()))
26
- return Infinity;
27
- return (Date.now() - d.valueOf()) / DAY_MS;
28
- }
29
- function hasLabel(record, label) {
30
- return record.curation?.labels?.includes(label) ?? false;
31
- }
32
- const CLEANUP_STATUSES = [
33
- "stale",
34
- "inactive",
35
- "archived",
36
- "unavailable",
37
- "needs_review",
38
- ];
39
- function isCleanupCandidate(record) {
40
- if (record.health?.cleanupCandidate)
41
- return true;
42
- const status = record.health?.status;
43
- if (!status)
44
- return false;
45
- return CLEANUP_STATUSES.includes(status);
46
- }
47
17
  export const LENSES = [
18
+ { id: "all", label: "All items", description: "Every item in the directory", toParams: () => ({}) },
48
19
  {
49
- id: "all",
50
- label: "All",
51
- description: "Every record in the directory.",
52
- match: () => true,
20
+ id: "new",
21
+ label: "Recently added",
22
+ description: "Apps recently added to the directory",
23
+ toParams: () => ({ label: "new" }),
53
24
  },
54
25
  {
55
26
  id: "hot",
56
27
  label: "Trending",
57
- description: "Records with recent attention stars ≥ 1000 or pushed within 30 days.",
58
- match: (r) => {
59
- const stars = r.github?.stars ?? 0;
60
- const pushed = daysSince(r.github?.pushedAt);
61
- return stars >= 1000 || pushed <= 30;
62
- },
63
- },
64
- {
65
- id: "new",
66
- label: "Recently added",
67
- description: "Fresh entries — pushed within 7 days or curated with the “new” label.",
68
- match: (r) => {
69
- const pushed = daysSince(r.github?.pushedAt);
70
- return pushed <= 7 || hasLabel(r, "new");
71
- },
28
+ description: "Apps with recent activity and attention",
29
+ toParams: () => ({ label: "hot" }),
72
30
  },
73
31
  {
74
32
  id: "mature",
75
33
  label: "Established",
76
- description: "Long-running projects curated tier and last push > 365 days ago.",
77
- match: (r) => {
78
- const tier = r.health?.tier;
79
- const pushed = daysSince(r.github?.pushedAt);
80
- return tier === "curated" && pushed > 365;
81
- },
34
+ description: "Long-running projects with stable history",
35
+ toParams: () => ({ label: "mature" }),
36
+ },
37
+ {
38
+ id: "production-like",
39
+ label: "Production-like",
40
+ description: "Real items, not toy projects",
41
+ toParams: () => ({ lens: "production-like" }),
42
+ },
43
+ {
44
+ id: "good-to-learn",
45
+ label: "Good to learn",
46
+ description: "Readable codebases with useful patterns",
47
+ toParams: () => ({ lens: "good-to-learn" }),
48
+ },
49
+ // Secondary (URL-only, not rendered as tabs)
50
+ {
51
+ id: "beginner-friendly",
52
+ label: "Beginner friendly",
53
+ description: "Smaller, readable, easier to understand",
54
+ toParams: () => ({ lens: "beginner-friendly" }),
55
+ },
56
+ {
57
+ id: "contribution-ready",
58
+ label: "Contribution ready",
59
+ description: "Clear issues, active maintainers, license, contribution docs",
60
+ toParams: () => ({ lens: "contribution-ready" }),
82
61
  },
83
62
  {
84
- id: "featured",
85
- label: "Featured",
86
- description: "Records curated with the “featured” label.",
87
- match: (r) => hasLabel(r, "featured"),
63
+ id: "launches",
64
+ label: "Launches",
65
+ description: "Recently launched OSS items seeking feedback",
66
+ toParams: () => ({ lens: "launches" }),
88
67
  },
89
68
  {
90
- id: "needs-review",
91
- label: "Needs review",
92
- description: "Cleanup candidates stale, archived, or unmaintained.",
93
- match: (r) => isCleanupCandidate(r),
69
+ id: "actively-developed",
70
+ label: "Active",
71
+ description: "Apps with recent commits, releases, and issue activity",
72
+ toParams: () => ({ status: "active" }),
73
+ },
74
+ {
75
+ id: "needs-maintainer",
76
+ label: "Needs maintainer",
77
+ description: "Useful items that need help",
78
+ toParams: () => ({ status: "stale,quiet" }),
94
79
  },
95
80
  ];
96
81
  /**
97
- * The lenses rendered as top-row tabs on the list page. Order
98
- * matters — left to right. Keep this in sync with what the design
99
- * calls for; URL deep-linking accepts any `LensId` even if it's not
100
- * in the primary row.
82
+ * The 6 lenses shown as top-row tabs on /items. Order matters — left to right.
83
+ * Keep this list aligned with what the design calls for: All, signal lenses,
84
+ * and the two curator-assigned lenses that have any matches.
101
85
  */
102
86
  export const PRIMARY_LENSES = [
103
87
  "all",
104
- "hot",
105
88
  "new",
89
+ "hot",
106
90
  "mature",
107
- "featured",
108
- "needs-review",
91
+ "production-like",
92
+ "good-to-learn",
109
93
  ];
110
94
  export function lensById(id) {
111
95
  if (!id)
112
96
  return undefined;
113
97
  return LENSES.find((l) => l.id === id);
114
98
  }
99
+ /**
100
+ * Check whether the current URL search params match a given lens's
101
+ * implied filter state. Used to highlight the active tab when a
102
+ * deep-link uses the underlying filter key (e.g. ?label=hot) rather
103
+ * than the abstract lens id (?lens=hot).
104
+ */
105
+ export function isLensActive(lensId, sp) {
106
+ if (lensId === "all") {
107
+ // "All" is active when no lens-shaped filter is present.
108
+ return !lensFromSearchParams(sp) && !sp.get("label") && !sp.get("status");
109
+ }
110
+ const def = lensById(lensId);
111
+ if (!def)
112
+ return false;
113
+ const target = def.toParams();
114
+ for (const [k, v] of Object.entries(target)) {
115
+ if (Array.isArray(v)) {
116
+ if (sp.getAll(k).length !== v.length)
117
+ return false;
118
+ for (const item of v)
119
+ if (!sp.getAll(k).includes(item))
120
+ return false;
121
+ }
122
+ else {
123
+ if (sp.get(k) !== v)
124
+ return false;
125
+ }
126
+ }
127
+ return true;
128
+ }
115
129
  export function isPrimaryLens(id) {
116
130
  if (!id)
117
131
  return false;
118
132
  return PRIMARY_LENSES.includes(id);
119
133
  }
120
134
  /**
121
- * Resolve the active lens from URL search params. Returns the empty
122
- * string when no `lens=...` is set, so callers can treat both
123
- * "absent" and "unknown" identically.
135
+ * Turn a lens's `toParams()` output into a URLSearchParams string
136
+ * (with the existing query preserved, then lens params merged on top).
124
137
  */
125
- export function lensFromSearchParams(params) {
126
- const v = params.get("lens");
127
- if (!v)
138
+ export function lensToQuery(id, current) {
139
+ if (!id || id === "all")
128
140
  return "";
129
- if (LENSES.some((l) => l.id === v))
130
- return v;
131
- return "";
141
+ const def = lensById(id);
142
+ if (!def)
143
+ return "";
144
+ const next = new URLSearchParams(current);
145
+ for (const [k, v] of Object.entries(def.toParams())) {
146
+ if (Array.isArray(v)) {
147
+ next.delete(k);
148
+ for (const item of v)
149
+ next.append(k, item);
150
+ }
151
+ else {
152
+ next.set(k, v);
153
+ }
154
+ }
155
+ return next.toString();
132
156
  }
133
157
  /**
134
- * `true` when the URL search params currently reflect this lens's
135
- * filter shape. The "all" lens is the implicit "no filter" state —
136
- * it's active only when no other lens-shaped params are present.
158
+ * Read a `?lens=...` value from URL search params.
137
159
  */
138
- export function isLensActive(lensId, params) {
139
- if (lensId === "all") {
140
- return lensFromSearchParams(params) === "" && !params.get("label");
141
- }
142
- return lensFromSearchParams(params) === lensId;
160
+ export function lensFromSearchParams(sp) {
161
+ const v = sp.get("lens");
162
+ if (!v)
163
+ return null;
164
+ if (LENSES.some((l) => l.id === v))
165
+ return v;
166
+ return null;
143
167
  }
144
168
  /**
145
- * Build a `href` for a lens tab. Selecting a lens resets the other
146
- * "view" params (label, page) and applies only the chosen lens's
147
- * filter. Other filters (q, stack, platform, category, sort) are
148
- * preserved.
149
- *
150
- * `pathPrefix` is the URL root for the list page — typically
151
- * `/projects` for the project-directory blueprint, `/resources` for
152
- * resource-hub, `/entities` for ecosystem-map. Defaults to `/`.
169
+ * Build a `href` for a lens tab. Per the single-select-view rule,
170
+ * clicking a lens resets the "view" params (lens, label, status, page)
171
+ * and applies only the chosen lens's params. Stack/Platform/Category/
172
+ * License/q/sort/density filters are preserved.
153
173
  */
154
- export function hrefForLens(lensId, params, pathPrefix = "/") {
155
- const sp = new URLSearchParams(params);
174
+ export function hrefForLens(lensId, current, pathPrefix = "/items") {
175
+ const sp = new URLSearchParams(current);
156
176
  sp.delete("lens");
157
177
  sp.delete("label");
178
+ sp.delete("status");
158
179
  sp.delete("page");
159
180
  if (lensId && lensId !== "all") {
160
- sp.set("lens", lensId);
181
+ const def = lensById(lensId);
182
+ if (def) {
183
+ for (const [k, v] of Object.entries(def.toParams())) {
184
+ if (Array.isArray(v)) {
185
+ sp.delete(k);
186
+ for (const item of v)
187
+ sp.append(k, item);
188
+ }
189
+ else {
190
+ sp.set(k, v);
191
+ }
192
+ }
193
+ }
161
194
  }
162
195
  const qs = sp.toString();
163
- const base = pathPrefix.replace(/\/$/, "");
164
- return qs ? `${base}?${qs}` : base || "/";
165
- }
166
- /**
167
- * Apply the lens filter to a record array. When `lensId` is
168
- * `""` (no lens) or `"all"`, returns the input unchanged.
169
- *
170
- * Type-parameterized so the same helper works on `ProjectRecord[]`
171
- * and on the lighter index payload that list pages ship.
172
- */
173
- export function applyLens(records, lensId) {
174
- if (!lensId || lensId === "all")
175
- return records;
176
- const def = lensById(lensId);
177
- if (!def)
178
- return records;
179
- return records.filter((r) => def.match(r));
196
+ const base = pathPrefix.replace(/\/$/, "") || "/";
197
+ return qs ? `${base}?${qs}` : base;
180
198
  }
181
199
  //# sourceMappingURL=lenses.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lenses.js","sourceRoot":"","sources":["../../src/lib/lenses.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lenses.js","sourceRoot":"","sources":["../../src/lib/lenses.ts"],"names":[],"mappings":"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"}
@@ -1,30 +1,20 @@
1
1
  /**
2
2
  * Parse a GitHub repo URL into its owner and repo components.
3
- *
4
- * Returns `null` (not `{ owner: null, repo: null }`) if the URL
5
- * doesn't match the github.com path shape — this lets callers
6
- * distinguish "no URL" from "URL without owner" with a single
7
- * null check.
3
+ * Returns nulls if the URL doesn't match github.com/{owner}/{repo}.
8
4
  *
9
5
  * Used in:
10
- * - ItemCard / AppCard avatar fallback (owner avatar via GitHub
11
- * avatars API).
12
- * - Detail page (showing owner + repo name in breadcrumb / header).
13
- * - Anywhere we need to display "@owner / repo" or link to the owner.
6
+ * - ItemCard avatar fallback (owner avatar via GitHub avatars
7
+ * API; component name retained for V1 published API stability)
8
+ * - App detail page (showing owner + repo name in breadcrumb / header)
9
+ * - Anywhere we need to display "@owner / repo" or link to the owner
14
10
  */
15
- export declare function getOwnerAndRepoFromRepoUrl(repoUrl: string | null | undefined): {
16
- owner: string;
17
- repo: string;
18
- } | null;
11
+ export declare function getOwnerAndRepoFromRepoUrl(repoUrl: string): {
12
+ owner: string | null;
13
+ repo: string | null;
14
+ };
19
15
  /**
20
- * Build a GitHub avatar URL for a given owner. Defaults to size 80
21
- * which is the smallest "high quality" size in GitHub's avatar CDN.
22
- *
23
- * Returns a string (not nullable) so callers can plug the result
24
- * straight into an `src` attribute. The avatar endpoint always
25
- * returns a placeholder image for unknown owners, so the only
26
- * reason to check `null` upstream is when you specifically want
27
- * to render a different fallback (e.g. initials).
16
+ * Build a GitHub avatar URL for a given owner.
17
+ * Returns null if owner is null/empty.
28
18
  */
29
- export declare function getOwnerAvatarUrl(owner: string, size?: number): string;
19
+ export declare function getOwnerAvatarUrl(owner: string | null, size?: number): string | null;
30
20
  //# sourceMappingURL=repo.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/lib/repo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACjC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQxC;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM,CAOlE"}
1
+ {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/lib/repo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAUA;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,GAAE,MAAW,GAAG,MAAM,GAAG,IAAI,CAGxF"}
package/dist/lib/repo.js CHANGED
@@ -1,44 +1,32 @@
1
1
  /**
2
2
  * Parse a GitHub repo URL into its owner and repo components.
3
- *
4
- * Returns `null` (not `{ owner: null, repo: null }`) if the URL
5
- * doesn't match the github.com path shape — this lets callers
6
- * distinguish "no URL" from "URL without owner" with a single
7
- * null check.
3
+ * Returns nulls if the URL doesn't match github.com/{owner}/{repo}.
8
4
  *
9
5
  * Used in:
10
- * - ItemCard / AppCard avatar fallback (owner avatar via GitHub
11
- * avatars API).
12
- * - Detail page (showing owner + repo name in breadcrumb / header).
13
- * - Anywhere we need to display "@owner / repo" or link to the owner.
6
+ * - ItemCard avatar fallback (owner avatar via GitHub avatars
7
+ * API; component name retained for V1 published API stability)
8
+ * - App detail page (showing owner + repo name in breadcrumb / header)
9
+ * - Anywhere we need to display "@owner / repo" or link to the owner
14
10
  */
15
11
  export function getOwnerAndRepoFromRepoUrl(repoUrl) {
16
- if (!repoUrl || !repoUrl.includes("github.com/"))
17
- return null;
12
+ if (!repoUrl || !repoUrl.includes("github.com/")) {
13
+ return { owner: null, repo: null };
14
+ }
18
15
  const m = repoUrl.match(/github\.com\/([^/]+)\/([^/?#]+)/);
19
16
  if (!m)
20
- return null;
17
+ return { owner: null, repo: null };
21
18
  return {
22
19
  owner: m[1],
23
20
  repo: m[2].replace(/\.git$/, ""),
24
21
  };
25
22
  }
26
23
  /**
27
- * Build a GitHub avatar URL for a given owner. Defaults to size 80
28
- * which is the smallest "high quality" size in GitHub's avatar CDN.
29
- *
30
- * Returns a string (not nullable) so callers can plug the result
31
- * straight into an `src` attribute. The avatar endpoint always
32
- * returns a placeholder image for unknown owners, so the only
33
- * reason to check `null` upstream is when you specifically want
34
- * to render a different fallback (e.g. initials).
24
+ * Build a GitHub avatar URL for a given owner.
25
+ * Returns null if owner is null/empty.
35
26
  */
36
27
  export function getOwnerAvatarUrl(owner, size = 80) {
37
- if (!owner) {
38
- // Defensive: callers may pass a possibly-empty string. Return the
39
- // generic GitHub identicon so an `<img>` doesn't 404.
40
- return `https://avatars.githubusercontent.com/identicon?v=4&s=${size}`;
41
- }
28
+ if (!owner)
29
+ return null;
42
30
  return `https://avatars.githubusercontent.com/${owner}?v=4&s=${size}`;
43
31
  }
44
32
  //# sourceMappingURL=repo.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/lib/repo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAAkC;IAElC,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9D,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IAC3D,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QACX,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,IAAI,GAAG,EAAE;IACxD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,kEAAkE;QAClE,sDAAsD;QACtD,OAAO,yDAAyD,IAAI,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,yCAAyC,KAAK,UAAU,IAAI,EAAE,CAAC;AACxE,CAAC"}
1
+ {"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/lib/repo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAAe;IAIxD,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACjD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACrC,CAAC;IACD,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IAC3D,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC3C,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QACX,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAoB,EAAE,OAAe,EAAE;IACvE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,yCAAyC,KAAK,UAAU,IAAI,EAAE,CAAC;AACxE,CAAC"}
@@ -1,11 +1,11 @@
1
- /**
2
- * Score-dimension utilities.
3
- *
4
- * Operates on the `scores` field of a `ProjectRecord` (or any
5
- * object that has the same shape — defined as `Score` in
6
- * `@grove-dev/core`). All helpers are dependency-free.
7
- */
8
- import type { Score } from "@grove-dev/core";
1
+ type AppScores = {
2
+ activity?: number;
3
+ maturity?: number;
4
+ learning?: number;
5
+ contribution?: number;
6
+ docs?: number;
7
+ overall?: number;
8
+ };
9
9
  /**
10
10
  * Format a single score (0-100) into a 0-5 "tier" used by the UI:
11
11
  *
@@ -15,8 +15,8 @@ import type { Score } from "@grove-dev/core";
15
15
  * 60-79 → 3 (high)
16
16
  * 80-100 → 4 (very high)
17
17
  *
18
- * The list page renders each score as a 4-cell bar; this gives
19
- * the visual a stable shape regardless of the underlying number.
18
+ * The list page renders each score as a 4-cell bar; this gives the
19
+ * visual a stable shape regardless of the underlying number.
20
20
  */
21
21
  export declare function scoreTier(n: number): 0 | 1 | 2 | 3 | 4;
22
22
  /** Compact textual label for a score tier (used in tooltips). */
@@ -24,15 +24,14 @@ export declare function scoreTierLabel(n: number): string;
24
24
  /** Short numeric label used inline next to a score bar, e.g. "82". */
25
25
  export declare function scoreLabel(n: number | undefined): string;
26
26
  /** Order of score dimensions, used by both list and detail. */
27
- export declare const SCORE_DIMENSIONS: (keyof Score)[];
27
+ export declare const SCORE_DIMENSIONS: (keyof AppScores)[];
28
28
  /** Human-readable label for a score dimension. */
29
- export declare const SCORE_LABELS: Record<keyof Score, string>;
29
+ export declare const SCORE_LABELS: Record<keyof AppScores, string>;
30
30
  /**
31
31
  * Reasoning copy per dimension, surfaced in the detail page so the
32
32
  * numbers don't feel magical. These are intentionally short — the
33
- * curation notes on each record carry the longer story.
33
+ * curation.notes on each item carries the longer story.
34
34
  */
35
- export declare const SCORE_REASONING: Record<keyof Score, string>;
36
- /** Re-export the `Score` type for callers that only need the lib API. */
37
- export type { Score };
35
+ export declare const SCORE_REASONING: Record<keyof AppScores, string>;
36
+ export {};
38
37
  //# sourceMappingURL=scores.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scores.d.ts","sourceRoot":"","sources":["../../src/lib/scores.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAMtD;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAahD;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGxD;AAED,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,EAAE,CAAC,MAAM,KAAK,CAAC,EAO3C,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,KAAK,EAAE,MAAM,CAOpD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,KAAK,EAAE,MAAM,CAavD,CAAC;AAEF,yEAAyE;AACzE,YAAY,EAAE,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"scores.d.ts","sourceRoot":"","sources":["../../src/lib/scores.ts"],"names":[],"mappings":"AAAA,KAAK,SAAS,GAAG;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAMtD;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAahD;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGxD;AAED,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,EAAE,CAAC,MAAM,SAAS,CAAC,EAO/C,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,SAAS,EAAE,MAAM,CAOxD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,SAAS,EAAE,MAAM,CAa3D,CAAC"}
@@ -1,10 +1,3 @@
1
- /**
2
- * Score-dimension utilities.
3
- *
4
- * Operates on the `scores` field of a `ProjectRecord` (or any
5
- * object that has the same shape — defined as `Score` in
6
- * `@grove-dev/core`). All helpers are dependency-free.
7
- */
8
1
  /**
9
2
  * Format a single score (0-100) into a 0-5 "tier" used by the UI:
10
3
  *
@@ -14,8 +7,8 @@
14
7
  * 60-79 → 3 (high)
15
8
  * 80-100 → 4 (very high)
16
9
  *
17
- * The list page renders each score as a 4-cell bar; this gives
18
- * the visual a stable shape regardless of the underlying number.
10
+ * The list page renders each score as a 4-cell bar; this gives the
11
+ * visual a stable shape regardless of the underlying number.
19
12
  */
20
13
  export function scoreTier(n) {
21
14
  if (n < 20)
@@ -70,7 +63,7 @@ export const SCORE_LABELS = {
70
63
  /**
71
64
  * Reasoning copy per dimension, surfaced in the detail page so the
72
65
  * numbers don't feel magical. These are intentionally short — the
73
- * curation notes on each record carry the longer story.
66
+ * curation.notes on each item carries the longer story.
74
67
  */
75
68
  export const SCORE_REASONING = {
76
69
  activity: "Recent commits, open and merged PRs, issue response time, release cadence.",
@@ -78,6 +71,6 @@ export const SCORE_REASONING = {
78
71
  learning: "Codebase readability, useful architecture patterns, comment quality, examples.",
79
72
  contribution: "Open issues, recent merged PRs from new contributors, maintainer responsiveness, contribution guide.",
80
73
  docs: "README quality, architecture docs, contributing guide, examples, code comments.",
81
- overall: "Composite judgment — how likely this record is to satisfy a curious developer.",
74
+ overall: "Composite judgment — how likely this item is to satisfy a curious developer.",
82
75
  };
83
76
  //# sourceMappingURL=scores.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scores.js","sourceRoot":"","sources":["../../src/lib/scores.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,OAAO,CAAC,CAAC;AACX,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,QAAQ,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,KAAK,CAAC;YACJ,OAAO,UAAU,CAAC;QACpB,KAAK,CAAC;YACJ,OAAO,KAAK,CAAC;QACf,KAAK,CAAC;YACJ,OAAO,QAAQ,CAAC;QAClB,KAAK,CAAC;YACJ,OAAO,MAAM,CAAC;QAChB,KAAK,CAAC;YACJ,OAAO,WAAW,CAAC;IACvB,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,UAAU,CAAC,CAAqB;IAC9C,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACtC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AAClC,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC/C,UAAU;IACV,UAAU;IACV,UAAU;IACV,cAAc;IACd,MAAM;IACN,SAAS;CACV,CAAC;AAEF,kDAAkD;AAClD,MAAM,CAAC,MAAM,YAAY,GAAgC;IACvD,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,YAAY,EAAE,cAAc;IAC5B,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAgC;IAC1D,QAAQ,EACN,4EAA4E;IAC9E,QAAQ,EACN,iFAAiF;IACnF,QAAQ,EACN,gFAAgF;IAClF,YAAY,EACV,sGAAsG;IACxG,IAAI,EACF,iFAAiF;IACnF,OAAO,EACL,gFAAgF;CACnF,CAAC"}
1
+ {"version":3,"file":"scores.js","sourceRoot":"","sources":["../../src/lib/scores.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,OAAO,CAAC,CAAC;AACX,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,QAAQ,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,KAAK,CAAC;YACJ,OAAO,UAAU,CAAC;QACpB,KAAK,CAAC;YACJ,OAAO,KAAK,CAAC;QACf,KAAK,CAAC;YACJ,OAAO,QAAQ,CAAC;QAClB,KAAK,CAAC;YACJ,OAAO,MAAM,CAAC;QAChB,KAAK,CAAC;YACJ,OAAO,WAAW,CAAC;IACvB,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,UAAU,CAAC,CAAqB;IAC9C,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACtC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AAClC,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,gBAAgB,GAAwB;IACnD,UAAU;IACV,UAAU;IACV,UAAU;IACV,cAAc;IACd,MAAM;IACN,SAAS;CACV,CAAC;AAEF,kDAAkD;AAClD,MAAM,CAAC,MAAM,YAAY,GAAoC;IAC3D,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,YAAY,EAAE,cAAc;IAC5B,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAoC;IAC9D,QAAQ,EACN,4EAA4E;IAC9E,QAAQ,EACN,iFAAiF;IACnF,QAAQ,EACN,gFAAgF;IAClF,YAAY,EACV,sGAAsG;IACxG,IAAI,EACF,iFAAiF;IACnF,OAAO,EACL,8EAA8E;CACjF,CAAC"}