"use client"; import type { HomepageSummarySite } from "yt-dlp-transcript-common/lib/homepageSummary"; import type { Breakdown, Metric } from "yt-dlp-transcript-common/lib/homepageChart"; import { siteChartColors } from "yt-dlp-transcript-common/lib/siteColor"; // Tiny dependency-free sparkline: a soft area fill, the trend line, and a dot on // the latest point. Avoids mounting a Recharts instance per card (mobile perf). function Sparkline({ data, color }: { data: number[]; color: string }) { const w = 104; const h = 32; const max = Math.max(1, ...data); const n = data.length; const pt = (v: number, i: number) => { const x = n <= 1 ? w : (i / (n - 1)) * w; const y = h - (v / max) * (h - 3) - 1.5; return [x, y] as const; }; const pts = data.map((v, i) => pt(v, i)); const line = pts.map(([x, y]) => `${x.toFixed(1)},${y.toFixed(1)}`).join(" "); const area = `0,${h} ${line} ${w},${h}`; const [lx, ly] = pts[pts.length - 1] ?? [w, h]; const gid = `spark-${color.replace(/[^a-z0-9]/gi, "")}`; return ( ); } function SiteCard({ site, metric, color, isLeader, bySite, hidden, onToggle, delay, }: { site: HomepageSummarySite; metric: Metric; color: string; isLeader: boolean; bySite: boolean; hidden: boolean; onToggle: () => void; delay: number; }) { const stat = site[metric]; const noun = metric === "downloaded" ? "downloads" : "transcripts"; const inner = ( <>
{bySite && ( )}

{site.siteTitle}

{isLeader && ( #1 this month )}
{site.siteDescription && (

{site.siteDescription}

)}
{stat.total.toLocaleString()} {noun} {stat.thisMonth > 0 && ( +{stat.thisMonth.toLocaleString()} mo. )}
); const cardClass = "group relative flex flex-col gap-3 rounded-[var(--radius)] border border-[var(--border)] bg-[var(--panel)] p-5 transition duration-200 hover:-translate-y-0.5 hover:border-[var(--border-strong)] hover:bg-[var(--panel-2)]" + (hidden ? " opacity-40" : ""); return (
{bySite && ( )} {inner}
); } // Responsive grid of public content sites. Card stats follow the active metric // and the grid sorts by that metric's "this month" count. In the By-site // breakdown the cards double as the chart legend (colored swatch + Show/Hide // toggle that filters the matching band); colors key on each site's place in // the unsorted `sites` list so they stay aligned with the chart — its chart // colour (siteChartColors: its accent's hue family in the validated palette, // else seriesColor at its index), the one homepageChartData gives its series. export function SiteGrid({ sites, metric, breakdown, hidden, onToggle, }: { sites: HomepageSummarySite[]; metric: Metric; breakdown: Breakdown; hidden: ReadonlySet; onToggle: (siteId: string) => void; }) { if (sites.length === 0) return null; const bySite = breakdown === "site"; const chart = siteChartColors(sites); const colorOf = new Map(sites.map((s, i) => [s.siteId, chart[i]])); const ordered = [...sites].sort( (a, b) => b[metric].thisMonth - a[metric].thisMonth || b[metric].total - a[metric].total || a.siteTitle.localeCompare(b.siteTitle), ); const maxThisMonth = Math.max(0, ...sites.map((s) => s[metric].thisMonth)); const leaderId = maxThisMonth > 0 ? ordered.find((s) => s[metric].thisMonth === maxThisMonth)?.siteId : undefined; return (
{ordered.map((site, i) => (
); }