← back to Model Arena
data/artifacts/0e969e848172/kimi.html
261 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gravity Sandbox</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
font-family: sans-serif;
}
canvas {
display: block;
}
#controls {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.8);
padding: 15px;
border-radius: 8px;
color: white;
user-select: none;
border: 1px solid #333;
}
button {
margin: 5px;
padding: 8px 15px;
cursor: pointer;
background: #222;
color: white;
border: 1px solid #444;
border-radius: 4px;
font-size: 14px;
}
button:hover {
background: #333;
border-color: #666;
}
button.active {
background: #2a5;
border-color: #4c7;
}
#info {
margin-top: 10px;
font-size: 12px;
color: #888;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="controls">
<button id="clearBtn">Clear All</button>
<button id="trailBtn" class="active">Trails: ON</button>
<div id="info">Click & drag to launch planets</div>
</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
const G = 0.8;
const SOFTENING = 10;
const TRAIL_LENGTH = 150;
const TIME_STEP = 0.4;
let planets = [];
let trails = true;
let isDragging = false;
let dragStart = {x: 0, y: 0};
let dragCurrent = {x: 0, y: 0};
class Planet {
constructor(x, y, vx, vy, mass) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.mass = mass;
this.radius = Math.cbrt(mass) * 2.5;
this.color = `hsl(${Math.random() * 360}, 80%, 60%)`;
this.history = [];
}
update() {
if (trails) {
this.history.push({x: this.x, y: this.y});
if (this.history.length > TRAIL_LENGTH) {
this.history.shift();
}
} else {
this.history = [];
}
this.x += this.vx * TIME_STEP;
this.y += this.vy * TIME_STEP;
if (this.x < -1000 || this.x > canvas.width + 1000 ||
this.y < -1000 || this.y > canvas.height + 1000) {
return false;
}
return true;
}
draw() {
if (trails && this.history.length > 1) {
ctx.lineWidth = 2;
for (let i = 0; i < this.history.length - 1; i++) {
const alpha = (i / this.history.length) * 0.6;
ctx.strokeStyle = this.color.replace(')', `, ${alpha})`).replace('hsl', 'hsla');
ctx.beginPath();
ctx.moveTo(this.history[i].x, this.history[i].y);
ctx.lineTo(this.history[i+1].x, this.history[i+1].y);
ctx.stroke();
}
}
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
const gradient = ctx.createRadialGradient(
this.x, this.y, 0,
this.x, this.y, this.radius * 3
);
gradient.addColorStop(0, this.color.replace(')', ', 0.4)').replace('hsl', 'hsla'));
gradient.addColorStop(1, 'transparent');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius * 3, 0, Math.PI * 2);
ctx.fill();
}
}
function applyGravity() {
for (let i = 0; i < planets.length; i++) {
for (let j = i + 1; j < planets.length; j++) {
const p1 = planets[i];
const p2 = planets[j];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const distSq = dx * dx + dy * dy;
const dist = Math.sqrt(distSq);
if (dist < p1.radius + p2.radius) {
const overlap = (p1.radius + p2.radius - dist) * 0.5;
const nx = dx / dist;
const ny = dy / dist;
p1.x -= nx * overlap;
p1.y -= ny * overlap;
p2.x += nx * overlap;
p2.y += ny * overlap;
}
const force = G * p1.mass * p2.mass / (distSq + SOFTENING * SOFTENING);
const fx = force * dx / dist;
const fy = force * dy / dist;
p1.vx += fx / p1.mass;
p1.vy += fy / p1.mass;
p2.vx -= fx / p2.mass;
p2.vy -= fy / p2.mass;
}
}
}
function animate() {
if (trails) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.15)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
applyGravity();
for (let i = planets.length - 1; i >= 0; i--) {
const alive = planets[i].update();
if (!alive) {
planets.splice(i, 1);
} else {
planets[i].draw();
}
}
if (isDragging) {
ctx.beginPath();
ctx.moveTo(dragStart.x, dragStart.y);
ctx.lineTo(dragCurrent.x, dragCurrent.y);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.stroke();
ctx.setLineDash([]);
ctx.beginPath();
ctx.arc(dragStart.x, dragStart.y, 8, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fill();
}
requestAnimationFrame(animate);
}
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
dragStart.x = e.clientX;
dragStart.y = e.clientY;
dragCurrent.x = e.clientX;
dragCurrent.y = e.clientY;
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging) {
dragCurrent.x = e.clientX;
dragCurrent.y = e.clientY;
}
});
canvas.addEventListener('mouseup', (e) => {
if (isDragging) {
const vx = (dragStart.x - e.clientX) * 0.08;
const vy = (dragStart.y - e.clientY) * 0.08;
const mass = 15 + Math.random() * 50;
planets.push(new Planet(dragStart.x, dragStart.y, vx, vy, mass));
isDragging = false;
}
});
document.getElementById('clearBtn').addEventListener('click', () => {
planets = [];
});
document.getElementById('trailBtn').addEventListener('click', (e) => {
trails = !trails;
e.target.textContent = trails ? 'Trails: ON' : 'Trails: OFF';
e.target.classList.toggle('active');
if (!trails) {
for (const p of planets) {
p.history = [];
}
}
});
planets.push(new Planet(canvas.width/2, canvas.height/2, 0, 0, 300));
animate();
</script>
</body>
</html>