diff --git a/src/components/chart-export-button.tsx b/src/components/chart-export-button.tsx index 94ba67a3..aa3aeba3 100644 --- a/src/components/chart-export-button.tsx +++ b/src/components/chart-export-button.tsx @@ -38,7 +38,9 @@ export function ChartExportButton({ filename = "openchainbench-chart", className = "", }: Props) { - const [state, setState] = useState<"idle" | "working" | "copied" | "error">("idle"); + const [state, setState] = useState< + "idle" | "working" | "copied" | "downloaded" | "error" + >("idle"); const capture = useCallback(async (): Promise => { const el = targetRef.current; @@ -99,8 +101,8 @@ export function ChartExportButton({ const blob = await capture(); if (!blob) throw new Error("no target"); triggerDownload(blob, filename); - setState("copied"); - setTimeout(() => setState("idle"), 1200); + setState("downloaded"); + setTimeout(() => setState("idle"), 1600); } catch (err) { console.warn("[chart-export] download failed", err); setState("error"); @@ -126,7 +128,11 @@ export function ChartExportButton({ )} - {state === "copied" ? "Copied" : state === "error" ? "Failed" : "Copy"} + {state === "copied" + ? "Copied" + : state === "error" + ? "Failed" + : "Copy"} @@ -138,7 +144,11 @@ export function ChartExportButton({ title="Download chart as PNG" aria-label="Download chart as PNG" > - + {state === "downloaded" ? ( + + ) : ( + + )} ); diff --git a/src/components/chart-watermark.tsx b/src/components/chart-watermark.tsx index fcacb29e..b5033a4e 100644 --- a/src/components/chart-watermark.tsx +++ b/src/components/chart-watermark.tsx @@ -1,40 +1,52 @@ /** - * Subtle openchainbench.com attribution that renders in both the on-screen - * chart and inside any PNG capture (see `chart-export-button.tsx`). Kept - * low-opacity so it never competes with the data; the copy is small enough - * to survive Twitter/Slack recompression without pixel loss on the value. + * DefiLlama-style diagonal attribution that lives BEHIND the chart data. + * Large, rotated, very low opacity — reads as "openchainbench.com" + * without competing with the lines/bars, and can't be crop-shaved because + * it sits at the middle of the frame. Captured in every PNG export + * (see `chart-export-button.tsx`). * * Two variants: - * - `` → inlined element for SVG charts - * (time-series). Positioned bottom-right of the plot area. - * - `` → absolutely-positioned HTML span for - * HTML-composed charts (ranked bar). Parent must be `relative`. + * - `` → inlined at plot center (rotated + * -22°) for SVG charts (time-series). Must be rendered BEFORE the + * data paths so it sits behind the lines. + * - `` → absolutely-positioned centered span for + * HTML-composed charts (ranked bar). Parent must be `relative` and + * the watermark must be rendered first (z-index 0) so bars stack on top. * - * Colour picks `--color-ink-faint` so it inherits the dark/light theme - * variables and stays legible on both without hardcoded hex. + * Opacity is intentionally lower than the bottom-right badge it replaced + * (0.10 vs 0.28) because the diagonal glyph is much larger and would + * otherwise dominate visually. Trade-off: still perfectly readable in a + * screenshot recompressed by Twitter/Slack; disappears entirely under + * fine-grained chart lines during normal reading. */ export function ChartWatermarkSvg({ - x, - y, - anchor = "end", + cx, + cy, + fontSize = 48, }: { - x: number; - y: number; - anchor?: "start" | "middle" | "end"; + /** Center X of the plot area (padL + innerW/2). */ + cx: number; + /** Center Y of the plot area (padT + innerH/2). */ + cy: number; + /** Font size in SVG user units — scale up to fill larger charts. */ + fontSize?: number; }) { return ( openchainbench.com @@ -42,22 +54,12 @@ export function ChartWatermarkSvg({ ); } -export function ChartWatermarkHtml({ - position = "bottom-right", -}: { - position?: "bottom-right" | "bottom-left" | "top-right"; -}) { - const cls = - position === "bottom-right" - ? "bottom-1 right-2" - : position === "bottom-left" - ? "bottom-1 left-2" - : "top-1 right-2"; +export function ChartWatermarkHtml() { return ( openchainbench.com diff --git a/src/components/ranked-bar-chart.tsx b/src/components/ranked-bar-chart.tsx index a06c4e6a..69a314ba 100644 --- a/src/components/ranked-bar-chart.tsx +++ b/src/components/ranked-bar-chart.tsx @@ -234,7 +234,7 @@ export function RankedBarChart({

{useLog ? "Log scale · " : ""}p50 · last 24 h · click rows to exclude

- + ); } diff --git a/src/components/time-series-chart/chart.tsx b/src/components/time-series-chart/chart.tsx index c48f600d..13052ba0 100644 --- a/src/components/time-series-chart/chart.tsx +++ b/src/components/time-series-chart/chart.tsx @@ -371,6 +371,16 @@ export function Chart({ + {/* Diagonal attribution watermark. Rendered BEFORE the series + paths so the data lines stack on top of it — reads as a + background stamp, not overlay. Sized to the plot area's + shorter axis so it scales gracefully with the chart. */} + + {/* Y gridlines + tick labels */} - - {/* Attribution watermark. Sits inside the plot's right pad so it - doesn't overlap data lines; captured in any PNG export via - chart-export-button.tsx. Kept low-opacity by ChartWatermarkSvg. */} - {/* Floating tooltip */}