Meta Description" name="description" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon Gravity Sandbox</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #050505;
touch-action: none;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
canvas {
display: block;
}
.ui-panel {
backdrop-filter: blur(10px);
background: rgba(20, 20, 20, 0.7);
border: 1px solid rgba(255, 255, 255, 0.1);
}
</style>
</head>
<body>
<!-- UI Overlay -->
<div class="fixed top-4 left-4 z-10 flex flex-col gap-4 pointer-events-none">
<div class="ui-panel p-4 rounded-2xl pointer-events-auto shadow-2xl">
<h1 class="text-white text-xl font-bold mb-1">Neon Gravity</h1>
<p class="text-gray-400 text-sm mb-4">Click/Tap to blast. Drag to pull.</p>
<div class="flex flex-col gap-3">
<button id="clearBtn" class="bg-red-500/20 hover:bg-red-500/40 text-red-400 py-2 px-4 rounded-lg border border-red-500/30 transition-all active:scale-95 text-sm font-medium">
Clear Particles
</button>
<div class="flex items-center justify-between gap-4">
<span class="text-gray-300 text-xs uppercase tracking-wider">Count</span>
<span id="particleCount" class="text-cyan-400 font-mono font-bold">0</span>
</div>
</div>
</div>
</div>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const particleCountDisplay = document.getElementById('particleCount');
const clearBtn = document.getElementById('clearBtn');
let width, height;
let particles = [];
const mouse = { x: -1000, y: -1000, active: false };
// Configuration
const GRAVITY = 0.15;
const FRICTION = 0.98;
const BOUNCE = 0.7;
const MOUSE_STRENGTH = 0.5;
const MAX_PARTICLES = 400;
function init() {
resize();
animate();
}
function resize() {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width * window.devicePixelRatio;
canvas.height = height * window.devicePixelRatio;
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
}
class Particle {
constructor(x, y, vx, vy, color) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.radius = Math.random() * 4 + 2;
this.color = color;
this.life = 1.0;
this.decay = Math.random() * 0.005 + 0.002;
}
update() {
// Interaction with mouse (Pulling)
if (mouse.active) {
const dx = mouse.x - this.x;
const dy = mouse.y - this.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 300) {
const force = (300 - dist) / 300;
this.vx += dx * force * 0.02;
this.vy += dy * force * 0.02;
}
}
// Physics
this.vy += GRAVITY;
this.vx *= FRICTION;
this.vy *= FRICTION;
this.x += this.vx;
this.y += this.vy;
// Floor/Wall collisions
if (this.y + this.radius > height) {
this.y = height - this.radius;
this.vy *= -BOUNCE;
this.vx *= 0.95; // Extra friction on floor
}
if (this.x + this.radius > width) {
this.x = width - this.radius;
this.vx *= -BOUNCE;
} else if (this.x - this.radius < 0) {
this.x = this.radius;
this.vx *= -BOUNCE;
}
this.life -= this.decay;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color.replace('opacity', this.life);
ctx.shadowBlur = 10;
ctx.shadowColor = this.color.replace('opacity', '0.5');
ctx.fill();
ctx.closePath();
}
}
function createExplosion(x, y) {
const colors = [
'rgba(34, 211, 238, opacity)', // Cyan
'rgba(168, 85, 247, opacity)', // Purple
'rgba(236, 72, 153, opacity)', // Pink
'rgba(52, 211, 153, opacity)' // Emerald
];
const color = colors[Math.floor(Math.random() * colors.length)];
for (let i = 0; i < 20; i++) {
if (particles.length >= MAX_PARTICLES) particles.shift();
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 15 + 2;
particles.push(new Particle(
x, y,
Math.cos(angle) * speed,
Math.sin(angle) * speed,
color
));
}
}
function animate() {
// Semi-transparent clear for motion trail
ctx.fillStyle = 'rgba(5, 5, 5, 0.2)';
ctx.fillRect(0, 0, width, height);
particles = particles.filter(p => p.life > 0);
particles.forEach(p => {
p.update();
p.draw();
});
// Reset shadow for performance
ctx.shadowBlur = 0;
particleCountDisplay.textContent = particles.length;
requestAnimationFrame(animate);
}
// Input Handling
function handleStart(e) {
const pos = e.touches ? e.touches[0] : e;
mouse.x = pos.clientX;
mouse.y = pos.clientY;
mouse.active = true;
createExplosion(mouse.x, mouse.y);
}
function handleMove(e) {
const pos = e.touches ? e.touches[0] : e;
mouse.x = pos.clientX;
mouse.y = pos.clientY;
if (mouse.active && Math.random() > 0.8) {
// Faint trail while dragging
createExplosion(mouse.x, mouse.y);
}
}
function handleEnd() {
mouse.active = false;
}
window.addEventListener('mousedown', handleStart);
window.addEventListener('mousemove', handleMove);
window.addEventListener('mouseup', handleEnd);
window.addEventListener('touchstart', (e) => {
e.preventDefault();
handleStart(e);
}, { passive: false });
window.addEventListener('touchmove', (e) => {
e.preventDefault();
handleMove(e);
}, { passive: false });
window.addEventListener('touchend', handleEnd);
window.addEventListener('resize', resize);
clearBtn.onclick = () => { particles = []; };
window.onload = init;
</script>
</body>
</html>
3
2
132KB
406KB
359.0ms
396.0ms
359.0ms