feat(portfolio): add operator profile with banner, publication, and honors

This commit is contained in:
kbot
2026-07-06 20:32:12 -05:00
parent f4264886f9
commit efe0b5b394
4 changed files with 495 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
"use client";
import { useEffect, useRef } from "react";
import { useReducedMotion } from "motion/react";
const COLORS = {
bg: "#030506",
line: "rgba(44, 53, 58, 0.46)",
dot: "rgba(244, 247, 248, 0.38)",
cyan: "rgba(0, 240, 255, 0.82)",
};
const GRID_COLUMNS = 14;
const GRID_ROWS = 13;
const PULSE_PERIOD_MS = 5200;
function projectX(width: number, xIndex: number, yIndex: number, time: number) {
const normalizedX = (xIndex / (GRID_COLUMNS - 1) - 0.5) * 2;
const normalizedY = yIndex / (GRID_ROWS - 1);
const depth = normalizedY * normalizedY;
return (
width * 0.5 +
normalizedX * width * (0.13 + depth * 0.5) +
Math.sin(time * 0.00008 + yIndex * 0.45) * 7
);
}
function projectY(height: number, yIndex: number, time: number) {
const normalizedY = yIndex / (GRID_ROWS - 1);
const drift = Math.sin(time * 0.00012 + yIndex * 0.7) * 5;
return height * 0.24 + height * 0.82 * normalizedY * normalizedY + drift;
}
export function HeroCanvas() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const shouldReduceMotion = useReducedMotion();
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) {
return;
}
const context = canvas.getContext("2d", { alpha: true });
if (!context) {
return;
}
let width = 0;
let height = 0;
let dpr = 1;
let frameId = 0;
let isMounted = true;
const resize = () => {
const rect = canvas.getBoundingClientRect();
dpr = Math.min(window.devicePixelRatio || 1, 2);
width = Math.max(1, rect.width);
height = Math.max(1, rect.height);
canvas.width = Math.floor(width * dpr);
canvas.height = Math.floor(height * dpr);
context.setTransform(dpr, 0, 0, dpr, 0, 0);
if (shouldReduceMotion) {
draw(0);
}
};
const drawGridLines = (time: number) => {
context.lineWidth = 1;
context.strokeStyle = COLORS.line;
for (let column = 0; column < GRID_COLUMNS; column += 1) {
context.beginPath();
for (let row = 0; row < GRID_ROWS; row += 1) {
const x = projectX(width, column, row, time);
const y = projectY(height, row, time);
if (row === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
}
context.stroke();
}
for (let row = 0; row < GRID_ROWS; row += 1) {
context.beginPath();
for (let column = 0; column < GRID_COLUMNS; column += 1) {
const x = projectX(width, column, row, time);
const y = projectY(height, row, time);
if (column === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
}
context.stroke();
}
};
const drawDots = (time: number) => {
context.fillStyle = COLORS.dot;
for (let row = 0; row < GRID_ROWS; row += 1) {
const normalizedY = row / (GRID_ROWS - 1);
const radius = 0.9 + normalizedY * 1.15;
for (let column = 0; column < GRID_COLUMNS; column += 1) {
context.beginPath();
context.arc(
projectX(width, column, row, time),
projectY(height, row, time),
radius,
0,
Math.PI * 2,
);
context.fill();
}
}
};
const drawPulse = (time: number) => {
if (shouldReduceMotion) {
return;
}
const progress = (time % PULSE_PERIOD_MS) / PULSE_PERIOD_MS;
const row = 8;
const maxColumn = Math.min(
GRID_COLUMNS - 1,
Math.max(1, Math.floor(progress * (GRID_COLUMNS + 2))),
);
context.save();
context.globalAlpha = progress > 0.84 ? (1 - progress) / 0.16 : 1;
context.lineWidth = 2;
context.strokeStyle = COLORS.cyan;
context.shadowBlur = 14;
context.shadowColor = COLORS.cyan;
context.beginPath();
for (let column = 0; column <= maxColumn; column += 1) {
const x = projectX(width, column, row, time);
const y = projectY(height, row, time);
if (column === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
}
context.stroke();
context.restore();
};
const draw = (time: number) => {
context.clearRect(0, 0, width, height);
context.fillStyle = COLORS.bg;
context.fillRect(0, 0, width, height);
drawGridLines(time);
drawDots(time);
drawPulse(time);
};
const tick = (time: number) => {
if (!isMounted) {
return;
}
draw(time);
frameId = window.requestAnimationFrame(tick);
};
resize();
window.addEventListener("resize", resize, { passive: true });
if (!shouldReduceMotion) {
frameId = window.requestAnimationFrame(tick);
}
return () => {
isMounted = false;
window.removeEventListener("resize", resize);
if (frameId) {
window.cancelAnimationFrame(frameId);
}
};
}, [shouldReduceMotion]);
return (
<canvas
ref={canvasRef}
aria-hidden="true"
className="pointer-events-none absolute inset-0 h-full w-full opacity-[0.32]"
/>
);
}