(globalThis as any).canvas_2022 = function (canvas: HTMLCanvasElement) { const isStandalone = canvas.getAttribute("data-standalone") === "true"; // Configuration for the grid of rotating squares const config = { gridRotation: 20, // Overall grid rotation in degrees squareSize: 20, // Size of each square spacing: 100, // Distance between square centers moveSpeedX: 0.01, // Horizontal movement speed (pixels per second) moveSpeedY: 0.01, // Vertical movement speed (pixels per second) squareColor: "#00220A", // Color of the squares squareOpacity: 1, // Opacity of the squares // Function to determine square rotation based on its coordinates and time // Can be adjusted for different patterns rotationFunction: (x: number, y: number, time: number): number => { // Combination of spatial wave and time-based rotation return Math.sin(x * 0.05) * Math.cos(y * 0.05) * 180; }, }; // Convert grid rotation to radians const gridRotationRad = (config.gridRotation * Math.PI) / 180; // Get the canvas context const ctx = canvas.getContext("2d"); if (!ctx) { console.error("Could not get canvas context"); return () => {}; } // Make canvas transparent if (isStandalone) { canvas.style.backgroundColor = "#154226"; } else { canvas.style.backgroundColor = "transparent"; } // Animation variables let width = canvas.width; let height = canvas.height; let offsetX = 0; let offsetY = 0; let time = 0; let animationFrameId: number; let lastTime = 0; // Update canvas dimensions when resized const updateDimensions = () => { width = canvas.width = canvas.clientWidth; height = canvas.height = canvas.clientHeight; }; // Calculate the diagonal length of the canvas (to ensure rotation covers corners) const calculateDiagonal = () => { return Math.sqrt(width * width + height * height); }; // Draw a single square with rotation const drawSquare = (x: number, y: number, size: number, rotation: number) => { ctx.save(); // Move to the center of the square position, rotate, then draw ctx.translate(x, y); ctx.rotate((rotation * Math.PI) / 180); // Convert rotation degrees to radians // Draw square centered at position ctx.fillRect(-size / 2, -size / 2, size, size); ctx.restore(); }; // Draw the entire grid of squares const drawGrid = () => { ctx.clearRect(0, 0, width, height); // Set drawing properties ctx.fillStyle = config.squareColor; ctx.globalAlpha = config.squareOpacity; // Save the current transformation state ctx.save(); // Move to the center of the canvas, rotate the grid, then move back const centerX = width / 2; const centerY = height / 2; ctx.translate(centerX, centerY); ctx.rotate(gridRotationRad); // Calculate how much of the grid to draw based on canvas size const diagonal = calculateDiagonal(); const gridSize = Math.ceil(diagonal / config.spacing) + 2; // Adjust for offset to create movement const adjustedOffsetX = offsetX % config.spacing; const adjustedOffsetY = offsetY % config.spacing; // Draw grid with enough squares to cover the rotated canvas const halfGrid = Math.ceil(gridSize / 2); for (let y = -halfGrid; y <= halfGrid; y++) { for (let x = -halfGrid; x <= halfGrid; x++) { // Calculate actual position with offset const posX = x * config.spacing + adjustedOffsetX; const posY = y * config.spacing + adjustedOffsetY; // Calculate square rotation based on its position and time const squareRotation = config.rotationFunction(posX, posY, time); // Draw the square drawSquare(posX, posY, config.squareSize, squareRotation); } } // Restore the transformation state ctx.restore(); // Reset global alpha ctx.globalAlpha = 1.0; }; // Animation loop const animate = (currentTime: number) => { animationFrameId = requestAnimationFrame(animate); // Calculate time elapsed since last frame const elapsed = currentTime - lastTime; lastTime = currentTime; // Update time variable for rotation function time += elapsed; // Update position offsets for movement offsetX += config.moveSpeedX * elapsed; offsetY += config.moveSpeedY * elapsed; // Draw the grid drawGrid(); }; // Initialize the animation const init = () => { // Set up resize handler window.addEventListener("resize", updateDimensions); // Initial setup updateDimensions(); // Start animation animationFrameId = requestAnimationFrame(animate); }; // Start the animation init(); // Return cleanup function return () => { window.removeEventListener("resize", updateDimensions); cancelAnimationFrame(animationFrameId); }; };