diff --git a/components/report/performance-charts.tsx b/components/report/performance-charts.tsx
new file mode 100644
index 00000000..940c5859
--- /dev/null
+++ b/components/report/performance-charts.tsx
@@ -0,0 +1,93 @@
+"use client";
+
+import dynamic from "next/dynamic";
+import Link from "next/link";
+
+// next/dynamic with ssr:false keeps recharts (and its d3 deps) out of the
+// server module graph. The homepage and report pages both render this, so
+// without this gate every Node process carries recharts in resident memory
+// even when nobody's looking at a chart.
+const ScoreTrend = dynamic(
+ () => import("@/components/charts/score-trend").then((m) => m.ScoreTrend),
+ { ssr: false, loading: () =>
},
+);
+const StatusPie = dynamic(
+ () => import("@/components/charts/status-pie").then((m) => m.StatusPie),
+ { ssr: false, loading: () =>
},
+);
+const SectionBar = dynamic(
+ () => import("@/components/charts/section-bar").then((m) => m.SectionBar),
+ { ssr: false, loading: () =>
},
+);
+const PriorityBar = dynamic(
+ () => import("@/components/charts/priority-bar").then((m) => m.PriorityBar),
+ { ssr: false, loading: () =>
},
+);
+
+// Sample data — illustrative only. Picked so the chart shows an obvious
+// upward trend, varied bar heights, and a non-trivial pie split. The 4
+// chart components are the SAME ones used on the signed-in /projects/[id]
+// dashboard, so the preview is faithful to the real product.
+const SAMPLE_TREND = [
+ { date: "2026-03-08T12:00:00Z", score: 48 },
+ { date: "2026-03-15T12:00:00Z", score: 54 },
+ { date: "2026-03-22T12:00:00Z", score: 61 },
+ { date: "2026-03-29T12:00:00Z", score: 65 },
+ { date: "2026-04-05T12:00:00Z", score: 70 },
+ { date: "2026-04-12T12:00:00Z", score: 73 },
+ { date: "2026-04-19T12:00:00Z", score: 81 },
+ { date: "2026-04-26T12:00:00Z", score: 84 },
+];
+
+const SAMPLE_STATUS = { pass: 38, warn: 14, fail: 3, unknown: 1 };
+
+const SAMPLE_SECTIONS = [
+ { section: "Crawl Summary", pass: 1, warn: 0, fail: 0 },
+ { section: "Data Found", pass: 9, warn: 2, fail: 1 },
+ { section: "Homepage Audit", pass: 7, warn: 1, fail: 0 },
+ { section: "Schema / Structured Data", pass: 4, warn: 2, fail: 0 },
+ { section: "robots.txt & sitemap.xml", pass: 5, warn: 0, fail: 0 },
+ { section: "LLM / AI Crawler Access", pass: 7, warn: 2, fail: 1 },
+ { section: "Positioning Clarity", pass: 5, warn: 2, fail: 0 },
+];
+
+const SAMPLE_PRIORITY = { p1: 0, p2: 2, p3: 6, p4: 5, p5: 4 };
+
+export type PerformanceChartsOverlayCopy = {
+ overlayTitle: string;
+ overlayBody: string;
+};
+
+export function PerformanceCharts({ overlayTitle, overlayBody }: PerformanceChartsOverlayCopy) {
+ return (
+
+
+
+ {/* Bottom-fade CTA so the charts are visible up top but it's clear
+ this is gated behind sign-up. */}
+
+
+
Premium
+
{overlayTitle}
+
{overlayBody}
+
+ Sign up free
+
+
+ 3 free AI credits on signup · free rule-based scans up to the daily limit.
+
+
+
+
+ );
+}
diff --git a/components/report/performance-preview.tsx b/components/report/performance-preview.tsx
index 539e15e1..f8ef46a2 100644
--- a/components/report/performance-preview.tsx
+++ b/components/report/performance-preview.tsx
@@ -1,57 +1,5 @@
-"use client";
-
-import dynamic from "next/dynamic";
import Link from "next/link";
-
-// next/dynamic with ssr:false keeps recharts (and its d3 deps) out of the
-// server module graph. The homepage and report pages both render this, so
-// without this gate every Node process carries recharts in resident memory
-// even when nobody's looking at a chart.
-const ScoreTrend = dynamic(
- () => import("@/components/charts/score-trend").then((m) => m.ScoreTrend),
- { ssr: false, loading: () =>
},
-);
-const StatusPie = dynamic(
- () => import("@/components/charts/status-pie").then((m) => m.StatusPie),
- { ssr: false, loading: () =>
},
-);
-const SectionBar = dynamic(
- () => import("@/components/charts/section-bar").then((m) => m.SectionBar),
- { ssr: false, loading: () =>
},
-);
-const PriorityBar = dynamic(
- () => import("@/components/charts/priority-bar").then((m) => m.PriorityBar),
- { ssr: false, loading: () =>
},
-);
-
-// Sample data — illustrative only. Picked so the chart shows an obvious
-// upward trend, varied bar heights, and a non-trivial pie split. The 4
-// chart components are the SAME ones used on the signed-in /projects/[id]
-// dashboard, so the preview is faithful to the real product.
-const SAMPLE_TREND = [
- { date: "2026-03-08T12:00:00Z", score: 48 },
- { date: "2026-03-15T12:00:00Z", score: 54 },
- { date: "2026-03-22T12:00:00Z", score: 61 },
- { date: "2026-03-29T12:00:00Z", score: 65 },
- { date: "2026-04-05T12:00:00Z", score: 70 },
- { date: "2026-04-12T12:00:00Z", score: 73 },
- { date: "2026-04-19T12:00:00Z", score: 81 },
- { date: "2026-04-26T12:00:00Z", score: 84 },
-];
-
-const SAMPLE_STATUS = { pass: 38, warn: 14, fail: 3, unknown: 1 };
-
-const SAMPLE_SECTIONS = [
- { section: "Crawl Summary", pass: 1, warn: 0, fail: 0 },
- { section: "Data Found", pass: 9, warn: 2, fail: 1 },
- { section: "Homepage Audit", pass: 7, warn: 1, fail: 0 },
- { section: "Schema / Structured Data", pass: 4, warn: 2, fail: 0 },
- { section: "robots.txt & sitemap.xml", pass: 5, warn: 0, fail: 0 },
- { section: "LLM / AI Crawler Access", pass: 7, warn: 2, fail: 1 },
- { section: "Positioning Clarity", pass: 5, warn: 2, fail: 0 },
-];
-
-const SAMPLE_PRIORITY = { p1: 0, p2: 2, p3: 6, p4: 5, p5: 4 };
+import { PerformanceCharts } from "./performance-charts";
type Variant = "report" | "home";
@@ -99,35 +47,10 @@ export function PerformancePreview({ variant = "report" }: { variant?: Variant }
-
-
-
- {/* Bottom-fade CTA so the charts are visible up top but it's clear
- this is gated behind sign-up. */}
-
-
-
Premium
-
{copy.overlayTitle}
-
{copy.overlayBody}
-
- Sign up free
-
-
- 3 free AI credits on signup · free rule-based scans up to the daily limit.
-
-
-
-
+
);
}