| 1 | // Written by AI. Options tuned with AI. |
| 2 | (globalThis as any).canvas_2022 = function(canvas: HTMLCanvasElement) { |
| 3 | const isStandalone = canvas.getAttribute("data-standalone") === "true"; |
| 4 | // Configuration for the grid of rotating squares |
| 5 | const config = { |
| 6 | gridRotation: 20, // Overall grid rotation in degrees |
| 7 | squareSize: 20, // Size of each square |
| 8 | spacing: 100, // Distance between square centers |
| 9 | moveSpeedX: 0.01, // Horizontal movement speed (pixels per second) |
| 10 | moveSpeedY: 0.01, // Vertical movement speed (pixels per second) |
| 11 | squareColor: "#00220A", // Color of the squares |
| 12 | squareOpacity: 1, // Opacity of the squares |
| 13 | |
| 14 | // Function to determine square rotation based on its coordinates and time |
| 15 | // Can be adjusted for different patterns |
| 16 | rotationFunction: (x: number, y: number, time: number): number => { |
| 17 | // Combination of spatial wave and time-based rotation |
| 18 | return Math.sin(x * 0.05) * Math.cos(y * 0.05) * 180; |
| 19 | }, |
| 20 | }; |
| 21 | |
| 22 | // Convert grid rotation to radians |
| 23 | const gridRotationRad = (config.gridRotation * Math.PI) / 180; |
| 24 | |
| 25 | // Get the canvas context |
| 26 | const ctx = canvas.getContext("2d"); |
| 27 | if (!ctx) { |
| 28 | console.error("Could not get canvas context"); |
| 29 | return () => {}; |
| 30 | } |
| 31 | |
| 32 | // Make canvas transparent |
| 33 | if (isStandalone) { |
| 34 | canvas.style.backgroundColor = "#154226"; |
| 35 | } else { |
| 36 | canvas.style.backgroundColor = "transparent"; |
| 37 | } |
| 38 | |
| 39 | // Animation variables |
| 40 | let width = canvas.width; |
| 41 | let height = canvas.height; |
| 42 | let offsetX = 0; |
| 43 | let offsetY = 0; |
| 44 | let time = 0; |
| 45 | let animationFrameId: number; |
| 46 | let lastTime = 0; |
| 47 | |
| 48 | // Update canvas dimensions when resized |
| 49 | const updateDimensions = () => { |
| 50 | width = canvas.width = canvas.clientWidth; |
| 51 | height = canvas.height = canvas.clientHeight; |
| 52 | }; |
| 53 | |
| 54 | // Calculate the diagonal length of the canvas (to ensure rotation covers corners) |
| 55 | const calculateDiagonal = () => { |
| 56 | return Math.sqrt(width * width + height * height); |
| 57 | }; |
| 58 | |
| 59 | // Draw a single square with rotation |
| 60 | const drawSquare = (x: number, y: number, size: number, rotation: number) => { |
| 61 | ctx.save(); |
| 62 | |
| 63 | // Move to the center of the square position, rotate, then draw |
| 64 | ctx.translate(x, y); |
| 65 | ctx.rotate((rotation * Math.PI) / 180); // Convert rotation degrees to radians |
| 66 | |
| 67 | // Draw square centered at position |
| 68 | ctx.fillRect(-size / 2, -size / 2, size, size); |
| 69 | |
| 70 | ctx.restore(); |
| 71 | }; |
| 72 | |
| 73 | // Draw the entire grid of squares |
| 74 | const drawGrid = () => { |
| 75 | ctx.clearRect(0, 0, width, height); |
| 76 | |
| 77 | // Set drawing properties |
| 78 | ctx.fillStyle = config.squareColor; |
| 79 | ctx.globalAlpha = config.squareOpacity; |
| 80 | |
| 81 | // Save the current transformation state |
| 82 | ctx.save(); |
| 83 | |
| 84 | // Move to the center of the canvas, rotate the grid, then move back |
| 85 | const centerX = width / 2; |
| 86 | const centerY = height / 2; |
| 87 | ctx.translate(centerX, centerY); |
| 88 | ctx.rotate(gridRotationRad); |
| 89 | |
| 90 | // Calculate how much of the grid to draw based on canvas size |
| 91 | const diagonal = calculateDiagonal(); |
| 92 | const gridSize = Math.ceil(diagonal / config.spacing) + 2; |
| 93 | |
| 94 | // Adjust for offset to create movement |
| 95 | const adjustedOffsetX = offsetX % config.spacing; |
| 96 | const adjustedOffsetY = offsetY % config.spacing; |
| 97 | |
| 98 | // Draw grid with enough squares to cover the rotated canvas |
| 99 | const halfGrid = Math.ceil(gridSize / 2); |
| 100 | |
| 101 | for (let y = -halfGrid; y <= halfGrid; y++) { |
| 102 | for (let x = -halfGrid; x <= halfGrid; x++) { |
| 103 | // Calculate actual position with offset |
| 104 | const posX = x * config.spacing + adjustedOffsetX; |
| 105 | const posY = y * config.spacing + adjustedOffsetY; |
| 106 | |
| 107 | // Calculate square rotation based on its position and time |
| 108 | const squareRotation = config.rotationFunction(posX, posY, time); |
| 109 | |
| 110 | // Draw the square |
| 111 | drawSquare(posX, posY, config.squareSize, squareRotation); |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | // Restore the transformation state |
| 116 | ctx.restore(); |
| 117 | |
| 118 | // Reset global alpha |
| 119 | ctx.globalAlpha = 1.0; |
| 120 | }; |
| 121 | |
| 122 | // Animation loop |
| 123 | const animate = (currentTime: number) => { |
| 124 | animationFrameId = requestAnimationFrame(animate); |
| 125 | |
| 126 | // Calculate time elapsed since last frame |
| 127 | const elapsed = currentTime - lastTime; |
| 128 | lastTime = currentTime; |
| 129 | |
| 130 | // Update time variable for rotation function |
| 131 | time += elapsed; |
| 132 | |
| 133 | // Update position offsets for movement |
| 134 | offsetX += config.moveSpeedX * elapsed; |
| 135 | offsetY += config.moveSpeedY * elapsed; |
| 136 | |
| 137 | // Draw the grid |
| 138 | drawGrid(); |
| 139 | }; |
| 140 | |
| 141 | // Initialize the animation |
| 142 | const init = () => { |
| 143 | // Set up resize handler |
| 144 | window.addEventListener("resize", updateDimensions); |
| 145 | |
| 146 | // Initial setup |
| 147 | updateDimensions(); |
| 148 | |
| 149 | // Start animation |
| 150 | animationFrameId = requestAnimationFrame(animate); |
| 151 | }; |
| 152 | |
| 153 | // Start the animation |
| 154 | init(); |
| 155 | |
| 156 | // Return cleanup function |
| 157 | return () => { |
| 158 | window.removeEventListener("resize", updateDimensions); |
| 159 | cancelAnimationFrame(animationFrameId); |
| 160 | }; |
| 161 | }; |