Created
March 19, 2025 13:03
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Rotating Letter Sphere</title> | |
<style> | |
body { | |
margin: 0; | |
padding: 0; | |
background-color: black; | |
overflow: hidden; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
height: 100vh; | |
color: white; | |
font-family: monospace; | |
} | |
canvas { | |
display: block; | |
} | |
</style> | |
</head> | |
<body> | |
<canvas id="canvas"></canvas> | |
<script> | |
// Constants | |
const LETTERS = "abcdefghijklmnopqrstuvwxyz"; | |
const POINT_COUNT = 1000; | |
const SPHERE_RADIUS = 200; | |
const ROTATION_SPEED_X = 0.005; | |
const ROTATION_SPEED_Y = 0.003; | |
// Setup canvas | |
const canvas = document.getElementById("canvas"); | |
const ctx = canvas.getContext("2d"); | |
// Set canvas size | |
function resizeCanvas() { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
} | |
// Initialize | |
resizeCanvas(); | |
window.addEventListener("resize", resizeCanvas); | |
// Generate random points on sphere surface | |
const points = []; | |
for (let i = 0; i < POINT_COUNT; i++) { | |
// Use spherical coordinates to distribute points evenly | |
const phi = Math.acos(2 * Math.random() - 1); | |
const theta = 2 * Math.PI * Math.random(); | |
// Convert to Cartesian coordinates | |
const x = SPHERE_RADIUS * Math.sin(phi) * Math.cos(theta); | |
const y = SPHERE_RADIUS * Math.sin(phi) * Math.sin(theta); | |
const z = SPHERE_RADIUS * Math.cos(phi); | |
// Select random letter | |
const letterIndex = Math.floor(Math.random() * LETTERS.length); | |
const letter = LETTERS[letterIndex]; | |
points.push({ x, y, z, letter }); | |
} | |
// Rotation state | |
let angleX = 0; | |
let angleY = 0; | |
// Animation function | |
function animate() { | |
// Update rotation | |
angleX += ROTATION_SPEED_X; | |
angleY += ROTATION_SPEED_Y; | |
// Clear canvas | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Sort points by z-value for correct depth rendering | |
const rotatedPoints = points.map((point) => { | |
// Apply rotation around Y axis | |
const sinY = Math.sin(angleY); | |
const cosY = Math.cos(angleY); | |
const x1 = point.x * cosY - point.z * sinY; | |
const z1 = point.z * cosY + point.x * sinY; | |
// Apply rotation around X axis | |
const sinX = Math.sin(angleX); | |
const cosX = Math.cos(angleX); | |
const y2 = point.y * cosX - z1 * sinX; | |
const z2 = z1 * cosX + point.y * sinX; | |
return { | |
x: x1, | |
y: y2, | |
z: z2, | |
letter: point.letter, | |
}; | |
}); | |
// Sort by z for proper depth rendering (furthest first) | |
rotatedPoints.sort((a, b) => b.z - a.z); | |
// Render points | |
rotatedPoints.forEach((point) => { | |
// Calculate screen position | |
const scale = 1000 / (1000 + point.z); // Perspective division | |
const screenX = canvas.width / 2 + point.x * scale; | |
const screenY = canvas.height / 2 + point.y * scale; | |
const colorValue = Math.round( | |
(255 * (point.z + SPHERE_RADIUS)) / (2 * SPHERE_RADIUS), | |
); | |
const color = `rgb(${colorValue},${colorValue},${colorValue})`; | |
// Draw letter | |
ctx.font = `${Math.max(8, 16 * scale)}px monospace`; | |
ctx.fillStyle = color; | |
ctx.fillText(point.letter, screenX, screenY); | |
}); | |
requestAnimationFrame(animate); | |
} | |
// Start animation | |
animate(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment