mirror of
https://github.com/timmypidashev/web.git
synced 2026-04-14 11:03:50 +00:00
Remove cursor; update background
This commit is contained in:
@@ -18,6 +18,8 @@ interface Cell {
|
|||||||
transitioning: boolean;
|
transitioning: boolean;
|
||||||
transitionComplete: boolean;
|
transitionComplete: boolean;
|
||||||
rippleEffect: number; // For ripple animation
|
rippleEffect: number; // For ripple animation
|
||||||
|
rippleStartTime: number; // When ripple started
|
||||||
|
rippleDistance: number; // Distance from ripple center
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Grid {
|
interface Grid {
|
||||||
@@ -42,16 +44,19 @@ interface BackgroundProps {
|
|||||||
position?: 'left' | 'right';
|
position?: 'left' | 'right';
|
||||||
}
|
}
|
||||||
|
|
||||||
const CELL_SIZE = 25;
|
const CELL_SIZE_MOBILE = 15;
|
||||||
|
const CELL_SIZE_DESKTOP = 25;
|
||||||
|
const TARGET_FPS = 60; // Target frame rate
|
||||||
|
const CYCLE_TIME = 3000; // 3 seconds per full cycle, regardless of FPS
|
||||||
const TRANSITION_SPEED = 0.05;
|
const TRANSITION_SPEED = 0.05;
|
||||||
const SCALE_SPEED = 0.05;
|
const SCALE_SPEED = 0.05;
|
||||||
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 MOUSE_INFLUENCE_RADIUS = 150; // Radius of mouse influence in pixels
|
||||||
const COLOR_SHIFT_AMOUNT = 30; // Maximum color shift amount
|
const COLOR_SHIFT_AMOUNT = 30; // Maximum color shift amount
|
||||||
const RIPPLE_SPEED = 0.2; // Speed of ripple propagation
|
const RIPPLE_SPEED = 0.02; // Speed of ripple propagation
|
||||||
const ELEVATION_FACTOR = 15; // Max height for 3D effect
|
const RIPPLE_ELEVATION_FACTOR = 4; // Height of ripple wave
|
||||||
|
const ELEVATION_FACTOR = 8; // Max height for 3D effect - reduced for more subtle effect
|
||||||
|
|
||||||
const Background: React.FC<BackgroundProps> = ({
|
const Background: React.FC<BackgroundProps> = ({
|
||||||
layout = 'index',
|
layout = 'index',
|
||||||
@@ -60,7 +65,8 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
const gridRef = useRef<Grid>();
|
const gridRef = useRef<Grid>();
|
||||||
const animationFrameRef = useRef<number>();
|
const animationFrameRef = useRef<number>();
|
||||||
const frameCount = useRef(0);
|
const lastUpdateTimeRef = useRef<number>(0);
|
||||||
|
const lastCycleTimeRef = useRef<number>(0);
|
||||||
const resizeTimeoutRef = useRef<NodeJS.Timeout>();
|
const resizeTimeoutRef = useRef<NodeJS.Timeout>();
|
||||||
const mouseRef = useRef<MousePosition>({
|
const mouseRef = useRef<MousePosition>({
|
||||||
x: -1000,
|
x: -1000,
|
||||||
@@ -83,11 +89,18 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
return colors[Math.floor(Math.random() * colors.length)];
|
return colors[Math.floor(Math.random() * colors.length)];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCellSize = () => {
|
||||||
|
// Check if we're on mobile based on screen width
|
||||||
|
const isMobile = window.innerWidth <= 768;
|
||||||
|
return isMobile ? CELL_SIZE_MOBILE : CELL_SIZE_DESKTOP;
|
||||||
|
};
|
||||||
|
|
||||||
const calculateGridDimensions = (width: number, height: number) => {
|
const calculateGridDimensions = (width: number, height: number) => {
|
||||||
const cols = Math.floor(width / CELL_SIZE);
|
const cellSize = getCellSize();
|
||||||
const rows = Math.floor(height / CELL_SIZE);
|
const cols = Math.floor(width / cellSize);
|
||||||
const offsetX = Math.floor((width - (cols * CELL_SIZE)) / 2);
|
const rows = Math.floor(height / cellSize);
|
||||||
const offsetY = Math.floor((height - (rows * CELL_SIZE)) / 2);
|
const offsetX = Math.floor((width - (cols * cellSize)) / 2);
|
||||||
|
const offsetY = Math.floor((height - (rows * cellSize)) / 2);
|
||||||
return { cols, rows, offsetX, offsetY };
|
return { cols, rows, offsetX, offsetY };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,7 +127,9 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
targetElevation: 0,
|
targetElevation: 0,
|
||||||
transitioning: false,
|
transitioning: false,
|
||||||
transitionComplete: false,
|
transitionComplete: false,
|
||||||
rippleEffect: 0
|
rippleEffect: 0,
|
||||||
|
rippleStartTime: 0,
|
||||||
|
rippleDistance: 0
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -224,7 +239,7 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createRippleEffect = (grid: Grid, centerX: number, centerY: number) => {
|
const createRippleEffect = (grid: Grid, centerX: number, centerY: number) => {
|
||||||
const maxDistance = Math.max(grid.cols, grid.rows) / 2;
|
const currentTime = Date.now();
|
||||||
|
|
||||||
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++) {
|
||||||
@@ -237,15 +252,9 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
|
|
||||||
// Only apply ripple to visible cells
|
// Only apply ripple to visible cells
|
||||||
if (cell.opacity > 0.1) {
|
if (cell.opacity > 0.1) {
|
||||||
// Delayed animation based on distance from center
|
cell.rippleStartTime = currentTime + distance * 100; // Delayed start based on distance
|
||||||
setTimeout(() => {
|
cell.rippleDistance = distance;
|
||||||
cell.rippleEffect = 1; // Start ripple
|
cell.rippleEffect = 0;
|
||||||
|
|
||||||
// After a short time, reset ripple
|
|
||||||
setTimeout(() => {
|
|
||||||
cell.rippleEffect = 0;
|
|
||||||
}, 300 + distance * 50);
|
|
||||||
}, distance * 100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,51 +281,51 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateCellAnimations = (grid: Grid) => {
|
const updateCellAnimations = (grid: Grid, deltaTime: number) => {
|
||||||
const mouseX = mouseRef.current.x;
|
const mouseX = mouseRef.current.x;
|
||||||
const mouseY = mouseRef.current.y;
|
const mouseY = mouseRef.current.y;
|
||||||
|
const cellSize = getCellSize();
|
||||||
|
|
||||||
|
// Adjust transition speeds based on time
|
||||||
|
const transitionFactor = TRANSITION_SPEED * (deltaTime / (1000 / TARGET_FPS));
|
||||||
|
const scaleFactor = SCALE_SPEED * (deltaTime / (1000 / TARGET_FPS));
|
||||||
|
|
||||||
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];
|
||||||
|
|
||||||
// Smooth transitions
|
// Smooth transitions
|
||||||
cell.opacity += (cell.targetOpacity - cell.opacity) * TRANSITION_SPEED;
|
cell.opacity += (cell.targetOpacity - cell.opacity) * transitionFactor;
|
||||||
cell.scale += (cell.targetScale - cell.scale) * SCALE_SPEED;
|
cell.scale += (cell.targetScale - cell.scale) * scaleFactor;
|
||||||
cell.elevation += (cell.targetElevation - cell.elevation) * SCALE_SPEED;
|
cell.elevation += (cell.targetElevation - cell.elevation) * scaleFactor;
|
||||||
|
|
||||||
// Apply mouse interaction
|
// Apply mouse interaction
|
||||||
const cellCenterX = grid.offsetX + i * CELL_SIZE + CELL_SIZE / 2;
|
const cellCenterX = grid.offsetX + i * cellSize + cellSize / 2;
|
||||||
const cellCenterY = grid.offsetY + j * CELL_SIZE + CELL_SIZE / 2;
|
const cellCenterY = grid.offsetY + j * cellSize + cellSize / 2;
|
||||||
const dx = cellCenterX - mouseX;
|
const dx = cellCenterX - mouseX;
|
||||||
const dy = cellCenterY - mouseY;
|
const dy = cellCenterY - mouseY;
|
||||||
const distanceToMouse = Math.sqrt(dx * dx + dy * dy);
|
const distanceToMouse = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
// Color wave effect based on mouse position
|
// 3D hill effect based on mouse position
|
||||||
if (distanceToMouse < MOUSE_INFLUENCE_RADIUS && cell.opacity > 0.1) {
|
if (distanceToMouse < MOUSE_INFLUENCE_RADIUS && cell.opacity > 0.1) {
|
||||||
// Calculate color adjustment based on distance
|
// Calculate height based on distance - peak at center, gradually decreasing
|
||||||
const influenceFactor = 1 - (distanceToMouse / MOUSE_INFLUENCE_RADIUS);
|
const influenceFactor = Math.cos((distanceToMouse / MOUSE_INFLUENCE_RADIUS) * Math.PI / 2);
|
||||||
|
// Only positive elevation (growing upward)
|
||||||
|
cell.targetElevation = ELEVATION_FACTOR * influenceFactor * influenceFactor; // squared for more pronounced effect
|
||||||
|
|
||||||
// Wave effect with sine function
|
// Slight color shift as cells rise
|
||||||
const waveOffset = (frameCount.current * 0.05 + distanceToMouse * 0.05) % (Math.PI * 2);
|
const colorShift = influenceFactor * COLOR_SHIFT_AMOUNT * 0.5;
|
||||||
const waveFactor = (Math.sin(waveOffset) * 0.5 + 0.5) * influenceFactor;
|
|
||||||
|
|
||||||
// Adjust color based on wave
|
|
||||||
cell.color = [
|
cell.color = [
|
||||||
Math.min(255, Math.max(0, cell.baseColor[0] + COLOR_SHIFT_AMOUNT * waveFactor)),
|
Math.min(255, Math.max(0, cell.baseColor[0] + colorShift)),
|
||||||
Math.min(255, Math.max(0, cell.baseColor[1] - COLOR_SHIFT_AMOUNT * waveFactor)),
|
Math.min(255, Math.max(0, cell.baseColor[1] + colorShift)),
|
||||||
Math.min(255, Math.max(0, cell.baseColor[2] + COLOR_SHIFT_AMOUNT * waveFactor))
|
Math.min(255, Math.max(0, cell.baseColor[2] + colorShift))
|
||||||
] as [number, number, number];
|
] as [number, number, number];
|
||||||
|
|
||||||
// 3D elevation effect when mouse is close
|
|
||||||
cell.targetElevation = ELEVATION_FACTOR * influenceFactor;
|
|
||||||
} else {
|
} else {
|
||||||
// Gradually return to base color when mouse is away
|
// Gradually return to base color and zero elevation when mouse is away
|
||||||
cell.color[0] += (cell.baseColor[0] - cell.color[0]) * 0.1;
|
cell.color[0] += (cell.baseColor[0] - cell.color[0]) * 0.1;
|
||||||
cell.color[1] += (cell.baseColor[1] - cell.color[1]) * 0.1;
|
cell.color[1] += (cell.baseColor[1] - cell.color[1]) * 0.1;
|
||||||
cell.color[2] += (cell.baseColor[2] - cell.color[2]) * 0.1;
|
cell.color[2] += (cell.baseColor[2] - cell.color[2]) * 0.1;
|
||||||
|
|
||||||
// Reset elevation when mouse moves away
|
|
||||||
cell.targetElevation = 0;
|
cell.targetElevation = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,9 +348,34 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gradually decrease ripple effect
|
// Handle ripple animation
|
||||||
if (cell.rippleEffect > 0) {
|
if (cell.rippleStartTime > 0) {
|
||||||
cell.rippleEffect = Math.max(0, cell.rippleEffect - RIPPLE_SPEED);
|
const elapsedTime = Date.now() - cell.rippleStartTime;
|
||||||
|
if (elapsedTime > 0) {
|
||||||
|
// Calculate ripple progress (0 to 1)
|
||||||
|
const rippleProgress = elapsedTime / 1000; // 1 second for full animation
|
||||||
|
|
||||||
|
if (rippleProgress < 1) {
|
||||||
|
// Create a smooth wave effect
|
||||||
|
const wavePhase = rippleProgress * Math.PI * 2;
|
||||||
|
const waveHeight = Math.sin(wavePhase) * Math.exp(-rippleProgress * 4);
|
||||||
|
|
||||||
|
// Apply wave height to cell elevation only if it's not being overridden by mouse
|
||||||
|
if (distanceToMouse >= MOUSE_INFLUENCE_RADIUS) {
|
||||||
|
cell.rippleEffect = waveHeight;
|
||||||
|
cell.targetElevation = RIPPLE_ELEVATION_FACTOR * waveHeight;
|
||||||
|
} else {
|
||||||
|
cell.rippleEffect = waveHeight * 0.3; // Reduced effect when mouse is influencing
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Reset ripple effects
|
||||||
|
cell.rippleEffect = 0;
|
||||||
|
cell.rippleStartTime = 0;
|
||||||
|
if (distanceToMouse >= MOUSE_INFLUENCE_RADIUS) {
|
||||||
|
cell.targetElevation = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,6 +388,7 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
const rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
const mouseX = e.clientX - rect.left;
|
const mouseX = e.clientX - rect.left;
|
||||||
const mouseY = e.clientY - rect.top;
|
const mouseY = e.clientY - rect.top;
|
||||||
|
const cellSize = getCellSize();
|
||||||
|
|
||||||
mouseRef.current.isDown = true;
|
mouseRef.current.isDown = true;
|
||||||
mouseRef.current.lastClickTime = Date.now();
|
mouseRef.current.lastClickTime = Date.now();
|
||||||
@@ -361,8 +396,8 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
const grid = gridRef.current;
|
const grid = gridRef.current;
|
||||||
|
|
||||||
// Calculate which cell was clicked
|
// Calculate which cell was clicked
|
||||||
const cellX = Math.floor((mouseX - grid.offsetX) / CELL_SIZE);
|
const cellX = Math.floor((mouseX - grid.offsetX) / cellSize);
|
||||||
const cellY = Math.floor((mouseY - grid.offsetY) / CELL_SIZE);
|
const cellY = Math.floor((mouseY - grid.offsetY) / cellSize);
|
||||||
|
|
||||||
if (cellX >= 0 && cellX < grid.cols && cellY >= 0 && cellY < grid.rows) {
|
if (cellX >= 0 && cellX < grid.cols && cellY >= 0 && cellY < grid.rows) {
|
||||||
mouseRef.current.cellX = cellX;
|
mouseRef.current.cellX = cellX;
|
||||||
@@ -381,13 +416,37 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseMove = (e: MouseEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
if (!canvasRef.current) return;
|
if (!canvasRef.current || !gridRef.current) return;
|
||||||
|
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
const rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
const cellSize = getCellSize();
|
||||||
|
|
||||||
mouseRef.current.x = e.clientX - rect.left;
|
mouseRef.current.x = e.clientX - rect.left;
|
||||||
mouseRef.current.y = e.clientY - rect.top;
|
mouseRef.current.y = e.clientY - rect.top;
|
||||||
|
|
||||||
|
// Drawing functionality - place cells while dragging
|
||||||
|
if (mouseRef.current.isDown) {
|
||||||
|
const grid = gridRef.current;
|
||||||
|
|
||||||
|
// Calculate which cell the mouse is over
|
||||||
|
const cellX = Math.floor((mouseRef.current.x - grid.offsetX) / cellSize);
|
||||||
|
const cellY = Math.floor((mouseRef.current.y - grid.offsetY) / cellSize);
|
||||||
|
|
||||||
|
// Only draw if we're on a new cell
|
||||||
|
if (cellX !== mouseRef.current.cellX || cellY !== mouseRef.current.cellY) {
|
||||||
|
mouseRef.current.cellX = cellX;
|
||||||
|
mouseRef.current.cellY = cellY;
|
||||||
|
|
||||||
|
// Spawn cell at this position if it's empty
|
||||||
|
if (cellX >= 0 && cellX < grid.cols && cellY >= 0 && cellY < grid.rows) {
|
||||||
|
const cell = grid.cells[cellX][cellY];
|
||||||
|
if (!cell.alive && !cell.transitioning) {
|
||||||
|
spawnCellAtPosition(grid, cellX, cellY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
const handleMouseUp = () => {
|
||||||
@@ -439,12 +498,15 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
const ctx = setupCanvas(canvas, displayWidth, displayHeight);
|
const ctx = setupCanvas(canvas, displayWidth, displayHeight);
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
frameCount.current = 0;
|
lastUpdateTimeRef.current = 0;
|
||||||
|
lastCycleTimeRef.current = 0;
|
||||||
|
|
||||||
|
const cellSize = getCellSize();
|
||||||
|
|
||||||
// Only initialize new grid if one doesn't exist or dimensions changed
|
// Only initialize new grid if one doesn't exist or dimensions changed
|
||||||
if (!gridRef.current ||
|
if (!gridRef.current ||
|
||||||
gridRef.current.cols !== Math.floor(displayWidth / CELL_SIZE) ||
|
gridRef.current.cols !== Math.floor(displayWidth / cellSize) ||
|
||||||
gridRef.current.rows !== Math.floor(displayHeight / CELL_SIZE)) {
|
gridRef.current.rows !== Math.floor(displayHeight / cellSize)) {
|
||||||
gridRef.current = initGrid(displayWidth, displayHeight);
|
gridRef.current = initGrid(displayWidth, displayHeight);
|
||||||
}
|
}
|
||||||
}, 250);
|
}, 250);
|
||||||
@@ -467,18 +529,30 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
canvas.addEventListener('mouseup', handleMouseUp, { signal });
|
canvas.addEventListener('mouseup', handleMouseUp, { signal });
|
||||||
canvas.addEventListener('mouseleave', handleMouseLeave, { signal });
|
canvas.addEventListener('mouseleave', handleMouseLeave, { signal });
|
||||||
|
|
||||||
const animate = () => {
|
const animate = (currentTime: number) => {
|
||||||
if (signal.aborted) return;
|
if (signal.aborted) return;
|
||||||
|
|
||||||
frameCount.current++;
|
// Initialize timing if first frame
|
||||||
|
if (!lastUpdateTimeRef.current) {
|
||||||
|
lastUpdateTimeRef.current = currentTime;
|
||||||
|
lastCycleTimeRef.current = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate time since last frame
|
||||||
|
const deltaTime = currentTime - lastUpdateTimeRef.current;
|
||||||
|
lastUpdateTimeRef.current = currentTime;
|
||||||
|
|
||||||
|
// Calculate time since last cycle update
|
||||||
|
const cycleElapsed = currentTime - lastCycleTimeRef.current;
|
||||||
|
|
||||||
if (gridRef.current) {
|
if (gridRef.current) {
|
||||||
// Every CYCLE_FRAMES, compute the next state
|
// Check if it's time for the next life cycle
|
||||||
if (frameCount.current % CYCLE_FRAMES === 0) {
|
if (cycleElapsed >= CYCLE_TIME) {
|
||||||
computeNextState(gridRef.current);
|
computeNextState(gridRef.current);
|
||||||
|
lastCycleTimeRef.current = currentTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCellAnimations(gridRef.current);
|
updateCellAnimations(gridRef.current, deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw frame
|
// Draw frame
|
||||||
@@ -487,8 +561,9 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
|
|
||||||
if (gridRef.current) {
|
if (gridRef.current) {
|
||||||
const grid = gridRef.current;
|
const grid = gridRef.current;
|
||||||
const cellSize = CELL_SIZE * 0.8;
|
const cellSize = getCellSize();
|
||||||
const roundness = cellSize * 0.2;
|
const displayCellSize = cellSize * 0.8;
|
||||||
|
const roundness = displayCellSize * 0.2;
|
||||||
|
|
||||||
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++) {
|
||||||
@@ -496,71 +571,38 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
// Draw all transitioning cells, even if they're fading out
|
// Draw all transitioning cells, even if they're fading out
|
||||||
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})`;
|
|
||||||
|
|
||||||
// Apply ripple and elevation effects to opacity
|
// Base opacity
|
||||||
const rippleBoost = cell.rippleEffect * 0.4; // Boost opacity during ripple
|
ctx.globalAlpha = cell.opacity * 0.9;
|
||||||
ctx.globalAlpha = Math.min(1, cell.opacity * 0.8 + rippleBoost);
|
|
||||||
|
|
||||||
const scaledSize = cellSize * cell.scale;
|
const scaledSize = displayCellSize * cell.scale;
|
||||||
const xOffset = (cellSize - scaledSize) / 2;
|
const xOffset = (displayCellSize - scaledSize) / 2;
|
||||||
const yOffset = (cellSize - scaledSize) / 2;
|
const yOffset = (displayCellSize - scaledSize) / 2;
|
||||||
|
|
||||||
// Apply 3D elevation effect
|
// Apply 3D elevation effect
|
||||||
const elevationOffset = cell.elevation;
|
const elevationOffset = cell.elevation;
|
||||||
|
|
||||||
const x = grid.offsetX + i * CELL_SIZE + (CELL_SIZE - cellSize) / 2 + xOffset;
|
const x = grid.offsetX + i * cellSize + (cellSize - displayCellSize) / 2 + xOffset;
|
||||||
const y = grid.offsetY + j * CELL_SIZE + (CELL_SIZE - cellSize) / 2 + yOffset - elevationOffset;
|
const y = grid.offsetY + j * cellSize + (cellSize - displayCellSize) / 2 + yOffset - elevationOffset;
|
||||||
const scaledRoundness = roundness * cell.scale;
|
const scaledRoundness = roundness * cell.scale;
|
||||||
|
|
||||||
// Draw shadow for 3D effect if cell has elevation
|
// Draw shadow for 3D effect when cell is elevated
|
||||||
if (elevationOffset > 1) {
|
if (elevationOffset > 0.5) {
|
||||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
|
ctx.fillStyle = `rgba(0, 0, 0, ${0.2 * (elevationOffset / ELEVATION_FACTOR)})`;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(x + scaledRoundness, y + elevationOffset + 5);
|
ctx.moveTo(x + scaledRoundness, y + elevationOffset * 1.1);
|
||||||
ctx.lineTo(x + scaledSize - scaledRoundness, y + elevationOffset + 5);
|
ctx.lineTo(x + scaledSize - scaledRoundness, y + elevationOffset * 1.1);
|
||||||
ctx.quadraticCurveTo(x + scaledSize, y + elevationOffset + 5, x + scaledSize, y + elevationOffset + 5 + scaledRoundness);
|
ctx.quadraticCurveTo(x + scaledSize, y + elevationOffset * 1.1, x + scaledSize, y + elevationOffset * 1.1 + scaledRoundness);
|
||||||
ctx.lineTo(x + scaledSize, y + elevationOffset + 5 + scaledSize - scaledRoundness);
|
ctx.lineTo(x + scaledSize, y + elevationOffset * 1.1 + scaledSize - scaledRoundness);
|
||||||
ctx.quadraticCurveTo(x + scaledSize, y + elevationOffset + 5 + scaledSize, x + scaledSize - scaledRoundness, y + elevationOffset + 5 + scaledSize);
|
ctx.quadraticCurveTo(x + scaledSize, y + elevationOffset * 1.1 + scaledSize, x + scaledSize - scaledRoundness, y + elevationOffset * 1.1 + scaledSize);
|
||||||
ctx.lineTo(x + scaledRoundness, y + elevationOffset + 5 + scaledSize);
|
ctx.lineTo(x + scaledRoundness, y + elevationOffset * 1.1 + scaledSize);
|
||||||
ctx.quadraticCurveTo(x, y + elevationOffset + 5 + scaledSize, x, y + elevationOffset + 5 + scaledSize - scaledRoundness);
|
ctx.quadraticCurveTo(x, y + elevationOffset * 1.1 + scaledSize, x, y + elevationOffset * 1.1 + scaledSize - scaledRoundness);
|
||||||
ctx.lineTo(x, y + elevationOffset + 5 + scaledRoundness);
|
ctx.lineTo(x, y + elevationOffset * 1.1 + scaledRoundness);
|
||||||
ctx.quadraticCurveTo(x, y + elevationOffset + 5, x + scaledRoundness, y + elevationOffset + 5);
|
ctx.quadraticCurveTo(x, y + elevationOffset * 1.1, x + scaledRoundness, y + elevationOffset * 1.1);
|
||||||
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();
|
ctx.fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw main cell with original color
|
// Draw main cell
|
||||||
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(x + scaledRoundness, y);
|
ctx.moveTo(x + scaledRoundness, y);
|
||||||
@@ -574,9 +616,9 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
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
|
// Draw highlight on elevated cells
|
||||||
if (elevationOffset > 1) {
|
if (elevationOffset > 0.5) {
|
||||||
ctx.fillStyle = `rgba(255, 255, 255, ${0.2 * elevationOffset / ELEVATION_FACTOR})`;
|
ctx.fillStyle = `rgba(255, 255, 255, ${0.1 * (elevationOffset / ELEVATION_FACTOR)})`;
|
||||||
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);
|
||||||
@@ -588,23 +630,7 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw ripple effect
|
// No need for separate ripple drawing since the elevation handles the 3D 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -616,7 +642,7 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize, { signal });
|
window.addEventListener('resize', handleResize, { signal });
|
||||||
animate();
|
animate(performance.now());
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
controller.abort();
|
controller.abort();
|
||||||
@@ -645,7 +671,8 @@ const Background: React.FC<BackgroundProps> = ({
|
|||||||
<div className={getContainerClasses()}>
|
<div className={getContainerClasses()}>
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
className="w-full h-full bg-black cursor-pointer"
|
className="w-full h-full bg-black"
|
||||||
|
style={{ cursor: 'default' }} // Changed from cursor-pointer to default
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-black/30 backdrop-blur-sm pointer-events-none" />
|
<div className="absolute inset-0 bg-black/30 backdrop-blur-sm pointer-events-none" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,342 +0,0 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
|
||||||
|
|
||||||
interface CursorState {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
isPointer: boolean;
|
|
||||||
isClicking: boolean;
|
|
||||||
isOverBackground: boolean;
|
|
||||||
isMobile: boolean;
|
|
||||||
isOverGiscus: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TrailPoint {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
id: number;
|
|
||||||
color: string;
|
|
||||||
opacity: number;
|
|
||||||
scale: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Cursor: React.FC = () => {
|
|
||||||
const [state, setState] = useState<CursorState>({
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
isPointer: false,
|
|
||||||
isClicking: false,
|
|
||||||
isOverBackground: false,
|
|
||||||
isMobile: false,
|
|
||||||
isOverGiscus: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const [trail, setTrail] = useState<TrailPoint[]>([]);
|
|
||||||
const cursorRef = useRef<HTMLDivElement>(null);
|
|
||||||
const requestRef = useRef<number>();
|
|
||||||
const targetX = useRef(0);
|
|
||||||
const targetY = useRef(0);
|
|
||||||
const trailIdCounter = useRef(0);
|
|
||||||
const lastTrailTime = useRef(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Check if device is mobile or tablet
|
|
||||||
const checkMobile = () => {
|
|
||||||
const isMobileOrTablet = window.matchMedia('(max-width: 1024px)').matches ||
|
|
||||||
('ontouchstart' in window) ||
|
|
||||||
(navigator.maxTouchPoints > 0);
|
|
||||||
setState(prev => ({ ...prev, isMobile: isMobileOrTablet }));
|
|
||||||
};
|
|
||||||
|
|
||||||
checkMobile();
|
|
||||||
|
|
||||||
// Add resize listener to detect device changes
|
|
||||||
window.addEventListener('resize', checkMobile);
|
|
||||||
|
|
||||||
const updateCursorPosition = (e: MouseEvent) => {
|
|
||||||
targetX.current = e.clientX;
|
|
||||||
targetY.current = e.clientY;
|
|
||||||
|
|
||||||
const target = e.target as HTMLElement;
|
|
||||||
|
|
||||||
// Check if we're over Giscus
|
|
||||||
const isOverGiscus = target.closest('.giscus') !== null ||
|
|
||||||
target.closest('#inject-comments') !== null ||
|
|
||||||
target.closest('.giscus-frame') !== null ||
|
|
||||||
target.closest('iframe') !== null;
|
|
||||||
|
|
||||||
// Check if the element is interactive
|
|
||||||
const isInteractive = target.tagName === 'A' || target.tagName === 'BUTTON' ||
|
|
||||||
target.closest('a') !== null || target.closest('button') !== null ||
|
|
||||||
window.getComputedStyle(target).cursor === 'pointer';
|
|
||||||
|
|
||||||
setState(prev => ({
|
|
||||||
...prev,
|
|
||||||
x: e.clientX,
|
|
||||||
y: e.clientY,
|
|
||||||
isPointer: isInteractive,
|
|
||||||
isOverBackground: target.tagName === 'CANVAS' ||
|
|
||||||
target.closest('canvas') !== null,
|
|
||||||
isOverGiscus: isOverGiscus
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Add trail points
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - lastTrailTime.current > 10) { // Control trail density
|
|
||||||
const cursorColor = getCursorColor();
|
|
||||||
setTrail(prevTrail => {
|
|
||||||
const newTrail = [
|
|
||||||
{
|
|
||||||
x: e.clientX,
|
|
||||||
y: e.clientY,
|
|
||||||
id: trailIdCounter.current++,
|
|
||||||
color: cursorColor,
|
|
||||||
opacity: 0.8,
|
|
||||||
scale: 1
|
|
||||||
},
|
|
||||||
...prevTrail.slice(0, 20) // Keep last 20 points
|
|
||||||
];
|
|
||||||
return newTrail;
|
|
||||||
});
|
|
||||||
lastTrailTime.current = now;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
|
||||||
setState(prev => ({ ...prev, isClicking: true }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
|
||||||
setState(prev => ({ ...prev, isClicking: false }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
setState(prev => ({ ...prev, x: -100, y: -100, isClicking: false }));
|
|
||||||
setTrail([]); // Clear trail when mouse leaves
|
|
||||||
};
|
|
||||||
|
|
||||||
// Smooth cursor movement animation
|
|
||||||
const animate = () => {
|
|
||||||
if (cursorRef.current) {
|
|
||||||
const currentX = parseFloat(cursorRef.current.style.left || '0');
|
|
||||||
const currentY = parseFloat(cursorRef.current.style.top || '0');
|
|
||||||
|
|
||||||
// Smooth interpolation
|
|
||||||
const newX = currentX + (targetX.current - currentX) * 0.2;
|
|
||||||
const newY = currentY + (targetY.current - currentY) * 0.2;
|
|
||||||
|
|
||||||
cursorRef.current.style.left = newX + 'px';
|
|
||||||
cursorRef.current.style.top = newY + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update trail points
|
|
||||||
setTrail(prevTrail =>
|
|
||||||
prevTrail.map(point => ({
|
|
||||||
...point,
|
|
||||||
opacity: point.opacity * 0.95, // Fade out
|
|
||||||
scale: point.scale * 0.97 // Shrink
|
|
||||||
})).filter(point => point.opacity > 0.01) // Remove faded points
|
|
||||||
);
|
|
||||||
|
|
||||||
requestRef.current = requestAnimationFrame(animate);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add event listeners
|
|
||||||
document.addEventListener('mousemove', updateCursorPosition);
|
|
||||||
document.addEventListener('mousedown', handleMouseDown);
|
|
||||||
document.addEventListener('mouseup', handleMouseUp);
|
|
||||||
document.addEventListener('mouseleave', handleMouseLeave);
|
|
||||||
|
|
||||||
// Start animation
|
|
||||||
requestRef.current = requestAnimationFrame(animate);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('mousemove', updateCursorPosition);
|
|
||||||
document.removeEventListener('mousedown', handleMouseDown);
|
|
||||||
document.removeEventListener('mouseup', handleMouseUp);
|
|
||||||
document.removeEventListener('mouseleave', handleMouseLeave);
|
|
||||||
window.removeEventListener('resize', checkMobile);
|
|
||||||
if (requestRef.current) {
|
|
||||||
cancelAnimationFrame(requestRef.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Helper function to get color from class names
|
|
||||||
const getColorFromClass = (element: Element) => {
|
|
||||||
const classes = element.className;
|
|
||||||
if (typeof classes !== 'string') return null;
|
|
||||||
|
|
||||||
// Map of class names to color values
|
|
||||||
const colorMap: { [key: string]: string } = {
|
|
||||||
'text-aqua': '#689d6a',
|
|
||||||
'text-green': '#98971a',
|
|
||||||
'text-yellow': '#d79921',
|
|
||||||
'text-blue': '#458588',
|
|
||||||
'text-purple': '#b16286',
|
|
||||||
'text-red': '#cc241d',
|
|
||||||
'text-orange': '#d65d0e',
|
|
||||||
// Bright variants
|
|
||||||
'hover:text-aqua': '#8ec07c',
|
|
||||||
'hover:text-green': '#b8bb26',
|
|
||||||
'hover:text-yellow': '#fabd2f',
|
|
||||||
'hover:text-blue': '#83a598',
|
|
||||||
'hover:text-purple': '#d3869b',
|
|
||||||
'hover:text-red': '#fb4934',
|
|
||||||
'hover:text-orange': '#fe8019',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Find the first matching color class
|
|
||||||
for (const [className, color] of Object.entries(colorMap)) {
|
|
||||||
if (classes.includes(className)) {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Determine cursor color based on element and state
|
|
||||||
const getCursorColor = () => {
|
|
||||||
if (state.isOverBackground) return '#ebdbb2';
|
|
||||||
|
|
||||||
// Get the element under cursor
|
|
||||||
const elementUnderCursor = document.elementFromPoint(state.x, state.y);
|
|
||||||
if (!elementUnderCursor) return '#ebdbb2';
|
|
||||||
|
|
||||||
// Check element type and apply appropriate color
|
|
||||||
if (elementUnderCursor.tagName === 'A' || elementUnderCursor.closest('a')) {
|
|
||||||
const linkElement = elementUnderCursor.tagName === 'A' ? elementUnderCursor : elementUnderCursor.closest('a')!;
|
|
||||||
|
|
||||||
// First try to get color from class
|
|
||||||
const classColor = getColorFromClass(linkElement);
|
|
||||||
if (classColor) return classColor;
|
|
||||||
|
|
||||||
// Fallback to computed style
|
|
||||||
const computedStyle = window.getComputedStyle(linkElement);
|
|
||||||
return computedStyle.color;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elementUnderCursor.tagName === 'BUTTON' || elementUnderCursor.closest('button')) {
|
|
||||||
return '#fabd2f'; // yellow.bright
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elementUnderCursor.tagName === 'INPUT' || elementUnderCursor.tagName === 'TEXTAREA') {
|
|
||||||
return '#8ec07c'; // aqua.bright
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any element with color classes
|
|
||||||
const classColor = getColorFromClass(elementUnderCursor);
|
|
||||||
if (classColor) return classColor;
|
|
||||||
|
|
||||||
return '#ebdbb2'; // default foreground color
|
|
||||||
};
|
|
||||||
|
|
||||||
const cursorColor = getCursorColor();
|
|
||||||
const scale = state.isClicking ? 0.8 : (state.isPointer ? 1.2 : 1);
|
|
||||||
|
|
||||||
// Hide cursor completely on mobile or when over Giscus
|
|
||||||
if (state.isMobile || state.isOverGiscus) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* Trail effect */}
|
|
||||||
{trail.map(point => (
|
|
||||||
<div
|
|
||||||
key={point.id}
|
|
||||||
className="pointer-events-none fixed z-[9997]"
|
|
||||||
style={{
|
|
||||||
left: point.x,
|
|
||||||
top: point.y,
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
opacity: point.opacity,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="rounded-full"
|
|
||||||
style={{
|
|
||||||
width: `${8 * point.scale}px`,
|
|
||||||
height: `${8 * point.scale}px`,
|
|
||||||
backgroundColor: point.color,
|
|
||||||
boxShadow: `0 0 ${4 * point.scale}px ${point.color}`,
|
|
||||||
filter: 'blur(1px)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Main cursor dot */}
|
|
||||||
<div
|
|
||||||
ref={cursorRef}
|
|
||||||
className="pointer-events-none fixed z-[9999]"
|
|
||||||
style={{
|
|
||||||
left: state.x,
|
|
||||||
top: state.y,
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="rounded-full border transition-all duration-150 ease-out"
|
|
||||||
style={{
|
|
||||||
width: '16px',
|
|
||||||
height: '16px',
|
|
||||||
borderColor: cursorColor || '#ebdbb2',
|
|
||||||
borderWidth: '2px',
|
|
||||||
backgroundColor: state.isClicking ? cursorColor : 'transparent',
|
|
||||||
transform: `scale(${scale})`,
|
|
||||||
boxShadow: state.isPointer ? `0 0 8px ${cursorColor}40` : 'none',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Inner cursor dot (for better visibility) */}
|
|
||||||
<div
|
|
||||||
className="pointer-events-none fixed z-[9999]"
|
|
||||||
style={{
|
|
||||||
left: state.x,
|
|
||||||
top: state.y,
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="rounded-full"
|
|
||||||
style={{
|
|
||||||
width: '4px',
|
|
||||||
height: '4px',
|
|
||||||
backgroundColor: cursorColor || '#ebdbb2',
|
|
||||||
transform: `scale(${scale})`,
|
|
||||||
transition: 'all 0.15s ease-out',
|
|
||||||
boxShadow: state.isPointer ? `0 0 6px ${cursorColor}` : 'none',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Hover glow effect */}
|
|
||||||
{state.isPointer && !state.isOverBackground && (
|
|
||||||
<div
|
|
||||||
className="pointer-events-none fixed z-[9998]"
|
|
||||||
style={{
|
|
||||||
left: state.x,
|
|
||||||
top: state.y,
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="rounded-full animate-pulse"
|
|
||||||
style={{
|
|
||||||
width: '32px',
|
|
||||||
height: '32px',
|
|
||||||
backgroundColor: `${cursorColor}10`,
|
|
||||||
filter: 'blur(4px)',
|
|
||||||
transform: `scale(${scale})`,
|
|
||||||
transition: 'all 0.2s ease-out',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Cursor;
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
import "@/style/globals.css";
|
import "@/style/globals.css";
|
||||||
import { ClientRouter } from "astro:transitions";
|
import { ClientRouter } from "astro:transitions";
|
||||||
|
|
||||||
import Cursor from "@/components/cursor";
|
|
||||||
import Header from "@/components/header";
|
import Header from "@/components/header";
|
||||||
import Footer from "@/components/footer";
|
import Footer from "@/components/footer";
|
||||||
import Background from "@/components/background";
|
import Background from "@/components/background";
|
||||||
@@ -53,7 +52,6 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-background text-foreground min-h-screen flex flex-col">
|
<body class="bg-background text-foreground min-h-screen flex flex-col">
|
||||||
<Cursor client:only="react" />
|
|
||||||
<Header client:load />
|
<Header client:load />
|
||||||
<main class="flex-1 flex flex-col">
|
<main class="flex-1 flex flex-col">
|
||||||
<div class="max-w-5xl mx-auto pt-12 px-4 py-8 flex-1">
|
<div class="max-w-5xl mx-auto pt-12 px-4 py-8 flex-1">
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import "@/style/globals.css";
|
|||||||
|
|
||||||
import { ClientRouter } from "astro:transitions";
|
import { ClientRouter } from "astro:transitions";
|
||||||
|
|
||||||
import Cursor from "@/components/cursor";
|
|
||||||
import Header from "@/components/header";
|
import Header from "@/components/header";
|
||||||
import Footer from "@/components/footer";
|
import Footer from "@/components/footer";
|
||||||
import Background from "@/components/background";
|
import Background from "@/components/background";
|
||||||
@@ -41,7 +40,6 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
<ClientRouter />
|
<ClientRouter />
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-background text-foreground">
|
<body class="bg-background text-foreground">
|
||||||
<Cursor client:only="react" />
|
|
||||||
<Header client:load />
|
<Header client:load />
|
||||||
<main transition:animate="fade">
|
<main transition:animate="fade">
|
||||||
<Background layout="index" client:only="react" transition:persist />
|
<Background layout="index" client:only="react" transition:persist />
|
||||||
|
|||||||
@@ -2,25 +2,6 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
/* Hide default cursor globally on desktop */
|
|
||||||
@media (min-width: 1025px) {
|
|
||||||
*:not(.giscus):not(.giscus *):not(.giscus-frame):not(.giscus-frame *):not(iframe):not(iframe *) {
|
|
||||||
cursor: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allow default cursor for Giscus */
|
|
||||||
.giscus, .giscus *, .giscus-frame, .giscus-frame *, #inject-comments, #inject-comments *, iframe, iframe * {
|
|
||||||
cursor: auto !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Show default cursor on mobile and tablet */
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
* {
|
|
||||||
cursor: auto !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Chrome, Edge, and Safari */
|
/* Chrome, Edge, and Safari */
|
||||||
*::-webkit-scrollbar {
|
*::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user