← back to Model Arena

data/artifacts/2ca024f41f3c/grok.html

355 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Solar System Orrery</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: #050510;
    color: #e0e0ff;
    font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
    overflow: hidden;
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }
  canvas {
    display: block;
    cursor: crosshair;
  }
  #ui {
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 16px;
    background: rgba(10, 12, 30, 0.85);
    border: 1px solid rgba(120, 140, 255, 0.25);
    border-radius: 12px;
    padding: 12px 20px;
    backdrop-filter: blur(8px);
    box-shadow: 0 8px 32px rgba(0,0,0,0.5);
    z-index: 10;
  }
  #ui label {
    font-size: 13px;
    opacity: 0.85;
    white-space: nowrap;
  }
  #speed {
    width: 140px;
    accent-color: #6af;
  }
  #pause {
    background: linear-gradient(135deg, #3a4a8a, #2a3a6a);
    color: #eef;
    border: 1px solid rgba(140,160,255,0.4);
    border-radius: 8px;
    padding: 8px 16px;
    font-size: 13px;
    cursor: pointer;
    min-width: 72px;
  }
  #pause:hover { background: linear-gradient(135deg, #4a5aaa, #3a4a8a); }
  #tooltip {
    position: fixed;
    pointer-events: none;
    background: rgba(15, 18, 40, 0.92);
    border: 1px solid rgba(140, 160, 255, 0.45);
    color: #fff;
    padding: 6px 12px;
    border-radius: 6px;
    font-size: 13px;
    letter-spacing: 0.03em;
    opacity: 0;
    transition: opacity 0.12s;
    z-index: 20;
    white-space: nowrap;
  }
  #title {
    position: fixed;
    top: 18px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 14px;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    opacity: 0.5;
    font-weight: 300;
  }
</style>
</head>
<body>
<div id="title">Solar System Orrery</div>
<canvas id="c"></canvas>
<div id="tooltip"></div>
<div id="ui">
  <label for="speed">Speed</label>
  <input type="range" id="speed" min="0" max="5" step="0.05" value="1">
  <button id="pause">Pause</button>
</div>
<script>
(function () {
  const canvas = document.getElementById('c');
  const ctx = canvas.getContext('2d');
  const tooltip = document.getElementById('tooltip');
  const speedSlider = document.getElementById('speed');
  const pauseBtn = document.getElementById('pause');

  let W, H, cx, cy;
  let dpr = 1;
  let paused = false;
  let speedMul = 1;
  let t = 0;
  let last = performance.now();
  let mouse = { x: -9999, y: -9999 };
  let stars = [];

  // Orbital periods in Earth years (approx). Distances scaled for visual layout.
  // radius = visual orbit radius in "AU units", size = planet draw radius
  const planets = [
    { name: 'Mercury', color: '#b0b0b0', radius: 0.39, size: 3.2, period: 0.241, angle: 0.8 },
    { name: 'Venus',   color: '#e8cda0', radius: 0.72, size: 5.5, period: 0.615, angle: 2.1 },
    { name: 'Earth',   color: '#6ab0ff', radius: 1.00, size: 5.8, period: 1.000, angle: 0.4 },
    { name: 'Mars',    color: '#e07050', radius: 1.52, size: 4.2, period: 1.881, angle: 3.5 },
    { name: 'Jupiter', color: '#d4a86a', radius: 2.80, size: 12,  period: 11.86, angle: 1.2 },
    { name: 'Saturn',  color: '#e8d5a0', radius: 3.60, size: 10,  period: 29.46, angle: 4.0, ring: true },
    { name: 'Uranus',  color: '#7ec8e0', radius: 4.40, size: 7.5, period: 84.01, angle: 0.6 },
    { name: 'Neptune', color: '#4a6aff', radius: 5.10, size: 7.2, period: 164.8, angle: 2.8 }
  ];

  const SUN_SIZE = 18;

  function resize() {
    dpr = Math.min(window.devicePixelRatio || 1, 2);
    W = window.innerWidth;
    H = window.innerHeight;
    canvas.width = W * dpr;
    canvas.height = H * dpr;
    canvas.style.width = W + 'px';
    canvas.style.height = H + 'px';
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    cx = W / 2;
    cy = H / 2 - 10;
    buildStars();
  }

  function buildStars() {
    stars = [];
    const n = Math.floor((W * H) / 3500);
    for (let i = 0; i < n; i++) {
      stars.push({
        x: Math.random() * W,
        y: Math.random() * H,
        r: Math.random() * 1.2 + 0.2,
        a: Math.random() * 0.6 + 0.2
      });
    }
  }

  function scale() {
    // Fit outermost orbit with padding
    const maxR = planets[planets.length - 1].radius;
    const pad = Math.min(W, H) * 0.08;
    return (Math.min(W, H) / 2 - pad - 20) / maxR;
  }

  function planetPos(p, s) {
    const ang = p.angle + (t * Math.PI * 2) / p.period;
    const r = p.radius * s;
    return {
      x: cx + Math.cos(ang) * r,
      y: cy + Math.sin(ang) * r,
      ang
    };
  }

  function drawStars() {
    for (const st of stars) {
      ctx.beginPath();
      ctx.fillStyle = `rgba(220,225,255,${st.a})`;
      ctx.arc(st.x, st.y, st.r, 0, Math.PI * 2);
      ctx.fill();
    }
  }

  function drawOrbit(p, s) {
    ctx.beginPath();
    ctx.strokeStyle = 'rgba(120, 140, 200, 0.18)';
    ctx.lineWidth = 1;
    ctx.arc(cx, cy, p.radius * s, 0, Math.PI * 2);
    ctx.stroke();
  }

  function drawSun() {
    const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, SUN_SIZE * 2.5);
    g.addColorStop(0, '#fff8e0');
    g.addColorStop(0.25, '#ffe060');
    g.addColorStop(0.5, '#ff9020');
    g.addColorStop(1, 'rgba(255,80,0,0)');
    ctx.beginPath();
    ctx.fillStyle = g;
    ctx.arc(cx, cy, SUN_SIZE * 2.5, 0, Math.PI * 2);
    ctx.fill();

    ctx.beginPath();
    ctx.fillStyle = '#ffcc33';
    ctx.arc(cx, cy, SUN_SIZE, 0, Math.PI * 2);
    ctx.fill();

    // core highlight
    ctx.beginPath();
    ctx.fillStyle = 'rgba(255,255,220,0.55)';
    ctx.arc(cx - 4, cy - 4, SUN_SIZE * 0.35, 0, Math.PI * 2);
    ctx.fill();
  }

  function drawPlanet(p, pos) {
    // subtle glow
    const glow = ctx.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, p.size * 2.2);
    glow.addColorStop(0, p.color);
    glow.addColorStop(0.4, p.color + '88');
    glow.addColorStop(1, 'transparent');
    ctx.beginPath();
    ctx.fillStyle = glow;
    ctx.arc(pos.x, pos.y, p.size * 2.2, 0, Math.PI * 2);
    ctx.fill();

    // Saturn ring behind
    if (p.ring) {
      ctx.save();
      ctx.translate(pos.x, pos.y);
      ctx.rotate(0.4);
      ctx.scale(1, 0.35);
      ctx.beginPath();
      ctx.strokeStyle = 'rgba(210, 190, 140, 0.55)';
      ctx.lineWidth = 2.5;
      ctx.arc(0, 0, p.size * 1.9, 0, Math.PI * 2);
      ctx.stroke();
      ctx.beginPath();
      ctx.strokeStyle = 'rgba(180, 160, 110, 0.35)';
      ctx.lineWidth = 1.5;
      ctx.arc(0, 0, p.size * 2.3, 0, Math.PI * 2);
      ctx.stroke();
      ctx.restore();
    }

    ctx.beginPath();
    ctx.fillStyle = p.color;
    ctx.arc(pos.x, pos.y, p.size, 0, Math.PI * 2);
    ctx.fill();

    // specular
    ctx.beginPath();
    ctx.fillStyle = 'rgba(255,255,255,0.35)';
    ctx.arc(pos.x - p.size * 0.3, pos.y - p.size * 0.3, p.size * 0.35, 0, Math.PI * 2);
    ctx.fill();

    // Saturn ring in front (lower half)
    if (p.ring) {
      ctx.save();
      ctx.translate(pos.x, pos.y);
      ctx.rotate(0.4);
      ctx.scale(1, 0.35);
      ctx.beginPath();
      ctx.strokeStyle = 'rgba(220, 200, 150, 0.5)';
      ctx.lineWidth = 2.5;
      ctx.arc(0, 0, p.size * 1.9, 0.05, Math.PI - 0.05);
      ctx.stroke();
      ctx.restore();
    }
  }

  function hitTest(s) {
    // Check sun
    const ds = Math.hypot(mouse.x - cx, mouse.y - cy);
    if (ds < SUN_SIZE + 4) return { name: 'Sun', x: cx, y: cy };

    for (let i = planets.length - 1; i >= 0; i--) {
      const p = planets[i];
      const pos = planetPos(p, s);
      const d = Math.hypot(mouse.x - pos.x, mouse.y - pos.y);
      const hitR = Math.max(p.size + 6, 10);
      if (d < hitR) return { name: p.name, x: pos.x, y: pos.y };
    }
    return null;
  }

  function frame(now) {
    const dt = Math.min((now - last) / 1000, 0.1);
    last = now;
    if (!paused) t += dt * speedMul;

    const s = scale();

    ctx.clearRect(0, 0, W, H);

    // deep space vignette
    const vg = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.max(W, H) * 0.7);
    vg.addColorStop(0, '#0a0a22');
    vg.addColorStop(1, '#030308');
    ctx.fillStyle = vg;
    ctx.fillRect(0, 0, W, H);

    drawStars();

    for (const p of planets) drawOrbit(p, s);
    drawSun();

    const positions = [];
    for (const p of planets) {
      const pos = planetPos(p, s);
      positions.push(pos);
      drawPlanet(p, pos);
    }

    const hit = hitTest(s);
    if (hit) {
      tooltip.textContent = hit.name;
      tooltip.style.opacity = '1';
      let tx = hit.x + 14;
      let ty = hit.y - 28;
      if (tx + 80 > W) tx = hit.x - 80;
      if (ty < 8) ty = hit.y + 18;
      tooltip.style.left = tx + 'px';
      tooltip.style.top = ty + 'px';
      canvas.style.cursor = 'pointer';
    } else {
      tooltip.style.opacity = '0';
      canvas.style.cursor = 'crosshair';
    }

    requestAnimationFrame(frame);
  }

  speedSlider.addEventListener('input', () => {
    speedMul = parseFloat(speedSlider.value);
  });

  pauseBtn.addEventListener('click', () => {
    paused = !paused;
    pauseBtn.textContent = paused ? 'Resume' : 'Pause';
  });

  canvas.addEventListener('mousemove', (e) => {
    mouse.x = e.clientX;
    mouse.y = e.clientY;
  });
  canvas.addEventListener('mouseleave', () => {
    mouse.x = -9999;
    mouse.y = -9999;
  });

  window.addEventListener('resize', resize);
  resize();
  requestAnimationFrame(frame);
})();
</script>
</body>
</html>