Created
May 12, 2025 19:05
-
-
Save knightad10/7f800fdc775fbf919804ebacee8793a1 to your computer and use it in GitHub Desktop.
[javascript] ❍ Circular Animations Set N°2
This file contains hidden or 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
<h1>CIRCLE ANIMATIONS COLLECTION N°2</h1> | |
<div class="container"> | |
<div class="animation-container"> | |
<div class="animation-title">Radial Pulse</div> | |
<div id="radial-pulse" class="circle-container"></div> | |
</div> | |
<div class="animation-container"> | |
<div class="animation-title">Orbital Pulse</div> | |
<div id="orbital-pulse" class="circle-container"></div> | |
</div> | |
<div class="animation-container"> | |
<div class="animation-title">Pendulum Wave</div> | |
<div id="pendulum-wave" class="circle-container"></div> | |
</div> | |
<div class="animation-container"> | |
<div class="animation-title">Pulse Wave</div> | |
<div id="pulse-wave" class="circle-container"></div> | |
</div> | |
<div class="animation-container"> | |
<div class="animation-title">Concentric Rings</div> | |
<div id="concentric-rings" class="circle-container"></div> | |
</div> | |
<div class="animation-container"> | |
<div class="animation-title">Sequential Pulse</div> | |
<div id="sequential-pulse" class="circle-container"></div> | |
</div> | |
<div class="animation-container"> | |
<div class="animation-title">Oscillating Dots</div> | |
<div id="oscillating-dots" class="circle-container"></div> | |
</div> | |
<div class="animation-container"> | |
<div class="animation-title">Pulsing Grid</div> | |
<div id="pulsing-grid" class="circle-container"></div> | |
</div> | |
<div class="animation-container"> | |
<div class="animation-title">Spiral Galaxy</div> | |
<div id="spiral-galaxy" class="circle-container"></div> | |
</div> | |
</div> |
This file contains hidden or 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
(function () { | |
// Add corner decorations to all animation containers | |
function addCornerDecorations() { | |
document.querySelectorAll(".animation-container").forEach((container) => { | |
const corners = ["top-left", "top-right", "bottom-left", "bottom-right"]; | |
corners.forEach((position) => { | |
const corner = document.createElement("div"); | |
corner.className = `corner ${position}`; | |
const svg = document.createElementNS( | |
"http://www.w3.org/2000/svg", | |
"svg" | |
); | |
svg.setAttribute("width", "16"); | |
svg.setAttribute("height", "16"); | |
svg.setAttribute("viewBox", "0 0 512 512"); | |
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); | |
const polygon = document.createElementNS( | |
"http://www.w3.org/2000/svg", | |
"polygon" | |
); | |
polygon.setAttribute( | |
"points", | |
"448,224 288,224 288,64 224,64 224,224 64,224 64,288 224,288 224,448 288,448 288,288 448,288" | |
); | |
polygon.setAttribute("fill", "currentColor"); | |
svg.appendChild(polygon); | |
corner.appendChild(svg); | |
container.appendChild(corner); | |
}); | |
}); | |
} | |
// 1. Radial Pulse (slowed down) | |
function setupRadialPulse() { | |
const container = document.getElementById("radial-pulse"); | |
if (!container) return; | |
container.innerHTML = ""; | |
const canvas = document.createElement("canvas"); | |
canvas.width = 180; | |
canvas.height = 180; | |
canvas.style.position = "absolute"; | |
canvas.style.left = "0"; | |
canvas.style.top = "0"; | |
container.appendChild(canvas); | |
const ctx = canvas.getContext("2d"); | |
const centerX = canvas.width / 2; | |
const centerY = canvas.height / 2; | |
const maxRadius = 75; | |
let time = 0; | |
let lastTime = 0; | |
// Rings of dots that will pulse outward | |
const ringCount = 8; | |
const dotsPerRing = 12; | |
const pulseSpeed = 0.35; // Slowed down from 0.5 to 0.35 | |
function animate(timestamp) { | |
if (!lastTime) lastTime = timestamp; | |
const deltaTime = timestamp - lastTime; | |
lastTime = timestamp; | |
time += deltaTime * 0.001; | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Draw central dot | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, 3, 0, Math.PI * 2); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.9)"; | |
ctx.fill(); | |
// Pulse wave effect - creates waves of dots moving outward | |
for (let i = 0; i < ringCount; i++) { | |
// Calculate current radius for this ring | |
// This creates a repeating pulse effect from center to edge | |
const pulsePhase = (time * pulseSpeed + i / ringCount) % 1; | |
const ringRadius = pulsePhase * maxRadius; | |
// Skip rings that are just starting (too close to center) | |
if (ringRadius < 5) continue; | |
// Opacity decreases as the pulse moves outward | |
const opacity = 1 - pulsePhase; | |
// Draw dots around the ring | |
for (let j = 0; j < dotsPerRing; j++) { | |
const angle = (j / dotsPerRing) * Math.PI * 2; | |
const x = centerX + Math.cos(angle) * ringRadius; | |
const y = centerY + Math.sin(angle) * ringRadius; | |
// Dot size decreases as the pulse moves outward | |
const dotSize = 2.5 * (1 - pulsePhase * 0.5); | |
ctx.beginPath(); | |
ctx.arc(x, y, dotSize, 0, Math.PI * 2); | |
ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`; | |
ctx.fill(); | |
} | |
} | |
requestAnimationFrame(animate); | |
} | |
requestAnimationFrame(animate); | |
} | |
// 2. Improved Smooth Orbital Pulse (sped up) | |
function setupOrbitalPulse() { | |
const container = document.getElementById("orbital-pulse"); | |
if (!container) return; | |
container.innerHTML = ""; | |
const canvas = document.createElement("canvas"); | |
canvas.width = 180; | |
canvas.height = 180; | |
canvas.style.position = "absolute"; | |
canvas.style.left = "0"; | |
canvas.style.top = "0"; | |
container.appendChild(canvas); | |
const ctx = canvas.getContext("2d"); | |
const centerX = canvas.width / 2; | |
const centerY = canvas.height / 2; | |
const maxRadius = 75; | |
let time = 0; | |
let lastTime = 0; | |
// Fixed orbital rings with smoother pulse | |
const orbits = [ | |
{ | |
radius: 15, | |
dotCount: 6 | |
}, | |
{ | |
radius: 25, | |
dotCount: 10 | |
}, | |
{ | |
radius: 35, | |
dotCount: 14 | |
}, | |
{ | |
radius: 45, | |
dotCount: 18 | |
}, | |
{ | |
radius: 55, | |
dotCount: 22 | |
}, | |
{ | |
radius: 65, | |
dotCount: 26 | |
} | |
]; | |
// Pulse parameters | |
const pulseFrequency = 0.5; // Sped up from 0.2 to 0.35 | |
const pulseAmplitude = 2; // Maximum displacement of dots | |
function animate(timestamp) { | |
if (!lastTime) lastTime = timestamp; | |
const deltaTime = timestamp - lastTime; | |
lastTime = timestamp; | |
time += deltaTime * 0.001; | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Draw center | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, 3, 0, Math.PI * 2); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.9)"; | |
ctx.fill(); | |
// Draw orbit circles (very faint) | |
orbits.forEach((orbit, orbitIndex) => { | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, orbit.radius, 0, Math.PI * 2); | |
ctx.strokeStyle = "rgba(255, 255, 255, 0.05)"; | |
ctx.lineWidth = 1; | |
ctx.stroke(); | |
// Calculate smooth pulse animation | |
// Use a sine wave that creates a continuous pulse from center to edge | |
const normalizedRadius = orbit.radius / maxRadius; // 0 to 1 value | |
const pulseDelay = normalizedRadius * 1.5; // Outer rings pulse later | |
// Create a smooth, continuous pulse wave moving outward | |
// Time is multiplied by pulseFrequency to control speed | |
const pulsePhase = (time * pulseFrequency - pulseDelay) % 1; | |
// Create a smooth bell curve for the pulse effect | |
// This makes it grow smoothly then shrink smoothly | |
const pulseEffect = Math.sin(pulsePhase * Math.PI) * pulseAmplitude; | |
// Only apply positive pulse effects (moving outward) | |
const finalPulseEffect = pulseEffect > 0 ? pulseEffect : 0; | |
// Draw dots around the orbit | |
for (let i = 0; i < orbit.dotCount; i++) { | |
const angle = (i / orbit.dotCount) * Math.PI * 2; | |
// Apply pulse to radius - smooth movement outward | |
const pulsedRadius = orbit.radius + finalPulseEffect; | |
const x = centerX + Math.cos(angle) * pulsedRadius; | |
const y = centerY + Math.sin(angle) * pulsedRadius; | |
// Dot size also gently increases with pulse | |
const dotSize = 2 + (finalPulseEffect / pulseAmplitude) * 1.5; | |
// Opacity also increases with pulse | |
const opacity = 0.7 + (finalPulseEffect / pulseAmplitude) * 0.3; | |
ctx.beginPath(); | |
ctx.arc(x, y, dotSize, 0, Math.PI * 2); | |
ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`; | |
ctx.fill(); | |
} | |
}); | |
requestAnimationFrame(animate); | |
} | |
requestAnimationFrame(animate); | |
} | |
// 3. Fixed Pendulum Wave (unchanged) | |
function setupPendulumWave() { | |
const container = document.getElementById("pendulum-wave"); | |
if (!container) return; | |
container.innerHTML = ""; | |
const canvas = document.createElement("canvas"); | |
canvas.width = 180; | |
canvas.height = 180; | |
canvas.style.position = "absolute"; | |
canvas.style.left = "0"; | |
canvas.style.top = "0"; | |
container.appendChild(canvas); | |
const ctx = canvas.getContext("2d"); | |
const centerX = canvas.width / 2; | |
const centerY = canvas.height / 2; | |
let time = 0; | |
let lastTime = 0; | |
// Pendulum parameters | |
const pendulumCount = 15; | |
const baseFrequency = 0.5; // All pendulums at the same frequency | |
const pendulumLength = 90; | |
const maxAngle = Math.PI / 12; // 15 degrees max angle | |
// Reference line | |
const referenceLine = document.createElement("div"); | |
referenceLine.style.position = "absolute"; | |
referenceLine.style.width = `${pendulumCount * 8}px`; | |
referenceLine.style.height = "1px"; | |
referenceLine.style.left = `${centerX - (pendulumCount * 8) / 2}px`; | |
referenceLine.style.top = `${centerY - pendulumLength}px`; | |
referenceLine.style.backgroundColor = "rgba(255, 255, 255, 0.15)"; | |
container.appendChild(referenceLine); | |
function animate(timestamp) { | |
if (!lastTime) lastTime = timestamp; | |
const deltaTime = timestamp - lastTime; | |
lastTime = timestamp; | |
time += deltaTime * 0.001; | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Calculate single angle for all pendulums - simple left-right motion | |
const angle = Math.sin(time * baseFrequency * Math.PI) * maxAngle; | |
// Draw pendulums - all moving in unison | |
for (let i = 0; i < pendulumCount; i++) { | |
// Calculate pendulum position | |
const pendulumX = centerX - pendulumCount * 4 + i * 8; | |
const pendulumY = centerY - pendulumLength; | |
// Calculate bob position - all with the same angle | |
const bobX = pendulumX + Math.sin(angle) * pendulumLength; | |
const bobY = pendulumY + Math.cos(angle) * pendulumLength; | |
// Draw pendulum line | |
ctx.beginPath(); | |
ctx.moveTo(pendulumX, pendulumY); | |
ctx.lineTo(bobX, bobY); | |
ctx.strokeStyle = "rgba(255, 255, 255, 0.4)"; | |
ctx.lineWidth = 1; | |
ctx.stroke(); | |
// Draw pendulum bob | |
ctx.beginPath(); | |
ctx.arc(bobX, bobY, 3, 0, Math.PI * 2); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.9)"; | |
ctx.fill(); | |
// Draw center | |
ctx.beginPath(); | |
ctx.arc(pendulumX, pendulumY, 1, 0, Math.PI * 2); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.5)"; | |
ctx.fill(); | |
} | |
requestAnimationFrame(animate); | |
} | |
requestAnimationFrame(animate); | |
} | |
// 4. Pulse Wave (unchanged) | |
function setupPulseWave() { | |
const container = document.getElementById("pulse-wave"); | |
if (!container) return; | |
container.innerHTML = ""; | |
const canvas = document.createElement("canvas"); | |
canvas.width = 180; | |
canvas.height = 180; | |
canvas.style.position = "absolute"; | |
canvas.style.left = "0"; | |
canvas.style.top = "0"; | |
container.appendChild(canvas); | |
const ctx = canvas.getContext("2d"); | |
const centerX = canvas.width / 2; | |
const centerY = canvas.height / 2; | |
let time = 0; | |
let lastTime = 0; | |
// Create dots in concentric rings (without visible circles) | |
const dotRings = [ | |
{ | |
radius: 15, | |
count: 6 | |
}, | |
{ | |
radius: 30, | |
count: 12 | |
}, | |
{ | |
radius: 45, | |
count: 18 | |
}, | |
{ | |
radius: 60, | |
count: 24 | |
}, | |
{ | |
radius: 75, | |
count: 30 | |
} | |
]; | |
function animate(timestamp) { | |
if (!lastTime) lastTime = timestamp; | |
const deltaTime = timestamp - lastTime; | |
lastTime = timestamp; | |
time += deltaTime * 0.001; | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Draw center | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, 2, 0, Math.PI * 2); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.9)"; | |
ctx.fill(); | |
// Draw dots in concentric circles with wave effect | |
dotRings.forEach((ring, ringIndex) => { | |
for (let i = 0; i < ring.count; i++) { | |
const angle = (i / ring.count) * Math.PI * 2; | |
// Calculate position with pulsing radius | |
const radiusPulse = Math.sin(time * 2 - ringIndex * 0.4) * 3; | |
const x = centerX + Math.cos(angle) * (ring.radius + radiusPulse); | |
const y = centerY + Math.sin(angle) * (ring.radius + radiusPulse); | |
// Calculate opacity with wave effect | |
const opacityWave = | |
0.4 + Math.sin(time * 2 - ringIndex * 0.4 + i * 0.2) * 0.6; | |
// Draw dot | |
ctx.beginPath(); | |
ctx.arc(x, y, 2, 0, Math.PI * 2); | |
ctx.fillStyle = `rgba(255, 255, 255, ${opacityWave})`; | |
ctx.fill(); | |
} | |
}); | |
requestAnimationFrame(animate); | |
} | |
requestAnimationFrame(animate); | |
} | |
// 5. Concentric Rings (unchanged) | |
function setupConcentricRings() { | |
const container = document.getElementById("concentric-rings"); | |
if (!container) return; | |
container.innerHTML = ""; | |
const canvas = document.createElement("canvas"); | |
canvas.width = 180; | |
canvas.height = 180; | |
canvas.style.position = "absolute"; | |
canvas.style.left = "0"; | |
canvas.style.top = "0"; | |
container.appendChild(canvas); | |
const ctx = canvas.getContext("2d"); | |
const centerX = canvas.width / 2; | |
const centerY = canvas.height / 2; | |
let time = 0; | |
let lastTime = 0; | |
// Ring parameters | |
const ringCount = 5; | |
const maxRadius = 75; | |
function animate(timestamp) { | |
if (!lastTime) lastTime = timestamp; | |
const deltaTime = timestamp - lastTime; | |
lastTime = timestamp; | |
time += deltaTime * 0.001; | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Draw center dot | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, 3, 0, Math.PI * 2); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.9)"; | |
ctx.fill(); | |
// Draw concentric rings of dots | |
for (let r = 0; r < ringCount; r++) { | |
const radius = ((r + 1) / ringCount) * maxRadius; | |
const dotCount = 6 + r * 6; // More dots in outer rings | |
// Phase offset for rotation based on ring index | |
const phaseOffset = r % 2 === 0 ? time * 0.2 : -time * 0.2; | |
// Each ring pulses at a different phase | |
const ringPhase = time + r * 0.7; | |
for (let i = 0; i < dotCount; i++) { | |
const angle = (i / dotCount) * Math.PI * 2 + phaseOffset; | |
// Add a pulsing effect to the radius (slight) | |
const radiusPulse = Math.sin(ringPhase) * 3; | |
const finalRadius = radius + radiusPulse; | |
const x = centerX + Math.cos(angle) * finalRadius; | |
const y = centerY + Math.sin(angle) * finalRadius; | |
// Enhanced dot size pulsing - more pronounced | |
// Base size that varies by ring position | |
const baseSize = 2 + r / (ringCount - 1); | |
// Size pulse effect - make it more dramatic (2x larger) | |
const sizePulse = Math.sin(ringPhase) * baseSize * 0.7 + baseSize; | |
// Enhanced opacity pulsing | |
const opacityPulse = 0.6 + Math.sin(ringPhase) * 0.4; | |
ctx.beginPath(); | |
ctx.arc(x, y, sizePulse, 0, Math.PI * 2); | |
ctx.fillStyle = `rgba(255, 255, 255, ${opacityPulse})`; | |
ctx.fill(); | |
} | |
} | |
requestAnimationFrame(animate); | |
} | |
requestAnimationFrame(animate); | |
} | |
// 6. Sequential Pulse (unchanged) | |
function setupSequentialPulse() { | |
const container = document.getElementById("sequential-pulse"); | |
if (!container) return; | |
container.innerHTML = ""; | |
const canvas = document.createElement("canvas"); | |
canvas.width = 180; | |
canvas.height = 180; | |
canvas.style.position = "absolute"; | |
canvas.style.left = "0"; | |
canvas.style.top = "0"; | |
container.appendChild(canvas); | |
const ctx = canvas.getContext("2d"); | |
const centerX = canvas.width / 2; | |
const centerY = canvas.height / 2; | |
let time = 0; | |
let lastTime = 0; | |
// Dot parameters | |
const radius = 70; | |
const dotCount = 16; | |
function animate(timestamp) { | |
if (!lastTime) lastTime = timestamp; | |
const deltaTime = timestamp - lastTime; | |
lastTime = timestamp; | |
time += deltaTime * 0.001; | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Draw center dot | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, 3, 0, Math.PI * 2); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.9)"; | |
ctx.fill(); | |
// Draw reference circle (very faint) | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); | |
ctx.strokeStyle = "rgba(255, 255, 255, 0.05)"; | |
ctx.lineWidth = 1; | |
ctx.stroke(); | |
// Draw dots with sequential pulsing | |
for (let i = 0; i < dotCount; i++) { | |
const angle = (i / dotCount) * Math.PI * 2; | |
// Sequential pulsing - wave moves around the circle | |
const pulsePhase = (time * 0.5 + i / dotCount) % 1; | |
const pulseFactor = Math.sin(pulsePhase * Math.PI * 2); | |
// Apply pulse to size and radius | |
const size = 2 + pulseFactor * 2; | |
const finalRadius = radius + pulseFactor * 5; | |
const x = centerX + Math.cos(angle) * finalRadius; | |
const y = centerY + Math.sin(angle) * finalRadius; | |
// Draw connecting line to center | |
ctx.beginPath(); | |
ctx.moveTo(centerX, centerY); | |
ctx.lineTo(x, y); | |
ctx.strokeStyle = `rgba(255, 255, 255, ${0.1 + pulseFactor * 0.2})`; | |
ctx.lineWidth = 1; | |
ctx.stroke(); | |
// Draw dot | |
ctx.beginPath(); | |
ctx.arc(x, y, size, 0, Math.PI * 2); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.9)"; | |
ctx.fill(); | |
} | |
requestAnimationFrame(animate); | |
} | |
requestAnimationFrame(animate); | |
} | |
// 7. Oscillating Dots (unchanged) | |
function setupOscillatingDots() { | |
const container = document.getElementById("oscillating-dots"); | |
if (!container) return; | |
container.innerHTML = ""; | |
const canvas = document.createElement("canvas"); | |
canvas.width = 180; | |
canvas.height = 180; | |
canvas.style.position = "absolute"; | |
canvas.style.left = "0"; | |
canvas.style.top = "0"; | |
container.appendChild(canvas); | |
const ctx = canvas.getContext("2d"); | |
const centerX = canvas.width / 2; | |
const centerY = canvas.height / 2; | |
let time = 0; | |
let lastTime = 0; | |
// Oscillation parameters | |
const dotCount = 20; | |
const rowCount = 5; | |
const spacing = 15; | |
function animate(timestamp) { | |
if (!lastTime) lastTime = timestamp; | |
const deltaTime = timestamp - lastTime; | |
lastTime = timestamp; | |
time += deltaTime * 0.001; | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Draw oscillating dots in rows | |
for (let row = 0; row < rowCount; row++) { | |
const y = centerY - ((rowCount - 1) / 2) * spacing + row * spacing; | |
for (let i = 0; i < dotCount; i++) { | |
// Calculate x-position with sine wave offset | |
const baseX = centerX - ((dotCount - 1) / 2) * 8 + i * 8; | |
// Wave parameters vary by row | |
const amplitude = 4 + row * 2; | |
const frequency = 1 + row * 0.2; | |
const phaseOffset = row * 0.5; | |
// Calculate offset | |
const offset = | |
Math.sin(time * frequency + i * 0.2 + phaseOffset) * amplitude; | |
const x = baseX; | |
const finalY = y + offset; | |
// Draw dot | |
ctx.beginPath(); | |
ctx.arc(x, finalY, 2, 0, Math.PI * 2); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.9)"; | |
ctx.fill(); | |
} | |
} | |
requestAnimationFrame(animate); | |
} | |
requestAnimationFrame(animate); | |
} | |
// 8. Pulsing Grid (unchanged) | |
function setupPulsingGrid() { | |
const container = document.getElementById("pulsing-grid"); | |
if (!container) return; | |
container.innerHTML = ""; | |
const canvas = document.createElement("canvas"); | |
canvas.width = 180; | |
canvas.height = 180; | |
canvas.style.position = "absolute"; | |
canvas.style.left = "0"; | |
canvas.style.top = "0"; | |
container.appendChild(canvas); | |
const ctx = canvas.getContext("2d"); | |
const centerX = canvas.width / 2; | |
const centerY = canvas.height / 2; | |
let time = 0; | |
let lastTime = 0; | |
// Grid parameters | |
const gridSize = 5; // 5x5 grid | |
const spacing = 15; | |
// Animation parameters | |
const breathingSpeed = 0.5; // Speed of expansion/contraction | |
const waveSpeed = 1.2; // Speed of wave patterns | |
const colorPulseSpeed = 1.0; // Speed of color pulsing | |
function animate(timestamp) { | |
if (!lastTime) lastTime = timestamp; | |
const deltaTime = timestamp - lastTime; | |
lastTime = timestamp; | |
time += deltaTime * 0.001; | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Calculate breathing effect - grid expands and contracts | |
const breathingFactor = Math.sin(time * breathingSpeed) * 0.2 + 1.0; // 0.8 to 1.2 | |
// Draw center dot | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, 3, 0, Math.PI * 2); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.9)"; | |
ctx.fill(); | |
// Draw pulsing grid pattern | |
for (let row = 0; row < gridSize; row++) { | |
for (let col = 0; col < gridSize; col++) { | |
// Skip center point | |
if ( | |
row === Math.floor(gridSize / 2) && | |
col === Math.floor(gridSize / 2) | |
) | |
continue; | |
// Calculate base position | |
const baseX = (col - (gridSize - 1) / 2) * spacing; | |
const baseY = (row - (gridSize - 1) / 2) * spacing; | |
// Calculate distance and angle from center for effects | |
const distance = Math.sqrt(baseX * baseX + baseY * baseY); | |
const maxDistance = (spacing * Math.sqrt(2) * (gridSize - 1)) / 2; | |
const normalizedDistance = distance / maxDistance; | |
const angle = Math.atan2(baseY, baseX); | |
// Apply complex wave effects | |
// 1. Radial wave (expands from center) | |
const radialPhase = (time - normalizedDistance) % 1; | |
const radialWave = Math.sin(radialPhase * Math.PI * 2) * 4; | |
// 2. Spiral wave (rotates around center) | |
const spiralPhase = (angle / (Math.PI * 2) + time * 0.3) % 1; | |
const spiralWave = Math.sin(spiralPhase * Math.PI * 2) * 3; | |
// 3. Breathing effect (entire grid expands/contracts) | |
const breathingX = baseX * breathingFactor; | |
const breathingY = baseY * breathingFactor; | |
// Combine all effects | |
const waveX = centerX + breathingX + Math.cos(angle) * radialWave; | |
const waveY = centerY + breathingY + Math.sin(angle) * radialWave; | |
// Dot size varies with distance and time | |
const baseSize = 1.5 + (1 - normalizedDistance) * 1.5; | |
// Complex pulsing effect | |
const pulseFactor = | |
Math.sin(time * 2 + normalizedDistance * 5) * 0.6 + 1; | |
const size = baseSize * pulseFactor; | |
// Color effects - subtle gradient between white and light blue | |
const blueAmount = | |
Math.sin(time * colorPulseSpeed + normalizedDistance * 3) * 0.3 + | |
0.3; | |
const whiteness = 1 - blueAmount; | |
// Calculate RGB values | |
const r = Math.floor(255 * whiteness + 200 * blueAmount); | |
const g = Math.floor(255 * whiteness + 220 * blueAmount); | |
const b = 255; | |
// Calculate opacity with subtle pulse | |
const opacity = | |
0.5 + | |
Math.sin(time * 1.5 + angle * 3) * 0.2 + | |
normalizedDistance * 0.3; | |
// Draw connecting lines (create a network effect) | |
if (row > 0 && col > 0 && row < gridSize - 1 && col < gridSize - 1) { | |
// Connect to adjacent points | |
const neighbors = [ | |
{ | |
r: row - 1, | |
c: col | |
}, // top | |
{ | |
r: row, | |
c: col + 1 | |
}, // right | |
{ | |
r: row + 1, | |
c: col | |
}, // bottom | |
{ | |
r: row, | |
c: col - 1 | |
} // left | |
]; | |
for (const neighbor of neighbors) { | |
// Calculate neighbor position | |
const nBaseX = (neighbor.c - (gridSize - 1) / 2) * spacing; | |
const nBaseY = (neighbor.r - (gridSize - 1) / 2) * spacing; | |
// Apply breathing effect | |
const nBreathingX = nBaseX * breathingFactor; | |
const nBreathingY = nBaseY * breathingFactor; | |
// Skip center point | |
if ( | |
neighbor.r === Math.floor(gridSize / 2) && | |
neighbor.c === Math.floor(gridSize / 2) | |
) | |
continue; | |
// Calculate distance for line opacity | |
const lineDistance = Math.sqrt( | |
Math.pow(col - neighbor.c, 2) + Math.pow(row - neighbor.r, 2) | |
); | |
const lineOpacity = | |
0.1 + Math.sin(time * 1.5 + lineDistance * 2) * 0.05; | |
// Draw line | |
ctx.beginPath(); | |
ctx.moveTo(waveX, waveY); | |
ctx.lineTo(centerX + nBreathingX, centerY + nBreathingY); | |
ctx.strokeStyle = `rgba(255, 255, 255, ${lineOpacity})`; | |
ctx.lineWidth = 0.5; | |
ctx.stroke(); | |
} | |
} | |
// Draw dot | |
ctx.beginPath(); | |
ctx.arc(waveX, waveY, size, 0, Math.PI * 2); | |
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${opacity})`; | |
ctx.fill(); | |
} | |
} | |
requestAnimationFrame(animate); | |
} | |
requestAnimationFrame(animate); | |
} | |
// 9. NEW: Spiral Galaxy Animation | |
function setupSpiralGalaxy() { | |
const container = document.getElementById("spiral-galaxy"); | |
if (!container) return; | |
container.innerHTML = ""; | |
const canvas = document.createElement("canvas"); | |
canvas.width = 180; | |
canvas.height = 180; | |
canvas.style.position = "absolute"; | |
canvas.style.left = "0"; | |
canvas.style.top = "0"; | |
container.appendChild(canvas); | |
const ctx = canvas.getContext("2d"); | |
const centerX = canvas.width / 2; | |
const centerY = canvas.height / 2; | |
let time = 0; | |
let lastTime = 0; | |
// Spiral parameters | |
const particleCount = 200; | |
const maxRadius = 75; | |
const spiralArms = 3; // Number of spiral arms | |
const rotationSpeed = 0.1; // Base rotation speed | |
// Create particles with initial positions | |
const particles = []; | |
for (let i = 0; i < particleCount; i++) { | |
// Random distance from center (more particles toward center) | |
const distanceFactor = Math.pow(Math.random(), 0.5); // Square root distribution | |
const distance = distanceFactor * maxRadius; | |
// Random angle with spiral arm offset | |
const armIndex = Math.floor(Math.random() * spiralArms); | |
const armOffset = (armIndex / spiralArms) * Math.PI * 2; | |
// Logarithmic spiral formula: r = a*e^(b*θ) | |
// We'll use this to determine the angle offset based on distance | |
const spiralTightness = 0.2; | |
const spiralAngle = Math.log(distance / 5) / spiralTightness; | |
// Initial particle properties | |
particles.push({ | |
distance: distance, | |
angle: spiralAngle + armOffset, | |
armIndex: armIndex, | |
size: 1 + Math.random() * 1.5, | |
opacity: 0.3 + Math.random() * 0.7, | |
// Each particle has its own speed variation | |
speedFactor: 0.8 + Math.random() * 0.4, | |
// Color variation (from white to blue-ish) | |
color: { | |
r: 220 + Math.floor(Math.random() * 35), | |
g: 220 + Math.floor(Math.random() * 35), | |
b: 255 | |
} | |
}); | |
} | |
function animate(timestamp) { | |
if (!lastTime) lastTime = timestamp; | |
const deltaTime = timestamp - lastTime; | |
lastTime = timestamp; | |
time += deltaTime * 0.001; | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Draw a simple center dot | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, 3, 0, Math.PI * 2); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.9)"; | |
ctx.fill(); | |
// Update and draw particles | |
for (const particle of particles) { | |
// Rotation speed decreases with distance (Keplerian rotation) | |
const rotationFactor = 1 / Math.sqrt(particle.distance / 10); | |
// Update angle based on distance from center (closer = faster) | |
particle.angle += | |
rotationSpeed * | |
rotationFactor * | |
particle.speedFactor * | |
deltaTime * | |
0.05; | |
// Calculate position | |
const x = centerX + Math.cos(particle.angle) * particle.distance; | |
const y = centerY + Math.sin(particle.angle) * particle.distance; | |
// Particle size and opacity pulse based on arm position | |
const armPhase = (time * 0.5 + particle.armIndex / spiralArms) % 1; | |
const pulseFactor = Math.sin(armPhase * Math.PI * 2) * 0.3 + 0.7; | |
// Draw particle | |
ctx.beginPath(); | |
ctx.arc(x, y, particle.size * pulseFactor, 0, Math.PI * 2); | |
// Apply color with opacity | |
const finalOpacity = particle.opacity * pulseFactor; | |
ctx.fillStyle = `rgba(${particle.color.r}, ${particle.color.g}, ${particle.color.b}, ${finalOpacity})`; | |
ctx.fill(); | |
// Draw trail for some particles (only the larger ones) | |
if (particle.size > 1.8) { | |
const trailLength = particle.distance * 0.15; // Trail length proportional to distance | |
const trailAngle = particle.angle - 0.1 * rotationFactor; // Trail points backward | |
const trailX = | |
centerX + Math.cos(trailAngle) * (particle.distance - trailLength); | |
const trailY = | |
centerY + Math.sin(trailAngle) * (particle.distance - trailLength); | |
// Draw trail | |
ctx.beginPath(); | |
ctx.moveTo(x, y); | |
ctx.lineTo(trailX, trailY); | |
ctx.strokeStyle = `rgba(${particle.color.r}, ${particle.color.g}, ${ | |
particle.color.b | |
}, ${finalOpacity * 0.3})`; | |
ctx.lineWidth = particle.size * 0.5; | |
ctx.stroke(); | |
} | |
} | |
requestAnimationFrame(animate); | |
} | |
requestAnimationFrame(animate); | |
} | |
// Initialize all preloaders | |
window.addEventListener("load", function () { | |
setupRadialPulse(); | |
setupOrbitalPulse(); | |
setupPendulumWave(); | |
setupPulseWave(); | |
setupConcentricRings(); | |
setupSequentialPulse(); | |
setupOscillatingDots(); | |
setupPulsingGrid(); | |
setupSpiralGalaxy(); | |
addCornerDecorations(); | |
}); | |
})(); |
This file contains hidden or 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
@import url("https://fonts.cdnfonts.com/css/thegoodmonolith"); | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
background: #000; | |
color: #f0f0f0; | |
font-family: "TheGoodMonolith", monospace; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
min-height: 100vh; | |
padding: 20px; | |
} | |
h1 { | |
margin-bottom: 30px; | |
font-size: 24px; | |
letter-spacing: 1px; | |
text-align: center; | |
} | |
.container { | |
display: grid; | |
grid-template-columns: repeat(3, 1fr); | |
gap: 30px; | |
max-width: 1000px; | |
margin: 0 auto; | |
} | |
@media (max-width: 900px) { | |
.container { | |
grid-template-columns: repeat(2, 1fr); | |
} | |
} | |
@media (max-width: 500px) { | |
.container { | |
grid-template-columns: 1fr; | |
} | |
} | |
.animation-container { | |
position: relative; | |
width: 220px; | |
height: 220px; | |
border: 1px solid rgba(255, 255, 255, 0.1); | |
background: rgba(0, 0, 0, 0.5); | |
padding: 10px; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
overflow: visible; | |
transition: border-color 0.3s ease; | |
} | |
.animation-container:hover { | |
border-color: rgba(255, 255, 255, 0.3); | |
} | |
.animation-title { | |
margin-bottom: 10px; | |
font-size: 12px; | |
letter-spacing: 0.5px; | |
text-transform: uppercase; | |
text-align: center; | |
} | |
.circle-container { | |
position: relative; | |
width: 180px; | |
height: 180px; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
} | |
.dot { | |
position: absolute; | |
border-radius: 50%; | |
background: #fff; | |
} | |
.line { | |
position: absolute; | |
background: rgba(255, 255, 255, 0.5); | |
transform-origin: 0% 50%; | |
} | |
.circle { | |
position: absolute; | |
border-radius: 50%; | |
border: 1px solid rgba(255, 255, 255, 0.5); | |
} | |
/* Corner decorations */ | |
.corner { | |
position: absolute; | |
width: 16px; | |
height: 16px; | |
color: white; | |
opacity: 0; | |
z-index: 10; | |
pointer-events: none; | |
transition: opacity 0.3s ease; | |
} | |
.animation-container:hover .corner { | |
opacity: 1; | |
} | |
.top-left { | |
top: -8px; | |
left: -8px; | |
transition-delay: 0s; | |
} | |
.top-right { | |
top: -8px; | |
right: -8px; | |
transform: rotate(90deg); | |
transition-delay: 0.1s; | |
} | |
.bottom-left { | |
bottom: -8px; | |
left: -8px; | |
transform: rotate(-90deg); | |
transition-delay: 0.2s; | |
} | |
.bottom-right { | |
bottom: -8px; | |
right: -8px; | |
transform: rotate(180deg); | |
transition-delay: 0.3s; | |
} | |
/* Animation keyframes */ | |
@keyframes orbit { | |
0% { | |
transform: rotate(0deg); | |
} | |
100% { | |
transform: rotate(360deg); | |
} | |
} | |
@keyframes reverseOrbit { | |
0% { | |
transform: rotate(0deg); | |
} | |
100% { | |
transform: rotate(-360deg); | |
} | |
} | |
@keyframes pulse { | |
0% { | |
transform: scale(0.8); | |
opacity: 0.3; | |
} | |
50% { | |
transform: scale(1.2); | |
opacity: 1; | |
} | |
100% { | |
transform: scale(0.8); | |
opacity: 0.3; | |
} | |
} | |
@keyframes ripple { | |
0% { | |
transform: scale(0.1); | |
opacity: 0.6; | |
} | |
100% { | |
transform: scale(1); | |
opacity: 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment