133 lines
5.2 KiB
TypeScript
133 lines
5.2 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
|
|
export function HeroShader() {
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) return;
|
|
|
|
let t = 0;
|
|
let animationFrameId: number;
|
|
|
|
const resize = () => {
|
|
const dpr = window.devicePixelRatio || 1;
|
|
canvas.width = canvas.offsetWidth * dpr;
|
|
canvas.height = canvas.offsetHeight * dpr;
|
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
};
|
|
resize();
|
|
window.addEventListener("resize", resize);
|
|
|
|
const draw = () => {
|
|
// Clear canvas with some transparency for trails?
|
|
// Or just clear completely given the math looks like it redraws?
|
|
// Tweet says background(9), which is very dark.
|
|
// Tweetcarts usually redraw fully or rely on trails.
|
|
// Given complexity, let's clear fully first.
|
|
ctx.fillStyle = "#090909"; // background(9) approximation
|
|
// ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Actually, let's make it transparent so it blends with our site
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
ctx.fillStyle = "rgba(255, 255, 255, 0.5)"; // stroke(w, 116) approx white with alpha
|
|
|
|
// Model space tuned for ~400x400 tweetcart output.
|
|
const baseSize = 400;
|
|
const scale = Math.min(canvas.width, canvas.height) / baseSize;
|
|
|
|
const canvasWidth = canvas.offsetWidth;
|
|
const canvasHeight = canvas.offsetHeight;
|
|
|
|
// Center of drawing - positioned to the right and aligned with content
|
|
const cx = canvasWidth * 0.7;
|
|
const cy = canvasHeight * 0.52;
|
|
|
|
// Loop for points
|
|
// for(t+=PI/90,i=1e4;i--;)a()
|
|
t += Math.PI / 90;
|
|
|
|
const density = Math.max(1, scale);
|
|
const pointCount = Math.min(18000, Math.floor(10000 * density));
|
|
|
|
for (let i = pointCount; i > 0; i--) {
|
|
// y = i / 790
|
|
let y = i / 790;
|
|
|
|
// k = (y<8 ? 9+sin(y^9)*6 : 4+cos(y)) * cos(i+t/4)
|
|
// Note: processing sin(y^9) is bitwise XOR in JS, but usually in math it's power?
|
|
// "y^9" in many tweetcarts (JS) is XOR if it's straight JS evaluation,
|
|
// but if it's GLSL it might mean pow.
|
|
// Given "tweetcart", it's likely JS/Processing, where ^ is XOR in standard JS but often used as Pow in math context?
|
|
// Processing language: ^ is bitwise XOR. pow(n, e) is power.
|
|
// Let's assume XOR as it's common in condensed code.
|
|
// Wait, standard JS `^` is XOR.
|
|
let k_term1 = (y < 8)
|
|
? (9 + Math.sin(y ** 9) * 6) // Trying Power first as it produces curves
|
|
: (4 + Math.cos(y));
|
|
|
|
// Wait, dweet/tweetcart usually use JS syntax.
|
|
// Let's try to replicate exact syntax logic.
|
|
// Logic: k = (condition) * cos(i + t/4)
|
|
|
|
// Re-evaluating y^9. If y is float, XOR converts to int.
|
|
// y = i / 790, which is float.
|
|
// Let's stick to Math.pow for smoother graphs if intended.
|
|
// But let's try standard JS behavior for ^ (XOR) might be intended for glitchy look?
|
|
// Let's try power first.
|
|
|
|
const k = ((y < 8) ? (9 + Math.sin(Math.pow(y, 9)) * 6) : (4 + Math.cos(y))) * Math.cos(i + t / 4);
|
|
|
|
// e = y/3 - 13 + cos(e ?? no, that was likely comma operator)
|
|
// Original: d=mag(k=(...), e=y/3-13) + cos(e+t*2+i%2*4)
|
|
// mag(a, b) = sqrt(a*a + b*b)
|
|
// So args to mag are k and e.
|
|
const e = y / 3 - 13;
|
|
|
|
const mag_ke = Math.sqrt(k * k + e * e);
|
|
|
|
// d = mag(...) + cos(e + t*2 + i%2*4)
|
|
const d = mag_ke + Math.cos(e + t * 2 + (i % 2) * 4);
|
|
|
|
// c = d/4 - t/2 + i%2*3
|
|
const c = d / 4 - t / 2 + (i % 2) * 3;
|
|
|
|
// q = y * k / 5 * (2 + sin(d*2 + y - t*4)) + 80
|
|
const q = y * k / 5 * (2 + Math.sin(d * 2 + y - t * 4)) + 80;
|
|
|
|
// Original offsets assume a 400x400 canvas; map from model space to screen space.
|
|
const modelX = q * Math.cos(c) + 200;
|
|
const modelY = q * Math.sin(c) + d * 9 + 60;
|
|
|
|
const x = cx + (modelX - 200) * scale;
|
|
const y_out = cy + (modelY - 200) * scale;
|
|
const pointSize = Math.min(2 * scale, 3.5);
|
|
|
|
ctx.fillRect(x, y_out, pointSize, pointSize);
|
|
}
|
|
|
|
animationFrameId = requestAnimationFrame(draw);
|
|
};
|
|
|
|
draw();
|
|
|
|
return () => {
|
|
window.removeEventListener("resize", resize);
|
|
cancelAnimationFrame(animationFrameId);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<canvas
|
|
ref={canvasRef}
|
|
className="absolute inset-0 h-full w-full opacity-60 mix-blend-screen pointer-events-none"
|
|
/>
|
|
);
|
|
}
|