Skip to content
Open
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"dependencies": {}}
18 changes: 18 additions & 0 deletions run_perf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import puppeteer from 'puppeteer';
import fs from 'fs';

(async () => {
try {
const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const page = await browser.newPage();

page.on('console', msg => console.log('PAGE LOG:', msg.text()));

const html = fs.readFileSync('test_perf.html', 'utf8');
await page.setContent(html);

await browser.close();
} catch(e) {
console.log("Could not run puppeteer benchmark, falling back to manual analysis");
}
})();
68 changes: 47 additions & 21 deletions site/components/home/ParticleNetwork.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,28 @@ const iconTypes: FloatingIcon["type"][] = ["phone", "app", "cloud", "signal", "c

// ---------- component ----------

const glowCache = new Map<string, HTMLCanvasElement>();

function getGlowCanvas(color: string): HTMLCanvasElement {
if (glowCache.has(color)) return glowCache.get(color)!;
const c = document.createElement("canvas");
const s = 128; // fixed offscreen size
c.width = s;
c.height = s;
const cctx = c.getContext("2d");
if (cctx) {
const gr = cctx.createRadialGradient(s / 2, s / 2, 0, s / 2, s / 2, s / 2);
gr.addColorStop(0, `rgba(${color}, 1)`);
gr.addColorStop(1, `rgba(${color}, 0)`);
cctx.fillStyle = gr;
cctx.fillRect(0, 0, s, s);
}
glowCache.set(color, c);
return c;
}



function ParticleNetwork() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const particles = useRef<Particle[]>([]);
Expand Down Expand Up @@ -258,17 +280,19 @@ function ParticleNetwork() {
ctx.clearRect(0, 0, w, h);

// --- gradient washes ---
const g1 = ctx.createRadialGradient(w * 0.15, h * 0.2, 0, w * 0.15, h * 0.2, w * 0.45);
g1.addColorStop(0, `rgba(${COLORS.BLUE}, 0.06)`);
g1.addColorStop(1, `rgba(${COLORS.BLUE}, 0)`);
ctx.fillStyle = g1;
ctx.fillRect(0, 0, w, h);

const g2 = ctx.createRadialGradient(w * 0.85, h * 0.75, 0, w * 0.85, h * 0.75, w * 0.45);
g2.addColorStop(0, `rgba(${COLORS.INDIGO}, 0.05)`);
g2.addColorStop(1, `rgba(${COLORS.INDIGO}, 0)`);
ctx.fillStyle = g2;
ctx.fillRect(0, 0, w, h);
ctx.save();

const blueImg = getGlowCanvas(COLORS.BLUE);
const s1 = w * 0.9;
ctx.globalAlpha = 0.06;
ctx.drawImage(blueImg, w * 0.15 - s1 / 2, h * 0.2 - s1 / 2, s1, s1);

const indigoImg = getGlowCanvas(COLORS.INDIGO);
const s2 = w * 0.9;
ctx.globalAlpha = 0.05;
ctx.drawImage(indigoImg, w * 0.85 - s2 / 2, h * 0.75 - s2 / 2, s2, s2);

ctx.restore();

const pts = particles.current;
const mx = mouse.current.x;
Expand Down Expand Up @@ -310,11 +334,12 @@ function ParticleNetwork() {
const o = Math.min(p.opacity + glow * 0.5, 1);

if (glow > 0.15) {
const gr = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 4);
gr.addColorStop(0, `rgba(${COLORS.BLUE}, ${glow * 0.2})`);
gr.addColorStop(1, `rgba(${COLORS.BLUE}, 0)`);
ctx.fillStyle = gr;
ctx.fillRect(p.x - r * 4, p.y - r * 4, r * 8, r * 8);
const glowImg = getGlowCanvas(COLORS.BLUE);
const size = r * 8;
ctx.save();
ctx.globalAlpha = glow * 0.2;
ctx.drawImage(glowImg, p.x - size / 2, p.y - size / 2, size, size);
ctx.restore();
}

ctx.beginPath();
Expand Down Expand Up @@ -365,11 +390,12 @@ function ParticleNetwork() {

// glow halo around icon when mouse is near
if (glow > 0.1) {
const gr = ctx.createRadialGradient(icon.x, icon.y, 0, icon.x, icon.y, icon.size * 1.5);
gr.addColorStop(0, `rgba(${icon.color}, ${glow * 0.12})`);
gr.addColorStop(1, `rgba(${icon.color}, 0)`);
ctx.fillStyle = gr;
ctx.fillRect(icon.x - icon.size * 1.5, icon.y - icon.size * 1.5, icon.size * 3, icon.size * 3);
const glowImg = getGlowCanvas(icon.color);
const size = icon.size * 3;
ctx.save();
ctx.globalAlpha = glow * 0.12;
ctx.drawImage(glowImg, icon.x - size / 2, icon.y - size / 2, size, size);
ctx.restore();
}

icon.x += icon.vx;
Expand Down
70 changes: 70 additions & 0 deletions test_perf.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

function testNativeGradients() {
const start = performance.now();
for (let i = 0; i < 10000; i++) {
const p = { x: 400, y: 400, radius: 2 };
const glow = 1;
const r = p.radius + glow * 2;
const gr = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 4);
gr.addColorStop(0, `rgba(68,131,237, ${glow * 0.2})`);
gr.addColorStop(1, `rgba(68,131,237, 0)`);
ctx.fillStyle = gr;
ctx.fillRect(p.x - r * 4, p.y - r * 4, r * 8, r * 8);
}
return performance.now() - start;
}

const glowCache = new Map();
function getGlowCanvas(color) {
if (glowCache.has(color)) return glowCache.get(color);
const c = document.createElement('canvas');
c.width = 64;
c.height = 64;
const cctx = c.getContext('2d');
const gr = cctx.createRadialGradient(32, 32, 0, 32, 32, 32);
gr.addColorStop(0, `rgba(${color}, 1)`);
gr.addColorStop(1, `rgba(${color}, 0)`);
cctx.fillStyle = gr;
cctx.fillRect(0, 0, 64, 64);
glowCache.set(color, c);
return c;
}

function testCachedImages() {
const start = performance.now();
for (let i = 0; i < 10000; i++) {
const p = { x: 400, y: 400, radius: 2 };
const glow = 1;
const r = p.radius + glow * 2;

const glowImg = getGlowCanvas('68,131,237');
const size = r * 8;

ctx.save();
ctx.globalAlpha = glow * 0.2;
ctx.drawImage(glowImg, p.x - size / 2, p.y - size / 2, size, size);
ctx.restore();
}
return performance.now() - start;
}

// Warmup
testNativeGradients();
testCachedImages();

const nativeTime = testNativeGradients();
const cachedTime = testCachedImages();

console.log(`Native Gradients: ${nativeTime.toFixed(2)}ms`);
console.log(`Cached Images: ${cachedTime.toFixed(2)}ms`);
console.log(`Speedup: ${(nativeTime / cachedTime).toFixed(2)}x`);
</script>
</body>
</html>
89 changes: 89 additions & 0 deletions test_perf2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

const COLORS = {
BLUE: "68,131,237",
INDIGO: "99,102,241",
VIOLET: "139,92,246",
BLUE_500: "59,130,246",
INDIGO_600: "79,70,229",
};

function testNativeGradients() {
ctx.clearRect(0,0,800,800);
const start = performance.now();
for (let i = 0; i < 2000; i++) {
const p = { x: Math.random()*800, y: Math.random()*800, radius: 2 };
const glow = Math.random();
const r = p.radius + glow * 2;

const gr = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 4);
gr.addColorStop(0, `rgba(${COLORS.BLUE}, ${glow * 0.2})`);
gr.addColorStop(1, `rgba(${COLORS.BLUE}, 0)`);
ctx.fillStyle = gr;
ctx.fillRect(p.x - r * 4, p.y - r * 4, r * 8, r * 8);
}
return performance.now() - start;
}

const glowCache = new Map();
function getGlowCanvas(color) {
if (glowCache.has(color)) return glowCache.get(color);
const c = document.createElement('canvas');
c.width = 128; // higher res for better quality
c.height = 128;
const cctx = c.getContext('2d');
const gr = cctx.createRadialGradient(64, 64, 0, 64, 64, 64);
gr.addColorStop(0, `rgba(${color}, 1)`);
gr.addColorStop(1, `rgba(${color}, 0)`);
cctx.fillStyle = gr;
cctx.fillRect(0, 0, 128, 128);
glowCache.set(color, c);
return c;
}

// pre-fill cache
getGlowCanvas(COLORS.BLUE);

function testCachedImages() {
ctx.clearRect(0,0,800,800);
const start = performance.now();
const glowImg = getGlowCanvas(COLORS.BLUE);

for (let i = 0; i < 2000; i++) {
const p = { x: Math.random()*800, y: Math.random()*800, radius: 2 };
const glow = Math.random();
const r = p.radius + glow * 2;
const size = r * 8;

// ctx.save and restore is actually slow, let's try direct globalAlpha changes
const oldAlpha = ctx.globalAlpha;
ctx.globalAlpha = glow * 0.2;
ctx.drawImage(glowImg, p.x - size / 2, p.y - size / 2, size, size);
ctx.globalAlpha = oldAlpha;
}
return performance.now() - start;
}

// Warmup
testNativeGradients();
testCachedImages();

let totalNative = 0;
let totalCached = 0;
for(let i=0; i<10; i++) {
totalNative += testNativeGradients();
totalCached += testCachedImages();
}

console.log(`Native Gradients (avg): ${(totalNative/10).toFixed(2)}ms`);
console.log(`Cached Images (avg): ${(totalCached/10).toFixed(2)}ms`);
console.log(`Speedup: ${(totalNative / totalCached).toFixed(2)}x`);
</script>
</body>
</html>
89 changes: 89 additions & 0 deletions test_perf3.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

const COLORS = {
BLUE: "68,131,237",
INDIGO: "99,102,241",
VIOLET: "139,92,246",
BLUE_500: "59,130,246",
INDIGO_600: "79,70,229",
};

function testNativeGradients() {
ctx.clearRect(0,0,800,800);
const start = performance.now();
for (let i = 0; i < 2000; i++) {
const p = { x: Math.random()*800, y: Math.random()*800, radius: 2 };
const glow = Math.random();
const r = p.radius + glow * 2;

const gr = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 4);
gr.addColorStop(0, `rgba(${COLORS.BLUE}, ${glow * 0.2})`);
gr.addColorStop(1, `rgba(${COLORS.BLUE}, 0)`);
ctx.fillStyle = gr;
ctx.fillRect(p.x - r * 4, p.y - r * 4, r * 8, r * 8);
}
return performance.now() - start;
}

const glowCache = new Map();
function getGlowCanvas(color) {
if (glowCache.has(color)) return glowCache.get(color);
const c = document.createElement('canvas');
c.width = 128; // higher res for better quality
c.height = 128;
const cctx = c.getContext('2d', { willReadFrequently: true });
const gr = cctx.createRadialGradient(64, 64, 0, 64, 64, 64);
gr.addColorStop(0, `rgba(${color}, 1)`);
gr.addColorStop(1, `rgba(${color}, 0)`);
cctx.fillStyle = gr;
cctx.fillRect(0, 0, 128, 128);
glowCache.set(color, c);
return c;
}

// pre-fill cache
getGlowCanvas(COLORS.BLUE);

function testCachedImages() {
ctx.clearRect(0,0,800,800);
const start = performance.now();
const glowImg = getGlowCanvas(COLORS.BLUE);

// Save state once
ctx.save();
for (let i = 0; i < 2000; i++) {
const p = { x: Math.random()*800, y: Math.random()*800, radius: 2 };
const glow = Math.random();
const r = p.radius + glow * 2;
const size = r * 8;

ctx.globalAlpha = glow * 0.2;
ctx.drawImage(glowImg, p.x - size / 2, p.y - size / 2, size, size);
}
ctx.restore();
return performance.now() - start;
}

// Warmup
testNativeGradients();
testCachedImages();

let totalNative = 0;
let totalCached = 0;
for(let i=0; i<10; i++) {
totalNative += testNativeGradients();
totalCached += testCachedImages();
}

console.log(`Native Gradients (avg): ${(totalNative/10).toFixed(2)}ms`);
console.log(`Cached Images (avg): ${(totalCached/10).toFixed(2)}ms`);
console.log(`Speedup: ${(totalNative / totalCached).toFixed(2)}x`);
</script>
</body>
</html>
Loading