diff --git a/.github/workflows/build-preview.yml b/.github/workflows/build-preview.yml
index da5083df2..04c7830db 100644
--- a/.github/workflows/build-preview.yml
+++ b/.github/workflows/build-preview.yml
@@ -28,6 +28,8 @@ jobs:
- name: Build site
run: pnpm --filter docs build
+ env:
+ GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload build artifact
uses: actions/upload-artifact@v4
diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml
index 9697e305a..db119f9d0 100644
--- a/.github/workflows/deploy-prod.yml
+++ b/.github/workflows/deploy-prod.yml
@@ -30,6 +30,8 @@ jobs:
- name: Build site
run: pnpm --filter docs build
+ env:
+ GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to Cloudflare Pages
uses: AdrianGonz97/refined-cf-pages-action@v1
diff --git a/docs/src/routes/docs/showcase/+page.md b/docs/src/routes/docs/showcase/+page.md
index 14971a20a..5cf35c909 100644
--- a/docs/src/routes/docs/showcase/+page.md
+++ b/docs/src/routes/docs/showcase/+page.md
@@ -1,33 +1,26 @@
# Showcase
-
- {#each sites as site}
-
-
- {site.name}
-
- {#if site.description}
-
{site.description}
- {/if}
-
- {#if site.source}
-
- {/if}
- {#if site.url}
-
- {/if}
-
-
- {/each}
-
-
-[More](https://github.com/techniq/layerchart/network/dependents)
+## Featured
+
+
+
+## [Supporters](https://github.com/techniq/layerchart?tab=readme-ov-file#sponsors)
+
+
+
+## Popular
+
+
+
+## Other
+
+
+
+## [More](https://github.com/techniq/layerchart/network/dependents)
diff --git a/docs/src/routes/docs/showcase/Showcase.svelte b/docs/src/routes/docs/showcase/Showcase.svelte
new file mode 100644
index 000000000..bc8276d1e
--- /dev/null
+++ b/docs/src/routes/docs/showcase/Showcase.svelte
@@ -0,0 +1,48 @@
+
+
+
+ {#each sites as site}
+
+
+ {site.name ?? site.reponame}
+
+ {#if site.description}
+
{site.description}
+ {/if}
+
+ {#if site.stars}
+
+
+ {site.stars.toLocaleString()}
+
+ {/if}
+ {#if site.repourl}
+
+ {/if}
+ {#if site.homepageurl}
+
+ {/if}
+
+
+ {/each}
+
diff --git a/docs/src/routes/docs/showcase/dependency.remote.ts b/docs/src/routes/docs/showcase/dependency.remote.ts
new file mode 100644
index 000000000..7bac6fe39
--- /dev/null
+++ b/docs/src/routes/docs/showcase/dependency.remote.ts
@@ -0,0 +1,206 @@
+import { prerender } from '$app/server';
+import { env } from '$env/dynamic/private';
+
+const POPULAR_STAR_THRESHOLD = 100;
+const OTHER_STAR_THRESHOLD = 10;
+
+export type Dependent = {
+ name?: string;
+ reponame?: string;
+ owner?: string;
+ description: string;
+ repourl?: string;
+ homepageurl?: string;
+ stars?: number;
+};
+
+export const getDependents = prerender(async () => {
+
+ const featuredSites: Dependent[] = [
+ {
+ name: 'Github Analysis',
+ description: 'Analyze your GitHub repositories and NPM packages',
+ repourl: 'https://github.com/techniq/github-analysis',
+ homepageurl: 'https://github.techniq.dev'
+ },
+ {
+ name: 'Strava Analysis',
+ description: 'Analyze your Strava activities',
+ repourl: 'https://github.com/techniq/strava-analysis',
+ homepageurl: 'https://strava.techniq.dev'
+ },
+ {
+ name: 'Zipline AI',
+ description: 'Features, context and embeddings for real-time AI/ML',
+ repourl: 'https://zipline.ai/',
+ homepageurl: 'https://github.com/zipline-ai'
+ }
+ ];
+
+ const supporterSites: Dependent[] = [
+ // could this be automated? pull sponsers, check dependents by owner === sponser, and add them here?
+ {
+ name: 'Tenzir',
+ description: 'Open source data pipelines for security teams',
+ repourl: 'https://github.com/tenzir',
+ homepageurl: 'https://tenzir.com/'
+ },
+ {
+ name: 'shadcn-svelte',
+ description: 'shadcn/ui, but for Svelte.',
+ repourl: 'https://github.com/huntabyte/shadcn-svelte',
+ homepageurl: 'https://shadcn-svelte.com/'
+ },
+ {
+ name: 'Sky Zoo',
+ description: 'Bluesky stats',
+ repourl: 'https://skyzoo.blue/',
+ homepageurl: 'https://github.com/jycouet/jyc.dev'
+ }
+ ];
+
+ // These do not have a GH repo, but will be promoted by adding to the top of popular sites.
+ const highlightedSites: Dependent[] = [
+ {
+ name: 'GEO audit',
+ description: 'GEO / AI audit that tracks your visibility impact',
+ homepageurl: 'https://www.geoaud.it/'
+ },
+ {
+ name: 'RetireNumber',
+ description: 'Get a second opinion on your retirement number.',
+ homepageurl: 'https://retirenumber.com/'
+ },
+ {
+ name: 'PowerOutage.com',
+ description: 'Tracks, records, and aggregates power outage data across the World',
+ homepageurl: 'https://poweroutage.com/'
+ },
+ {
+ name: 'IOM UN Migration: Ukraine Regional Response',
+ description: 'Needs, Intentions, and Border Crossings',
+ homepageurl:
+ 'https://dtm.iom.int/online-interactive-resources/ukraine-regional-response-dashboard/index.html'
+ },
+ {
+ name: 'Loyola Chicago: Center for Criminal Justice',
+ description: 'The First Year of the Pretrial Fairness Act',
+ homepageurl: 'https://pfa-1yr.loyolaccj.org/'
+ },
+ {
+ name: 'ftop',
+ description: 'Comperative performance metrics for Fortnite islands',
+ homepageurl: 'https://ftop.app/'
+ },
+ {
+ name: 'Nocturne',
+ description: 'A next-generation platform for diabetes management',
+ homepageurl: 'https://nocturne.app/'
+ }
+ ];
+
+ const githubHeaders: Record = {
+ Accept: 'application/vnd.github.v3+json',
+ 'User-Agent': 'LayerChart docs'
+ };
+
+ if (env.GITHUB_API_TOKEN) {
+ const prefix = env.GITHUB_API_TOKEN.startsWith('ghp_') ? 'token' : 'Bearer';
+ githubHeaders['Authorization'] = `${prefix} ${env.GITHUB_API_TOKEN}`;
+ }
+
+ const totalStart = performance.now();
+
+ // Step 1: Find repos with "layerchart" in package.json via code search
+ // NOTE: Code search API has a strict rate limit, so pages are fetched sequentially
+ const repoSet = new Set();
+ let page = 1;
+ const perPage = 100;
+
+ const step1Start = performance.now();
+ while (true) {
+ const searchUrl = `https://api.github.com/search/code?q=${encodeURIComponent('"layerchart" filename:package.json')}&per_page=${perPage}&page=${page}`;
+ const res = await fetch(searchUrl, { headers: githubHeaders });
+
+ if (!res.ok) {
+ console.error(`GitHub code search failed: ${res.status} ${res.statusText}`);
+ break;
+ }
+
+ const data = await res.json();
+ const items = data.items ?? [];
+
+ for (const item of items) {
+ const fullName = item.repository?.full_name;
+ if (fullName && fullName !== 'techniq/layerchart') {
+ repoSet.add(fullName);
+ }
+ }
+
+ if (items.length < perPage || repoSet.size >= data.total_count) break;
+ page++;
+ }
+ console.log(
+ `[getDependents] Step 1 - Code search: ${((performance.now() - step1Start) / 1000).toFixed(2)}s (${repoSet.size} repos found, ${page} pages)`
+ );
+
+ // Step 2: Batch-fetch repo details via GitHub GraphQL (parallel)
+ const step2Start = performance.now();
+ const repos = [...repoSet];
+ const batchSize = 50;
+
+ const batchPromises = [];
+ for (let i = 0; i < repos.length; i += batchSize) {
+ const batch = repos.slice(i, i + batchSize);
+ const fragments = batch
+ .map((fullName, idx) => {
+ const [owner, name] = fullName.split('/');
+ return `repo${idx}: repository(owner: ${JSON.stringify(owner)}, name: ${JSON.stringify(name)}) { stargazerCount description homepageUrl url owner { login } name }`;
+ })
+ .join('\n');
+
+ batchPromises.push(
+ fetch('https://api.github.com/graphql', {
+ method: 'POST',
+ headers: githubHeaders,
+ body: JSON.stringify({ query: `{ ${fragments} }` })
+ }).then(async (res) => {
+ if (!res.ok) return [];
+ const { data } = await res.json();
+ if (!data) return [];
+ return Object.values(data)
+ .filter(Boolean)
+ .map((repo: any) => ({
+ owner: repo.owner.login,
+ reponame: repo.name,
+ description: repo.description || null,
+ repourl: repo.url,
+ homepageurl: repo.homepageUrl || null,
+ stars: repo.stargazerCount
+ }));
+ })
+ );
+ }
+
+ const batchResults = await Promise.all(batchPromises);
+ const dependents: Dependent[] = batchResults.flat();
+
+ console.log(
+ `[getDependents] Step 2 - GraphQL details: ${((performance.now() - step2Start) / 1000).toFixed(2)}s (${dependents.length} repos, ${batchPromises.length} batches)`
+ );
+ console.log(`[getDependents] Total: ${((performance.now() - totalStart) / 1000).toFixed(2)}s`);
+
+ dependents
+ .sort((a, b) => (b.stars ?? 0) - (a.stars ?? 0)) // Sort by stars descending
+ .filter((d) => featuredSites.some((f) => f.reponame === d.reponame)) // Filter out any featured sites
+ .filter((d) => supporterSites.some((s) => s.reponame === d.reponame)); // Filter out any supporter sites
+ const popularSites = [
+ ...highlightedSites,
+ ...dependents.filter((d) => (d.stars ?? 0) >= POPULAR_STAR_THRESHOLD)
+ ];
+ const otherSites = dependents.filter(
+ (d) => (d.stars ?? 0) >= OTHER_STAR_THRESHOLD && (d.stars ?? 0) < POPULAR_STAR_THRESHOLD
+ );
+
+ return { featuredSites, supporterSites, popularSites, otherSites };
+});