feat: harden portfolio and add SEO foundations
This commit is contained in:
1 parent
b079afdd73
commit
62bcfbd8fa
121 files changed
+7769
-10469
No files matched your search
@@ -1,70 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function FilmGrain() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const reducedMotion = window.matchMedia(
|
||||
"(prefers-reduced-motion: reduce)",
|
||||
).matches;
|
||||
const coarsePointer = window.matchMedia("(pointer: coarse)").matches;
|
||||
if (reducedMotion || coarsePointer) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
let animId: number;
|
||||
let lastFrame = 0;
|
||||
const fps = 24; // Cinematic film fps
|
||||
const frameInterval = 1000 / fps;
|
||||
|
||||
const resize = () => {
|
||||
canvas.width = window.innerWidth / 2;
|
||||
canvas.height = window.innerHeight / 2;
|
||||
};
|
||||
resize();
|
||||
window.addEventListener("resize", resize);
|
||||
|
||||
const render = (timestamp: number) => {
|
||||
animId = requestAnimationFrame(render);
|
||||
|
||||
const delta = timestamp - lastFrame;
|
||||
if (delta < frameInterval) return;
|
||||
lastFrame = timestamp - (delta % frameInterval);
|
||||
|
||||
const { width, height } = canvas;
|
||||
const imageData = ctx.createImageData(width, height);
|
||||
const data = imageData.data;
|
||||
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
const noise = Math.random() * 20;
|
||||
data[i] = noise;
|
||||
data[i + 1] = noise;
|
||||
data[i + 2] = noise;
|
||||
data[i + 3] = 12; // Very subtle
|
||||
}
|
||||
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
};
|
||||
|
||||
animId = requestAnimationFrame(render);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animId);
|
||||
window.removeEventListener("resize", resize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="film-grain pointer-events-none fixed inset-0 z-[9990] h-full w-full opacity-[0.35] mix-blend-overlay"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
);
|
||||
return <div aria-hidden="true" className="film-grain" />;
|
||||
}
|
||||
Reference in new issue
Block a user