Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,22 @@ table {
animation: chart-pop-rise 2200ms cubic-bezier(0.16, 0.84, 0.32, 1) forwards;
}

/* MiniChart live-pulse halo. Replaces per-circle SMIL <animate> pairs
(r 2->7->2, opacity .45->0->.45 over 1.8s): the hub grid renders one
halo per provider per bench, so SMIL added hundreds of <animate>
elements to the /benchmarks HTML for a single shared effect.
transform-box: fill-box keeps the scale centered on the data point. */
@keyframes mini-chart-pulse {
0% { transform: scale(1); opacity: 0.45; }
50% { transform: scale(3.5); opacity: 0; }
100% { transform: scale(1); opacity: 0.45; }
}
.mini-chart-pulse {
transform-box: fill-box;
transform-origin: center;
animation: mini-chart-pulse 1.8s linear infinite;
}

/* Pill - used for category/chain filters across the redesign.
min-height bumps mobile tap target without disturbing desktop visual density. */
.pill {
Expand Down
6 changes: 6 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,25 @@ const interTight = Inter_Tight({
display: "swap",
});

// preload: false on the serif + mono families: they style prose/labels
// below the fold, and their 3 woff2 preloads competed with LCP-critical
// Inter/Inter Tight for bandwidth on first paint. display: swap still
// applies, so they load lazily without invisible text.
const sourceSerif = Source_Serif_4({
variable: "--font-source-serif",
subsets: ["latin"],
weight: ["400", "500", "600"],
style: ["italic", "normal"],
display: "swap",
preload: false,
});

const jetbrainsMono = JetBrains_Mono({
variable: "--font-jetbrains-mono",
subsets: ["latin"],
weight: ["400", "500"],
display: "swap",
preload: false,
});

export const viewport: Viewport = {
Expand Down
51 changes: 13 additions & 38 deletions src/components/mini-chart.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useMemo } from "react";
import { downsample, MINI_CHART_POINTS } from "@/lib/downsample";
import { buildProviderColors } from "@/lib/series-colors";

/**
Expand Down Expand Up @@ -52,7 +53,7 @@ export function MiniChart({
.map((r) => ({
slug: r.slug,
name: r.name,
values: downsample(benchmark.extras.series24h[r.slug] ?? [], 28),
values: downsample(benchmark.extras.series24h[r.slug] ?? [], MINI_CHART_POINTS),
color: colors.get(r.slug) ?? "var(--color-ink-soft)",
}))
.filter((s) => s.values.length > 1);
Expand Down Expand Up @@ -103,21 +104,17 @@ export function MiniChart({
vectorEffect="non-scaling-stroke"
points={points}
/>
{/* Live pulse halo. animated outward */}
<circle cx={lastX} cy={lastY} r={2} fill={color} opacity={0.35}>
<animate
attributeName="r"
values="2;7;2"
dur="1.8s"
repeatCount="indefinite"
/>
<animate
attributeName="opacity"
values="0.45;0;0.45"
dur="1.8s"
repeatCount="indefinite"
/>
</circle>
{/* Live pulse halo. CSS keyframes (globals.css) instead of
SMIL <animate>: with 37 benches x N providers on the hub
grid the two <animate> children per halo added ~800
elements of markup for an effect one shared class covers. */}
<circle
cx={lastX}
cy={lastY}
r={2}
fill={color}
className="mini-chart-pulse"
/>
{/* Solid endpoint */}
<circle cx={lastX} cy={lastY} r={2.2} fill={color} />
</g>
Expand Down Expand Up @@ -166,25 +163,3 @@ export function MiniChart({
);
}

// 48-px-tall sparkline can't render >~30 distinct points anyway. Bucket
// long series down so the rendered SVG path stays compact (each kept
// point is the bucket mean, preserving the shape that drove the polyline
// at full resolution). Cuts the /benchmarks HTML payload sharply when
// the grid is showing every bench's series at once.
function downsample(values: number[], target: number): number[] {
if (values.length <= target) return values;
const bucketSize = values.length / target;
const out: number[] = [];
for (let i = 0; i < target; i++) {
const start = Math.floor(i * bucketSize);
const end = Math.floor((i + 1) * bucketSize);
let sum = 0;
let n = 0;
for (let j = start; j < end && j < values.length; j++) {
sum += values[j];
n++;
}
if (n > 0) out.push(sum / n);
}
return out;
}
14 changes: 13 additions & 1 deletion src/data/benchmarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { promises as fs } from "node:fs";
import path from "node:path";
import yaml from "js-yaml";
import { cache } from "react";
import { downsample, MINI_CHART_POINTS } from "@/lib/downsample";
import type { Benchmark } from "@/types/benchmark";
import {
loadAllBenchmarks,
Expand Down Expand Up @@ -81,7 +82,18 @@ export function toBenchmarkCardData(b: Benchmark): BenchmarkCardData {
availability: r.availability,
ms: { p50: r.ms.p50 },
})),
extras: { series24h: b.extras?.series24h ?? {} },
// Downsample at the projection boundary: the card's MiniChart caps
// rendering at MINI_CHART_POINTS anyway, so shipping the raw 24h
// series (hundreds of points x providers x benches) only bloated
// the RSC payload without changing a single drawn pixel.
extras: {
series24h: Object.fromEntries(
Object.entries(b.extras?.series24h ?? {}).map(([slug, values]) => [
slug,
downsample(values, MINI_CHART_POINTS),
])
),
},
};
}

Expand Down
28 changes: 28 additions & 0 deletions src/lib/downsample.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Bucket-mean downsampling for sparkline series. Each kept point is the
* mean of its bucket, preserving the shape that drove the polyline at
* full resolution. Shared by the MiniChart renderer (client) and the
* BenchmarkCardData projection (server) so the hub can ship pre-shrunk
* series over the RSC wire without changing what the chart draws.
*/
export function downsample(values: number[], target: number): number[] {
if (values.length <= target) return values;
const bucketSize = values.length / target;
const out: number[] = [];
for (let i = 0; i < target; i++) {
const start = Math.floor(i * bucketSize);
const end = Math.floor((i + 1) * bucketSize);
let sum = 0;
let n = 0;
for (let j = start; j < end && j < values.length; j++) {
sum += values[j];
n++;
}
if (n > 0) out.push(sum / n);
}
return out;
}

/** Max points a 48-px MiniChart sparkline can usefully render. Series
* shipped to hub cards are capped to this at the projection boundary. */
export const MINI_CHART_POINTS = 28;
Loading