diff --git a/src/app/compare/[slug]/page.tsx b/src/app/compare/[slug]/page.tsx
index 37920584..e0bf7e3d 100644
--- a/src/app/compare/[slug]/page.tsx
+++ b/src/app/compare/[slug]/page.tsx
@@ -1004,11 +1004,6 @@ export default async function ComparePage({
bench={s}
aName={a.name}
bName={b.name}
- // Per-card trend charts only for perp venue pairs, where the
- // benches are daily or slow-moving series worth a history.
- // Latency / fee benches on other pairs read as flat lines.
- aSlug={perpPair ? a.slug : undefined}
- bSlug={perpPair ? b.slug : undefined}
/>
))}
diff --git a/src/components/compare-bench-card.tsx b/src/components/compare-bench-card.tsx
index 20e82066..d319b6c9 100644
--- a/src/components/compare-bench-card.tsx
+++ b/src/components/compare-bench-card.tsx
@@ -1,7 +1,6 @@
"use client";
import { Fragment } from "react";
-import { CompareTrendChart } from "@/components/compare-trend-chart";
import Link from "next/link";
import type { Benchmark } from "@/types/benchmark";
import { fmtUnit, fmtValue, unitSuffix } from "@/lib/format";
@@ -72,16 +71,10 @@ export function CompareBenchCard({
bench,
aName,
bName,
- aSlug,
- bSlug,
}: {
bench: CompareBench;
aName: string;
bName: string;
- /** Provider slugs, for the trend chart's /api/series filter. Optional
- * so older call sites keep working without the chart. */
- aSlug?: string;
- bSlug?: string;
}) {
const hasScopes = bench.panelScopes.length > 0;
@@ -153,17 +146,6 @@ export function CompareBenchCard({
>
)}
- {aSlug && bSlug && (
-
- )}
-
{bench.note && (
{bench.note}
diff --git a/src/components/compare-trend-chart.tsx b/src/components/compare-trend-chart.tsx
deleted file mode 100644
index c35c9369..00000000
--- a/src/components/compare-trend-chart.tsx
+++ /dev/null
@@ -1,242 +0,0 @@
-"use client";
-
-import { useEffect, useMemo, useRef, useState } from "react";
-import { fmtUnit } from "@/lib/format";
-import { brandColor } from "@/lib/brand";
-import { lineColor } from "@/lib/series-colors";
-
-type Range = "7d" | "30d" | "90d";
-
-type SeriesPayload = {
- timestamps: number[];
- providers: { slug: string; values: (number | null)[] }[];
-};
-
-/**
- * Two-line trend for one shared bench on a compare page: the same
- * /api/series the bench page reads, filtered to the pair, so every
- * compare card carries the history behind its two headline numbers
- * rather than a single 24h figure. Fetched when the card scrolls into
- * view (a pair shares up to a dozen benches) and cached per range.
- * Hidden when neither side has a point in the window.
- */
-export function CompareTrendChart({
- benchSlug,
- unit,
- aSlug,
- bSlug,
- aName,
- bName,
-}: {
- benchSlug: string;
- unit: string;
- aSlug: string;
- bSlug: string;
- aName: string;
- bName: string;
-}) {
- const [range, setRange] = useState("30d");
- const [data, setData] = useState>>({});
- // Starts visible when IntersectionObserver is unavailable (old
- // browsers, SSR) so the chart still loads; otherwise waits for the
- // card to scroll near the viewport.
- const [visible, setVisible] = useState(
- () => typeof IntersectionObserver === "undefined",
- );
- const [hover, setHover] = useState(null);
- const rootRef = useRef(null);
-
- useEffect(() => {
- const el = rootRef.current;
- if (!el || visible) return;
- const io = new IntersectionObserver(
- (entries) => {
- if (entries.some((e) => e.isIntersecting)) setVisible(true);
- },
- { rootMargin: "200px" },
- );
- io.observe(el);
- return () => io.disconnect();
- }, [visible]);
-
- useEffect(() => {
- if (!visible || range in data) return;
- let cancelled = false;
- const qs = new URLSearchParams({ range, raw: "1", providers: `${aSlug},${bSlug}` });
- fetch(`/api/series/${benchSlug}?${qs.toString()}`)
- .then((r) => (r.ok ? r.json() : null))
- .then((json: SeriesPayload | null) => {
- if (cancelled) return;
- setData((d) => ({ ...d, [range]: json }));
- })
- .catch(() => {
- if (!cancelled) setData((d) => ({ ...d, [range]: null }));
- });
- return () => {
- cancelled = true;
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [visible, range, benchSlug, aSlug, bSlug]);
-
- const payload = data[range];
- const series = useMemo(() => {
- if (!payload) return null;
- const pick = (slug: string) =>
- payload.providers.find((p) => p.slug === slug)?.values ?? [];
- const a = pick(aSlug);
- const b = pick(bSlug);
- const n = Math.max(a.length, b.length, payload.timestamps.length);
- if (n === 0) return null;
- const hasA = a.some((v) => v !== null);
- const hasB = b.some((v) => v !== null);
- if (!hasA && !hasB) return null;
- return { a, b, ts: payload.timestamps, n };
- }, [payload, aSlug, bSlug]);
-
- const aColor = brandColor(aSlug) ?? lineColor(0);
- const bColor = brandColor(bSlug) ?? lineColor(1);
-
- // Nothing yet, or confirmed empty: keep the card's layout stable with
- // a slim placeholder only while loading; render nothing on empty.
- if (payload === null || (payload && !series)) return ;
- if (!series) {
- return (
-
- );
- }
-
- const W = 800;
- const H = 170;
- const PAD_L = 54;
- const PAD_R = 8;
- const PAD_T = 10;
- const PAD_B = 20;
- const plotW = W - PAD_L - PAD_R;
- const plotH = H - PAD_T - PAD_B;
- const vals = [...series.a, ...series.b].filter((v): v is number => v !== null && Number.isFinite(v));
- const max = Math.max(...vals, 0);
- const min = Math.min(...vals, 0);
- const span = max - min || 1;
- const x = (i: number) => PAD_L + (series.n <= 1 ? plotW / 2 : (i / (series.n - 1)) * plotW);
- const y = (v: number) => PAD_T + plotH - ((v - min) / span) * plotH;
- const path = (arr: (number | null)[]) => {
- const segs: string[] = [];
- let cur = "";
- arr.forEach((v, i) => {
- if (v === null || !Number.isFinite(v)) {
- if (cur) segs.push(cur);
- cur = "";
- return;
- }
- cur += `${cur ? "L" : "M"}${x(i).toFixed(1)},${y(v).toFixed(1)}`;
- });
- if (cur) segs.push(cur);
- return segs.join(" ");
- };
- const hv = hover !== null ? { a: series.a[hover] ?? null, b: series.b[hover] ?? null, t: series.ts[hover] } : null;
- const fmt = (v: number | null) => (v === null ? "n/a" : fmtUnit(v, unit));
- const first = series.ts[0];
- const last = series.ts[series.n - 1];
-
- return (
-
-
-
-
-
- {aName}
-
-
-
- {bName}
-
- {hv && (
-
- {fmtTs(hv.t, range)} ยท {fmt(hv.a)} vs {fmt(hv.b)}
-
- )}
-
-
- {(["7d", "30d", "90d"] as const).map((r) => (
-
- ))}
-
-
-
-
- );
-}
-
-function fmtTs(ms: number | undefined, range: Range): string {
- if (!ms) return "";
- const d = new Date(ms);
- const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
- const day = `${months[d.getUTCMonth()]} ${d.getUTCDate()}`;
- if (range === "7d") return `${day} ${String(d.getUTCHours()).padStart(2, "0")}:00`;
- return day;
-}