Magnetic Particle Vortex
Home
Snippets
Magnetic Particle Vortex
HTML
CSS
JS
<div class="vortex-card"> <div class="slider-viewport" id="track-zone"> <canvas id="vortex-canvas"></canvas> </div> <div class="meta-label">Module // Kinetic Gravity</div> <div class="meta-title">Vortex Core Interface</div> </div>
:root { --bg: #040406; --surface: #0a0b12; --border: rgba(255, 255, 255, 0.04); --accent: #00f3ff; } body { margin: 0; background-color: var(--bg); color: #f8fafc; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; height: 100vh; display: flex; justify-content: center; align-items: center; user-select: none; -webkit-user-select: none; padding: 20px; box-sizing: border-box; } .vortex-card { position: relative; width: 100%; max-width: 320px; background: var(--surface); border: 1px solid var(--border); border-radius: 28px; padding: 24px; box-sizing: border-box; box-shadow: 0 40px 80px rgba(0, 0, 0, 0.6); } .slider-viewport { position: relative; width: 100%; height: 160px; background: #000000; border-radius: 18px; margin-bottom: 20px; overflow: hidden; cursor: grab; border: 1px solid rgba(255, 255, 255, 0.01); } .slider-viewport:active { cursor: grabbing; } canvas { display: block; width: 100%; height: 100%; } .meta-label { font-size: 10px; font-weight: 700; color: var(--accent); letter-spacing: 2px; text-transform: uppercase; margin-bottom: 4px; } .meta-title { font-size: 16px; font-weight: 600; color: #fff; }
const zone = document.getElementById('track-zone'); const canvas = document.getElementById('vortex-canvas'); const ctx = canvas.getContext('2d'); let particles = []; const maxParticles = 60; let coreX = 60; let targetCoreX = 60; let isDragging = false; function initVortex() { const rect = zone.getBoundingClientRect(); canvas.width = rect.width; canvas.height = rect.height; coreX = canvas.width / 2; targetCoreX = coreX; particles = []; for (let i = 0; i < maxParticles; i++) { particles.push({ angle: Math.random() * Math.PI * 2, radius: Math.random() * 25 + 10, speed: Math.random() * 0.04 + 0.02, size: Math.random() * 1.2 + 0.8, pulsePhase: Math.random() * 10 }); } } function renderLoop() { ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'; ctx.fillRect(0, 0, canvas.width, canvas.height); const centerY = canvas.height / 2; coreX += (targetCoreX - coreX) * 0.15; ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(20, centerY); ctx.lineTo(canvas.width - 20, centerY); ctx.stroke(); particles.forEach(p => { p.angle += p.speed; let currentRadius = isDragging ? p.radius * 0.6 : p.radius; let px = coreX + Math.cos(p.angle) * currentRadius; let py = centerY + Math.sin(p.angle) * currentRadius * 0.7; let brightness = Math.sin(p.angle + p.pulsePhase) * 0.3 + 0.7; ctx.fillStyle = `rgba(0, 243, 255, ${brightness})`; ctx.beginPath(); ctx.arc(px, py, p.size, 0, Math.PI * 2); ctx.fill(); }); ctx.strokeStyle = isDragging ? 'rgba(0, 243, 255, 0.8)' : 'rgba(0, 243, 255, 0.25)'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(coreX, centerY, isDragging ? 8 : 12, 0, Math.PI * 2); ctx.stroke(); requestAnimationFrame(renderLoop); } function processInput(clientX) { const rect = canvas.getBoundingClientRect(); let relativeX = clientX - rect.left; targetCoreX = Math.max(30, Math.min(relativeX, canvas.width - 30)); } zone.addEventListener('mousedown', (e) => { isDragging = true; processInput(e.clientX); }); window.addEventListener('mousemove', (e) => { if (isDragging) processInput(e.clientX); }); window.addEventListener('mouseup', () => isDragging = false); zone.addEventListener('touchstart', (e) => { isDragging = true; processInput(e.touches[0].clientX); }, { passive: true }); window.addEventListener('touchmove', (e) => { if (isDragging) processInput(e.touches[0].clientX); }, { passive: true }); window.addEventListener('touchend', () => isDragging = false); window.addEventListener('resize', initVortex); initVortex(); renderLoop();
Ad #1
Ad #2
Scroll to Top