mirror of
https://github.com/timmypidashev/web.git
synced 2026-04-14 11:03:50 +00:00
Add interactivity to the background animation:
This commit is contained in:
@@ -4,6 +4,7 @@ interface Cell {
|
|||||||
alive: boolean;
|
alive: boolean;
|
||||||
next: boolean;
|
next: boolean;
|
||||||
color: [number, number, number];
|
color: [number, number, number];
|
||||||
|
baseColor: [number, number, number]; // Original color
|
||||||
currentX: number;
|
currentX: number;
|
||||||
currentY: number;
|
currentY: number;
|
||||||
targetX: number;
|
targetX: number;
|
||||||
@@ -12,8 +13,11 @@ interface Cell {
|
|||||||
targetOpacity: number;
|
targetOpacity: number;
|
||||||
scale: number;
|
scale: number;
|
||||||
targetScale: number;
|
targetScale: number;
|
||||||
|
elevation: number; // For 3D effect
|
||||||
|
targetElevation: number;
|
||||||
transitioning: boolean;
|
transitioning: boolean;
|
||||||
transitionComplete: boolean;
|
transitionComplete: boolean;
|
||||||
|
rippleEffect: number; // For ripple animation
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Grid {
|
interface Grid {
|
||||||
@@ -24,17 +28,30 @@ interface Grid {
|
|||||||
offsetY: number;
|
offsetY: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MousePosition {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
isDown: boolean;
|
||||||
|
lastClickTime: number;
|
||||||
|
cellX: number;
|
||||||
|
cellY: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface BackgroundProps {
|
interface BackgroundProps {
|
||||||
layout?: 'index' | 'sidebar';
|
layout?: 'index' | 'sidebar';
|
||||||
position?: 'left' | 'right';
|
position?: 'left' | 'right';
|
||||||
}
|
}
|
||||||
|
|
||||||
const CELL_SIZE = 25;
|
const CELL_SIZE = 25;
|
||||||
const TRANSITION_SPEED = 0.05; // Reduced from 0.1 for slower animations
|
const TRANSITION_SPEED = 0.05;
|
||||||
const SCALE_SPEED = 0.05; // Reduced from 0.15 for slower animations
|
const SCALE_SPEED = 0.05;
|
||||||
const CYCLE_FRAMES = 180; // Increased from 120 to give more time for transitions
|
const CYCLE_FRAMES = 180;
|
||||||
const INITIAL_DENSITY = 0.15;
|
const INITIAL_DENSITY = 0.15;
|
||||||
const SIDEBAR_WIDTH = 240;
|
const SIDEBAR_WIDTH = 240;
|
||||||
|
const MOUSE_INFLUENCE_RADIUS = 150; // Radius of mouse influence in pixels
|
||||||
|
const COLOR_SHIFT_AMOUNT = 30; // Maximum color shift amount
|
||||||
|
const RIPPLE_SPEED = 0.2; // Speed of ripple propagation
|
||||||
|
const ELEVATION_FACTOR = 15; // Max height for 3D effect
|
||||||
|
|
||||||
const Background: React.FC<BackgroundProps> = ({
|
const Background: React.FC<BackgroundProps> = ({
|
||||||
layout = 'index',
|
layout = 'index',
|
||||||
@@ -45,6 +62,14 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
const animationFrameRef = useRef<number>();
|
const animationFrameRef = useRef<number>();
|
||||||
const frameCount = useRef(0);
|
const frameCount = useRef(0);
|
||||||
const resizeTimeoutRef = useRef<NodeJS.Timeout>();
|
const resizeTimeoutRef = useRef<NodeJS.Timeout>();
|
||||||
|
const mouseRef = useRef<MousePosition>({
|
||||||
|
x: -1000,
|
||||||
|
y: -1000,
|
||||||
|
isDown: false,
|
||||||
|
lastClickTime: 0,
|
||||||
|
cellX: -1,
|
||||||
|
cellY: -1
|
||||||
|
});
|
||||||
|
|
||||||
const randomColor = (): [number, number, number] => {
|
const randomColor = (): [number, number, number] => {
|
||||||
const colors = [
|
const colors = [
|
||||||
@@ -70,10 +95,13 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
const { cols, rows, offsetX, offsetY } = calculateGridDimensions(width, height);
|
const { cols, rows, offsetX, offsetY } = calculateGridDimensions(width, height);
|
||||||
|
|
||||||
const cells = Array(cols).fill(0).map((_, i) =>
|
const cells = Array(cols).fill(0).map((_, i) =>
|
||||||
Array(rows).fill(0).map((_, j) => ({
|
Array(rows).fill(0).map((_, j) => {
|
||||||
|
const baseColor = randomColor();
|
||||||
|
return {
|
||||||
alive: Math.random() < INITIAL_DENSITY,
|
alive: Math.random() < INITIAL_DENSITY,
|
||||||
next: false,
|
next: false,
|
||||||
color: randomColor(),
|
color: [...baseColor] as [number, number, number],
|
||||||
|
baseColor: baseColor,
|
||||||
currentX: i,
|
currentX: i,
|
||||||
currentY: j,
|
currentY: j,
|
||||||
targetX: i,
|
targetX: i,
|
||||||
@@ -82,9 +110,13 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
targetOpacity: 0,
|
targetOpacity: 0,
|
||||||
scale: 0,
|
scale: 0,
|
||||||
targetScale: 0,
|
targetScale: 0,
|
||||||
|
elevation: 0,
|
||||||
|
targetElevation: 0,
|
||||||
transitioning: false,
|
transitioning: false,
|
||||||
transitionComplete: false
|
transitionComplete: false,
|
||||||
}))
|
rippleEffect: 0
|
||||||
|
};
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const grid = { cells, cols, rows, offsetX, offsetY };
|
const grid = { cells, cols, rows, offsetX, offsetY };
|
||||||
@@ -119,11 +151,9 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
const col = (x + i + grid.cols) % grid.cols;
|
const col = (x + i + grid.cols) % grid.cols;
|
||||||
const row = (y + j + grid.rows) % grid.rows;
|
const row = (y + j + grid.rows) % grid.rows;
|
||||||
|
|
||||||
// Only count cells that are actually alive according to the game state
|
|
||||||
// Not cells that are just visually transitioning
|
|
||||||
if (grid.cells[col][row].alive) {
|
if (grid.cells[col][row].alive) {
|
||||||
neighbors.count++;
|
neighbors.count++;
|
||||||
neighbors.colors.push(grid.cells[col][row].color);
|
neighbors.colors.push(grid.cells[col][row].baseColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +188,8 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
cell.next = count === 3;
|
cell.next = count === 3;
|
||||||
if (cell.next) {
|
if (cell.next) {
|
||||||
cell.color = averageColors(colors);
|
cell.baseColor = averageColors(colors);
|
||||||
|
cell.color = [...cell.baseColor];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,9 +211,11 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
if (!cell.next) {
|
if (!cell.next) {
|
||||||
cell.targetScale = 0;
|
cell.targetScale = 0;
|
||||||
cell.targetOpacity = 0;
|
cell.targetOpacity = 0;
|
||||||
|
cell.targetElevation = 0;
|
||||||
} else {
|
} else {
|
||||||
cell.targetScale = 1;
|
cell.targetScale = 1;
|
||||||
cell.targetOpacity = 1;
|
cell.targetOpacity = 1;
|
||||||
|
cell.targetElevation = 0;
|
||||||
}
|
}
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
@@ -190,7 +223,59 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createRippleEffect = (grid: Grid, centerX: number, centerY: number) => {
|
||||||
|
const maxDistance = Math.max(grid.cols, grid.rows) / 2;
|
||||||
|
|
||||||
|
for (let i = 0; i < grid.cols; i++) {
|
||||||
|
for (let j = 0; j < grid.rows; j++) {
|
||||||
|
const cell = grid.cells[i][j];
|
||||||
|
|
||||||
|
// Calculate distance from cell to ripple center
|
||||||
|
const dx = i - centerX;
|
||||||
|
const dy = j - centerY;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
// Only apply ripple to visible cells
|
||||||
|
if (cell.opacity > 0.1) {
|
||||||
|
// Delayed animation based on distance from center
|
||||||
|
setTimeout(() => {
|
||||||
|
cell.rippleEffect = 1; // Start ripple
|
||||||
|
|
||||||
|
// After a short time, reset ripple
|
||||||
|
setTimeout(() => {
|
||||||
|
cell.rippleEffect = 0;
|
||||||
|
}, 300 + distance * 50);
|
||||||
|
}, distance * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const spawnCellAtPosition = (grid: Grid, x: number, y: number) => {
|
||||||
|
if (x >= 0 && x < grid.cols && y >= 0 && y < grid.rows) {
|
||||||
|
const cell = grid.cells[x][y];
|
||||||
|
|
||||||
|
if (!cell.alive && !cell.transitioning) {
|
||||||
|
cell.alive = true;
|
||||||
|
cell.next = true;
|
||||||
|
cell.transitioning = true;
|
||||||
|
cell.transitionComplete = false;
|
||||||
|
cell.baseColor = randomColor();
|
||||||
|
cell.color = [...cell.baseColor];
|
||||||
|
cell.targetScale = 1;
|
||||||
|
cell.targetOpacity = 1;
|
||||||
|
cell.targetElevation = 0;
|
||||||
|
|
||||||
|
// Create a small ripple from the new cell
|
||||||
|
createRippleEffect(grid, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const updateCellAnimations = (grid: Grid) => {
|
const updateCellAnimations = (grid: Grid) => {
|
||||||
|
const mouseX = mouseRef.current.x;
|
||||||
|
const mouseY = mouseRef.current.y;
|
||||||
|
|
||||||
for (let i = 0; i < grid.cols; i++) {
|
for (let i = 0; i < grid.cols; i++) {
|
||||||
for (let j = 0; j < grid.rows; j++) {
|
for (let j = 0; j < grid.rows; j++) {
|
||||||
const cell = grid.cells[i][j];
|
const cell = grid.cells[i][j];
|
||||||
@@ -198,7 +283,44 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
// Smooth transitions
|
// Smooth transitions
|
||||||
cell.opacity += (cell.targetOpacity - cell.opacity) * TRANSITION_SPEED;
|
cell.opacity += (cell.targetOpacity - cell.opacity) * TRANSITION_SPEED;
|
||||||
cell.scale += (cell.targetScale - cell.scale) * SCALE_SPEED;
|
cell.scale += (cell.targetScale - cell.scale) * SCALE_SPEED;
|
||||||
|
cell.elevation += (cell.targetElevation - cell.elevation) * SCALE_SPEED;
|
||||||
|
|
||||||
|
// Apply mouse interaction
|
||||||
|
const cellCenterX = grid.offsetX + i * CELL_SIZE + CELL_SIZE / 2;
|
||||||
|
const cellCenterY = grid.offsetY + j * CELL_SIZE + CELL_SIZE / 2;
|
||||||
|
const dx = cellCenterX - mouseX;
|
||||||
|
const dy = cellCenterY - mouseY;
|
||||||
|
const distanceToMouse = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
// Color wave effect based on mouse position
|
||||||
|
if (distanceToMouse < MOUSE_INFLUENCE_RADIUS && cell.opacity > 0.1) {
|
||||||
|
// Calculate color adjustment based on distance
|
||||||
|
const influenceFactor = 1 - (distanceToMouse / MOUSE_INFLUENCE_RADIUS);
|
||||||
|
|
||||||
|
// Wave effect with sine function
|
||||||
|
const waveOffset = (frameCount.current * 0.05 + distanceToMouse * 0.05) % (Math.PI * 2);
|
||||||
|
const waveFactor = (Math.sin(waveOffset) * 0.5 + 0.5) * influenceFactor;
|
||||||
|
|
||||||
|
// Adjust color based on wave
|
||||||
|
cell.color = [
|
||||||
|
Math.min(255, Math.max(0, cell.baseColor[0] + COLOR_SHIFT_AMOUNT * waveFactor)),
|
||||||
|
Math.min(255, Math.max(0, cell.baseColor[1] - COLOR_SHIFT_AMOUNT * waveFactor)),
|
||||||
|
Math.min(255, Math.max(0, cell.baseColor[2] + COLOR_SHIFT_AMOUNT * waveFactor))
|
||||||
|
] as [number, number, number];
|
||||||
|
|
||||||
|
// 3D elevation effect when mouse is close
|
||||||
|
cell.targetElevation = ELEVATION_FACTOR * influenceFactor;
|
||||||
|
} else {
|
||||||
|
// Gradually return to base color when mouse is away
|
||||||
|
cell.color[0] += (cell.baseColor[0] - cell.color[0]) * 0.1;
|
||||||
|
cell.color[1] += (cell.baseColor[1] - cell.color[1]) * 0.1;
|
||||||
|
cell.color[2] += (cell.baseColor[2] - cell.color[2]) * 0.1;
|
||||||
|
|
||||||
|
// Reset elevation when mouse moves away
|
||||||
|
cell.targetElevation = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle cell state transitions
|
||||||
if (cell.transitioning) {
|
if (cell.transitioning) {
|
||||||
// When a cell is completely faded out, update its alive state
|
// When a cell is completely faded out, update its alive state
|
||||||
if (!cell.next && cell.opacity < 0.01 && cell.scale < 0.01) {
|
if (!cell.next && cell.opacity < 0.01 && cell.scale < 0.01) {
|
||||||
@@ -207,6 +329,7 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
cell.transitionComplete = true;
|
cell.transitionComplete = true;
|
||||||
cell.opacity = 0;
|
cell.opacity = 0;
|
||||||
cell.scale = 0;
|
cell.scale = 0;
|
||||||
|
cell.elevation = 0;
|
||||||
}
|
}
|
||||||
// When a new cell is born
|
// When a new cell is born
|
||||||
else if (cell.next && !cell.alive && !cell.transitionComplete) {
|
else if (cell.next && !cell.alive && !cell.transitionComplete) {
|
||||||
@@ -215,8 +338,66 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
cell.transitionComplete = true;
|
cell.transitionComplete = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gradually decrease ripple effect
|
||||||
|
if (cell.rippleEffect > 0) {
|
||||||
|
cell.rippleEffect = Math.max(0, cell.rippleEffect - RIPPLE_SPEED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
|
if (!gridRef.current || !canvasRef.current) return;
|
||||||
|
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
const mouseX = e.clientX - rect.left;
|
||||||
|
const mouseY = e.clientY - rect.top;
|
||||||
|
|
||||||
|
mouseRef.current.isDown = true;
|
||||||
|
mouseRef.current.lastClickTime = Date.now();
|
||||||
|
|
||||||
|
const grid = gridRef.current;
|
||||||
|
|
||||||
|
// Calculate which cell was clicked
|
||||||
|
const cellX = Math.floor((mouseX - grid.offsetX) / CELL_SIZE);
|
||||||
|
const cellY = Math.floor((mouseY - grid.offsetY) / CELL_SIZE);
|
||||||
|
|
||||||
|
if (cellX >= 0 && cellX < grid.cols && cellY >= 0 && cellY < grid.rows) {
|
||||||
|
mouseRef.current.cellX = cellX;
|
||||||
|
mouseRef.current.cellY = cellY;
|
||||||
|
|
||||||
|
const cell = grid.cells[cellX][cellY];
|
||||||
|
|
||||||
|
if (cell.alive) {
|
||||||
|
// Create ripple effect from existing cell
|
||||||
|
createRippleEffect(grid, cellX, cellY);
|
||||||
|
} else {
|
||||||
|
// Spawn new cell at empty position
|
||||||
|
spawnCellAtPosition(grid, cellX, cellY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
if (!canvasRef.current) return;
|
||||||
|
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
|
||||||
|
mouseRef.current.x = e.clientX - rect.left;
|
||||||
|
mouseRef.current.y = e.clientY - rect.top;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
mouseRef.current.isDown = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
mouseRef.current.isDown = false;
|
||||||
|
mouseRef.current.x = -1000;
|
||||||
|
mouseRef.current.y = -1000;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupCanvas = (canvas: HTMLCanvasElement, width: number, height: number) => {
|
const setupCanvas = (canvas: HTMLCanvasElement, width: number, height: number) => {
|
||||||
@@ -280,6 +461,12 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
gridRef.current = initGrid(displayWidth, displayHeight);
|
gridRef.current = initGrid(displayWidth, displayHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add mouse event listeners
|
||||||
|
canvas.addEventListener('mousedown', handleMouseDown, { signal });
|
||||||
|
canvas.addEventListener('mousemove', handleMouseMove, { signal });
|
||||||
|
canvas.addEventListener('mouseup', handleMouseUp, { signal });
|
||||||
|
canvas.addEventListener('mouseleave', handleMouseLeave, { signal });
|
||||||
|
|
||||||
const animate = () => {
|
const animate = () => {
|
||||||
if (signal.aborted) return;
|
if (signal.aborted) return;
|
||||||
|
|
||||||
@@ -310,16 +497,71 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
if ((cell.alive || cell.targetOpacity > 0) && cell.opacity > 0.01) {
|
if ((cell.alive || cell.targetOpacity > 0) && cell.opacity > 0.01) {
|
||||||
const [r, g, b] = cell.color;
|
const [r, g, b] = cell.color;
|
||||||
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
||||||
ctx.globalAlpha = cell.opacity * 0.8;
|
|
||||||
|
// Apply ripple and elevation effects to opacity
|
||||||
|
const rippleBoost = cell.rippleEffect * 0.4; // Boost opacity during ripple
|
||||||
|
ctx.globalAlpha = Math.min(1, cell.opacity * 0.8 + rippleBoost);
|
||||||
|
|
||||||
const scaledSize = cellSize * cell.scale;
|
const scaledSize = cellSize * cell.scale;
|
||||||
const xOffset = (cellSize - scaledSize) / 2;
|
const xOffset = (cellSize - scaledSize) / 2;
|
||||||
const yOffset = (cellSize - scaledSize) / 2;
|
const yOffset = (cellSize - scaledSize) / 2;
|
||||||
|
|
||||||
|
// Apply 3D elevation effect
|
||||||
|
const elevationOffset = cell.elevation;
|
||||||
|
|
||||||
const x = grid.offsetX + i * CELL_SIZE + (CELL_SIZE - cellSize) / 2 + xOffset;
|
const x = grid.offsetX + i * CELL_SIZE + (CELL_SIZE - cellSize) / 2 + xOffset;
|
||||||
const y = grid.offsetY + j * CELL_SIZE + (CELL_SIZE - cellSize) / 2 + yOffset;
|
const y = grid.offsetY + j * CELL_SIZE + (CELL_SIZE - cellSize) / 2 + yOffset - elevationOffset;
|
||||||
const scaledRoundness = roundness * cell.scale;
|
const scaledRoundness = roundness * cell.scale;
|
||||||
|
|
||||||
|
// Draw shadow for 3D effect if cell has elevation
|
||||||
|
if (elevationOffset > 1) {
|
||||||
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x + scaledRoundness, y + elevationOffset + 5);
|
||||||
|
ctx.lineTo(x + scaledSize - scaledRoundness, y + elevationOffset + 5);
|
||||||
|
ctx.quadraticCurveTo(x + scaledSize, y + elevationOffset + 5, x + scaledSize, y + elevationOffset + 5 + scaledRoundness);
|
||||||
|
ctx.lineTo(x + scaledSize, y + elevationOffset + 5 + scaledSize - scaledRoundness);
|
||||||
|
ctx.quadraticCurveTo(x + scaledSize, y + elevationOffset + 5 + scaledSize, x + scaledSize - scaledRoundness, y + elevationOffset + 5 + scaledSize);
|
||||||
|
ctx.lineTo(x + scaledRoundness, y + elevationOffset + 5 + scaledSize);
|
||||||
|
ctx.quadraticCurveTo(x, y + elevationOffset + 5 + scaledSize, x, y + elevationOffset + 5 + scaledSize - scaledRoundness);
|
||||||
|
ctx.lineTo(x, y + elevationOffset + 5 + scaledRoundness);
|
||||||
|
ctx.quadraticCurveTo(x, y + elevationOffset + 5, x + scaledRoundness, y + elevationOffset + 5);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Draw side of elevated cell
|
||||||
|
const sideHeight = elevationOffset;
|
||||||
|
ctx.fillStyle = `rgba(${r*0.7}, ${g*0.7}, ${b*0.7}, ${ctx.globalAlpha})`;
|
||||||
|
|
||||||
|
// Left side
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, y + scaledRoundness);
|
||||||
|
ctx.lineTo(x, y + scaledSize - scaledRoundness + sideHeight);
|
||||||
|
ctx.lineTo(x + scaledRoundness, y + scaledSize + sideHeight);
|
||||||
|
ctx.lineTo(x + scaledRoundness, y + scaledSize);
|
||||||
|
ctx.lineTo(x, y + scaledSize - scaledRoundness);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Right side
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x + scaledSize, y + scaledRoundness);
|
||||||
|
ctx.lineTo(x + scaledSize, y + scaledSize - scaledRoundness + sideHeight);
|
||||||
|
ctx.lineTo(x + scaledSize - scaledRoundness, y + scaledSize + sideHeight);
|
||||||
|
ctx.lineTo(x + scaledSize - scaledRoundness, y + scaledSize);
|
||||||
|
ctx.lineTo(x + scaledSize, y + scaledSize - scaledRoundness);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Bottom side
|
||||||
|
ctx.fillStyle = `rgba(${r*0.5}, ${g*0.5}, ${b*0.5}, ${ctx.globalAlpha})`;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x + scaledRoundness, y + scaledSize);
|
||||||
|
ctx.lineTo(x + scaledSize - scaledRoundness, y + scaledSize);
|
||||||
|
ctx.lineTo(x + scaledSize - scaledRoundness, y + scaledSize + sideHeight);
|
||||||
|
ctx.lineTo(x + scaledRoundness, y + scaledSize + sideHeight);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw main cell with original color
|
||||||
|
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(x + scaledRoundness, y);
|
ctx.moveTo(x + scaledRoundness, y);
|
||||||
ctx.lineTo(x + scaledSize - scaledRoundness, y);
|
ctx.lineTo(x + scaledSize - scaledRoundness, y);
|
||||||
@@ -331,6 +573,38 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
ctx.lineTo(x, y + scaledRoundness);
|
ctx.lineTo(x, y + scaledRoundness);
|
||||||
ctx.quadraticCurveTo(x, y, x + scaledRoundness, y);
|
ctx.quadraticCurveTo(x, y, x + scaledRoundness, y);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
|
// Draw highlight on top for 3D effect
|
||||||
|
if (elevationOffset > 1) {
|
||||||
|
ctx.fillStyle = `rgba(255, 255, 255, ${0.2 * elevationOffset / ELEVATION_FACTOR})`;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x + scaledRoundness, y);
|
||||||
|
ctx.lineTo(x + scaledSize - scaledRoundness, y);
|
||||||
|
ctx.quadraticCurveTo(x + scaledSize, y, x + scaledSize, y + scaledRoundness);
|
||||||
|
ctx.lineTo(x + scaledSize, y + scaledSize/3);
|
||||||
|
ctx.lineTo(x, y + scaledSize/3);
|
||||||
|
ctx.lineTo(x, y + scaledRoundness);
|
||||||
|
ctx.quadraticCurveTo(x, y, x + scaledRoundness, y);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw ripple effect
|
||||||
|
if (cell.rippleEffect > 0) {
|
||||||
|
const rippleRadius = cell.rippleEffect * cellSize * 2;
|
||||||
|
const rippleAlpha = (1 - cell.rippleEffect) * 0.5;
|
||||||
|
|
||||||
|
ctx.strokeStyle = `rgba(255, 255, 255, ${rippleAlpha})`;
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(
|
||||||
|
x + scaledSize / 2,
|
||||||
|
y + scaledSize / 2,
|
||||||
|
rippleRadius,
|
||||||
|
0,
|
||||||
|
Math.PI * 2
|
||||||
|
);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,7 +635,7 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
return 'fixed inset-0 -z-10';
|
return 'fixed inset-0 -z-10';
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseClasses = 'fixed top-0 bottom-0 hidden lg:block -z-10 pointer-events-none';
|
const baseClasses = 'fixed top-0 bottom-0 hidden lg:block -z-10';
|
||||||
return position === 'left'
|
return position === 'left'
|
||||||
? `${baseClasses} left-0`
|
? `${baseClasses} left-0`
|
||||||
: `${baseClasses} right-0`;
|
: `${baseClasses} right-0`;
|
||||||
@@ -371,9 +645,9 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
<div className={getContainerClasses()}>
|
<div className={getContainerClasses()}>
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
className="w-full h-full bg-black"
|
className="w-full h-full bg-black cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-black/30 backdrop-blur-sm" />
|
<div className="absolute inset-0 bg-black/30 backdrop-blur-sm pointer-events-none" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user