From cf36c54b764ac2c2d26bdb2a26b51e32a032b44d Mon Sep 17 00:00:00 2001 From: Justin Levine <20596508+justinlevinedotme@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:26:58 -0700 Subject: [PATCH] feat(docs): add sponsor page, site footer, and header sponsor link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add /sponsor page with hero, metallic plaque sponsor tiers, stargazers wall, and CTA - Stargazers fetched from GitHub API at build time (1hr revalidation) - Hatch pattern strips flanking content with grid background outside - Add shared SiteFooter component with logo, project links, Discord, and GitHub - Add ♡ Sponsor button to docs header - Footer used on both docs layout and sponsor page --- apps/docs/app/docs/layout.tsx | 10 + apps/docs/app/sponsor/page.tsx | 375 +++++++++++++++++++++++++++ apps/docs/components/docs/footer.tsx | 126 +++++++++ 3 files changed, 511 insertions(+) create mode 100644 apps/docs/app/sponsor/page.tsx create mode 100644 apps/docs/components/docs/footer.tsx diff --git a/apps/docs/app/docs/layout.tsx b/apps/docs/app/docs/layout.tsx index 08ccdad..494993a 100644 --- a/apps/docs/app/docs/layout.tsx +++ b/apps/docs/app/docs/layout.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from "react" import Link from "next/link" +import { Heart } from "lucide-react" import { FumadocsSidebar } from "@/components/docs/fumadocs-sidebar" import { MobileNav } from "@/components/docs/mobile-nav" import { ThemeSwitcher } from "@/components/docs/theme-switcher" @@ -8,6 +9,7 @@ import { source } from "@/lib/source" import { JalcoLogo } from "@/components/icons/jalco-logo" import { GitHubStarsButton } from "@/registry/github-stars-button/github-stars-button" +import { SiteFooter } from "@/components/docs/footer" export default function DocsLayout({ children }: { children: ReactNode }) { @@ -50,6 +52,12 @@ export default function DocsLayout({ children }: { children: ReactNode }) {
+
+ + ) diff --git a/apps/docs/app/sponsor/page.tsx b/apps/docs/app/sponsor/page.tsx new file mode 100644 index 0000000..62a14b3 --- /dev/null +++ b/apps/docs/app/sponsor/page.tsx @@ -0,0 +1,375 @@ +import type { Metadata } from "next" +import Image from "next/image" +import Link from "next/link" +import { Heart, ExternalLink, Star, Plus } from "lucide-react" +import { JalcoLogo } from "@/components/icons/jalco-logo" +import { ThemeSwitcher } from "@/components/docs/theme-switcher" +import { GitHubStarsButton } from "@/registry/github-stars-button/github-stars-button" +import { SiteFooter } from "@/components/docs/footer" +import { Button } from "@/registry/ui/button" + +export const metadata: Metadata = { + title: "Sponsor — jal-co/ui", + description: + "Support jal-co/ui. Every component is free and always will be — but if you want to help, this is the way.", +} + +const GITHUB_SPONSORS_URL = "https://github.com/sponsors/jal-co" + +interface Stargazer { + login: string + avatar_url: string +} + +async function getStargazers(): Promise { + try { + const pages: Stargazer[] = [] + let page = 1 + // Fetch up to 5 pages (500 stargazers) to be safe + while (page <= 5) { + const res = await fetch( + `https://api.github.com/repos/jal-co/ui/stargazers?per_page=100&page=${page}`, + { + headers: { Accept: "application/vnd.github.v3+json" }, + next: { revalidate: 3600 }, + } + ) + if (!res.ok) break + const data: Stargazer[] = await res.json() + if (data.length === 0) break + pages.push(...data) + if (data.length < 100) break + page++ + } + // Filter out the org account + return pages.filter((s) => s.login !== "jal-co") + } catch { + return [] + } +} + +const tiers = [ + { + name: "Gold", + slots: 3, + sponsors: [] as { name: string; href: string; logo?: string }[], + colors: { + bg: "linear-gradient(145deg, #d4a84b 0%, #f5d98a 30%, #c9952e 60%, #e8c55a 100%)", + text: "#5c3d0e", + border: "#c9952e", + slotBg: "rgba(212, 168, 75, 0.08)", + slotBorder: "rgba(201, 149, 46, 0.25)", + }, + }, + { + name: "Silver", + slots: 3, + sponsors: [] as { name: string; href: string; logo?: string }[], + colors: { + bg: "linear-gradient(145deg, #a8a8a8 0%, #d4d4d4 30%, #8a8a8a 60%, #c0c0c0 100%)", + text: "#2a2a2a", + border: "#8a8a8a", + slotBg: "rgba(168, 168, 168, 0.06)", + slotBorder: "rgba(138, 138, 138, 0.2)", + }, + }, + { + name: "Bronze", + slots: 4, + sponsors: [] as { name: string; href: string; logo?: string }[], + colors: { + bg: "linear-gradient(145deg, #b5745a 0%, #d4956e 30%, #8c5a3e 60%, #c98a68 100%)", + text: "#3d1e0e", + border: "#8c5a3e", + slotBg: "rgba(181, 116, 90, 0.06)", + slotBorder: "rgba(140, 90, 62, 0.2)", + }, + }, +] + +export default async function SponsorPage() { + const stargazers = await getStargazers() + return ( +
+
+ +
+
+ + + jal-co/ui + + + + +
+ + + +
+
+ +
+
+
+ {/* Hero */} +
+
+
+ + Sponsor +
+

+ I'll never charge, but if you want to help +

+

+ jal-co/ui is an open source React component library distributed + through the shadcn registry. Every component is free and that's + not changing. +

+

+ I'm not going to paywall components or gate features behind a + sponsorship tier. That's not the point. But if something from + the registry saved you an afternoon, or you just like that this + exists in the open, sponsoring is a nice way to say so. It helps + me justify spending real time on it instead of treating it like a + side-of-desk thing. +

+

+ Any amount is genuinely appreciated. And if money's not your + thing, starring the repo or sharing a component you liked works + too. +

+
+ + + + +
+ + + + {/* Sponsors */} +
+ + + {tiers.map((tier) => { + const filled = tier.sponsors.length + const empty = tier.slots - filled + + return ( +
+ {/* Plaque header */} +
+ {/* Rivets */} + {["left-2.5 top-1/2 -translate-y-1/2", "right-2.5 top-1/2 -translate-y-1/2"].map((pos) => ( + + ))} +

+ {tier.name} +

+
+ + {/* Sponsor slots */} +
+ {tier.sponsors.map((sponsor) => ( + + {sponsor.logo ? ( + {sponsor.name} + ) : ( + + {sponsor.name} + + )} + + ))} + + {Array.from({ length: empty }).map((_, i) => ( + + + + ))} +
+
+ ) + })} +
+ + {/* Stargazers */} + {stargazers.length > 0 && ( +
+
+

+ Stargazers +

+ + {stargazers.length} + +
+ +
+ {stargazers.map((user) => ( + + {user.login} + + ))} +
+
+ )} + + {/* CTA */} +
+
+

+ Want to support the project? +

+

+ Every bit helps — whether it's a sponsorship, a star, or sharing + something you found useful. +

+
+ + +
+
+
+
+ + +
+
+ ) +} diff --git a/apps/docs/components/docs/footer.tsx b/apps/docs/components/docs/footer.tsx new file mode 100644 index 0000000..4750f1b --- /dev/null +++ b/apps/docs/components/docs/footer.tsx @@ -0,0 +1,126 @@ +import Link from "next/link" +import { JalcoLogo } from "@/components/icons/jalco-logo" + +function DiscordIcon(props: React.SVGProps) { + return ( + + ) +} + +function GitHubIcon(props: React.SVGProps) { + return ( + + ) +} + +export function SiteFooter() { + return ( + + ) +}