← back to Model Arena

data/artifacts/097db196034e/gpt.html

476 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>AI Constellation Arena</title>
<style>
  html, body {
    margin: 0;
    padding: 0;
    background: radial-gradient(circle at top, #050815 0%, #02030a 45%, #000000 100%);
    height: 100%;
    overflow: hidden;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    color: #9be8ff;
  }
  canvas {
    display: block;
  }
  .overlay-title {
    position: fixed;
    top: 12px;
    left: 16px;
    font-size: 14px;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: #52e9ff;
    text-shadow: 0 0 8px rgba(0, 255, 255, 0.65);
    pointer-events: none;
    user-select: none;
  }
  .overlay-subtitle {
    position: fixed;
    top: 28px;
    left: 16px;
    font-size: 10px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: #2fa8ff;
    opacity: 0.7;
    pointer-events: none;
    user-select: none;
  }
</style>
</head>
<body>
<canvas id="constellation"></canvas>
<div class="overlay-title">AI CONSTELLATION ARENA</div>
<div class="overlay-subtitle">FORCE-DIRECTED MESSAGE SPACE</div>

<script>
(function() {
  const canvas = document.getElementById('constellation');
  const ctx = canvas.getContext('2d');
  let width = window.innerWidth;
  let height = window.innerHeight;
  canvas.width = width;
  canvas.height = height;

  window.addEventListener('resize', () => {
    width = window.innerWidth;
    height = window.innerHeight;
    canvas.width = width;
    canvas.height = height;
  });

  const NODE_COUNT = Math.min(36, Math.floor((width * height) / 26000) + 16);
  const LINK_DISTANCE = 140;
  const LINK_STIFFNESS = 0.012;
  const REPULSION = 8000;
  const DAMPING = 0.96;
  const CENTERING = 0.001;
  const NODE_RADIUS_BASE = 4;
  const HOVER_RADIUS = 60;
  const MESSAGE_SPEED = 0.4;
  const MESSAGE_INTERVAL = 600;
  const MAX_MESSAGES = 80;

  let nodes = [];
  let links = [];
  let messages = [];
  let mouse = { x: -9999, y: -9999, active: false };
  let lastMessageTime = 0;

  function randRange(a, b) {
    return a + Math.random() * (b - a);
  }

  function createNodes() {
    nodes = [];
    const cx = width / 2;
    const cy = height / 2;
    const radius = Math.min(width, height) * 0.4;
    for (let i = 0; i < NODE_COUNT; i++) {
      const angle = (i / NODE_COUNT) * Math.PI * 2 + randRange(-0.25, 0.25);
      const r = radius * (0.4 + Math.random() * 0.6);
      const x = cx + Math.cos(angle) * r + randRange(-40, 40);
      const y = cy + Math.sin(angle) * r + randRange(-40, 40);
      nodes.push({
        id: i,
        x,
        y,
        vx: randRange(-0.2, 0.2),
        vy: randRange(-0.2, 0.2),
        fx: 0,
        fy: 0,
        mass: 1.0 + Math.random() * 0.5,
        size: NODE_RADIUS_BASE + Math.random() * 2.2,
        pulse: Math.random() * Math.PI * 2
      });
    }
  }

  function createLinks() {
    links = [];
    const k = 3 + Math.floor(Math.random() * 3);
    for (let i = 0; i < nodes.length; i++) {
      const n = nodes[i];
      let candidates = nodes.map((m, idx) => ({ node: m, idx }));
      candidates = candidates.filter(obj => obj.idx !== i);
      candidates.sort((a, b) => {
        const dx1 = a.node.x - n.x;
        const dy1 = a.node.y - n.y;
        const dx2 = b.node.x - n.x;
        const dy2 = b.node.y - n.y;
        return dx1*dx1 + dy1*dy1 - (dx2*dx2 + dy2*dy2);
      });
      const connections = Math.min(k, candidates.length);
      for (let j = 0; j < connections; j++) {
        const targetIndex = candidates[j].idx;
        if (i < targetIndex && !links.some(l => (l.source === i && l.target === targetIndex) || (l.source === targetIndex && l.target === i))) {
          links.push({
            source: i,
            target: targetIndex,
            strength: LINK_STIFFNESS * (0.8 + Math.random() * 0.4),
            baseDistance: LINK_DISTANCE * (0.8 + Math.random() * 0.4)
          });
        }
      }
    }
  }

  function init() {
    createNodes();
    createLinks();
  }

  function spawnMessage() {
    if (links.length === 0) return;
    const link = links[Math.floor(Math.random() * links.length)];
    const forward = Math.random() < 0.5;
    messages.push({
      link,
      t: 0,
      dir: forward ? 1 : -1
    });
    if (messages.length > MAX_MESSAGES) {
      messages.splice(0, messages.length - MAX_MESSAGES);
    }
  }

  canvas.addEventListener('mousemove', e => {
    const rect = canvas.getBoundingClientRect();
    mouse.x = e.clientX - rect.left;
    mouse.y = e.clientY - rect.top;
    mouse.active = true;
  });

  canvas.addEventListener('mouseleave', () => {
    mouse.active = false;
    mouse.x = -9999;
    mouse.y = -9999;
  });

  function applyForces(dt) {
    const dtSec = dt / 16.67;

    for (let i = 0; i < nodes.length; i++) {
      nodes[i].fx = 0;
      nodes[i].fy = 0;
    }

    for (let i = 0; i < nodes.length; i++) {
      for (let j = i + 1; j < nodes.length; j++) {
        const a = nodes[i];
        const b = nodes[j];
        let dx = b.x - a.x;
        let dy = b.y - a.y;
        let dist2 = dx * dx + dy * dy + 0.01;
        let dist = Math.sqrt(dist2);
        const force = REPULSION / dist2;
        dx /= dist;
        dy /= dist;
        a.fx -= dx * force;
        a.fy -= dy * force;
        b.fx += dx * force;
        b.fy += dy * force;
      }
    }

    for (let i = 0; i < links.length; i++) {
      const link = links[i];
      const a = nodes[link.source];
      const b = nodes[link.target];
      let dx = b.x - a.x;
      let dy = b.y - a.y;
      let dist = Math.sqrt(dx * dx + dy * dy) || 0.001;
      const diff = dist - link.baseDistance;
      const force = diff * link.strength;
      dx /= dist;
      dy /= dist;
      const fx = dx * force;
      const fy = dy * force;
      a.fx += fx;
      a.fy += fy;
      b.fx -= fx;
      b.fy -= fy;
    }

    const cx = width / 2;
    const cy = height / 2;
    for (let i = 0; i < nodes.length; i++) {
      const n = nodes[i];
      const dx = cx - n.x;
      const dy = cy - n.y;
      n.fx += dx * CENTERING;
      n.fy += dy * CENTERING;

      n.vx += (n.fx / n.mass) * dtSec;
      n.vy += (n.fy / n.mass) * dtSec;
      n.vx *= DAMPING;
      n.vy *= DAMPING;

      n.x += n.vx * dtSec * 60;
      n.y += n.vy * dtSec * 60;

      const margin = 80;
      if (n.x < margin) { n.x = margin; n.vx *= -0.3; }
      if (n.x > width - margin) { n.x = width - margin; n.vx *= -0.3; }
      if (n.y < margin) { n.y = margin; n.vy *= -0.3; }
      if (n.y > height - margin) { n.y = height - margin; n.vy *= -0.3; }

      n.pulse += dtSec * 1.4;
    }
  }

  function updateMessages(dt) {
    const dtSec = dt / 16.67;
    for (let i = messages.length - 1; i >= 0; i--) {
      const m = messages[i];
      m.t += MESSAGE_SPEED * dtSec;
      if (m.t > 1.2 || m.t < -0.2) {
        messages.splice(i, 1);
      }
    }
  }

  function isNeighborMap() {
    const neighbor = new Map();
    for (let i = 0; i < nodes.length; i++) {
      neighbor.set(i, false);
    }
    if (!mouse.active) return neighbor;
    let hovered = -1;
    let minDist2 = HOVER_RADIUS * HOVER_RADIUS;
    for (let i = 0; i < nodes.length; i++) {
      const n = nodes[i];
      const dx = n.x - mouse.x;
      const dy = n.y - mouse.y;
      const d2 = dx * dx + dy * dy;
      if (d2 < minDist2) {
        minDist2 = d2;
        hovered = i;
      }
    }
    if (hovered === -1) return neighbor;
    neighbor.set(hovered, true);
    for (let i = 0; i < links.length; i++) {
      const l = links[i];
      if (l.source === hovered) neighbor.set(l.target, true);
      if (l.target === hovered) neighbor.set(l.source, true);
    }
    return neighbor;
  }

  function drawBackground() {
    ctx.clearRect(0, 0, width, height);
    const grd = ctx.createRadialGradient(
      width * 0.5, height * 0.0, 0,
      width * 0.5, height * 0.0, Math.max(width, height) * 0.9
    );
    grd.addColorStop(0, "rgba(20, 40, 80, 0.6)");
    grd.addColorStop(0.35, "rgba(2, 6, 30, 0.9)");
    grd.addColorStop(1, "rgba(0, 0, 0, 1)");
    ctx.fillStyle = grd;
    ctx.fillRect(0, 0, width, height);

    const stars = 80;
    ctx.save();
    ctx.globalAlpha = 0.6;
    ctx.fillStyle = "#0a1726";
    for (let i = 0; i < stars; i++) {
      const x = (i * 9973) % width;
      const y = (i * 7919) % height;
      const r = ((i * 37) % 10) / 10 * 0.8 + 0.2;
      ctx.beginPath();
      ctx.arc(x, y, r, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.restore();
  }

  function drawLinks(neighbor) {
    ctx.save();
    for (let i = 0; i < links.length; i++) {
      const l = links[i];
      const a = nodes[l.source];
      const b = nodes[l.target];

      let activeLevel = 0;
      for (let j = 0; j < messages.length; j++) {
        const m = messages[j];
        if (m.link === l) {
          const t = Math.max(0, Math.min(1, (m.dir === 1 ? m.t : 1 - m.t)));
          const bump = Math.sin(t * Math.PI);
          if (bump > activeLevel) activeLevel = bump;
        }
      }

      const baseAlpha = 0.16;
      const glowAlpha = baseAlpha + activeLevel * 0.45;
      const baseWidth = 0.7;
      const widthLine = baseWidth + activeLevel * 1.7;
      const neighborHighlight =
        mouse.active &&
        (neighbor.get(l.source) || neighbor.get(l.target));

      ctx.lineWidth = neighborHighlight ? widthLine + 0.3 : widthLine;
      ctx.strokeStyle = neighborHighlight
        ? `rgba(120, 255, 255, ${glowAlpha + 0.12})`
        : `rgba(0, 220, 255, ${glowAlpha})`;

      ctx.beginPath();
      ctx.moveTo(a.x, a.y);
      ctx.lineTo(b.x, b.y);
      ctx.stroke();

      if (activeLevel > 0.01) {
        const grad = ctx.createLinearGradient(a.x, a.y, b.x, b.y);
        const midColor = `rgba(0, 255, 255, ${0.25 + activeLevel * 0.5})`;
        grad.addColorStop(0, "rgba(0,0,0,0)");
        grad.addColorStop(0.5, midColor);
        grad.addColorStop(1, "rgba(0,0,0,0)");
        ctx.strokeStyle = grad;
        ctx.lineWidth = widthLine * 1.5;
        ctx.beginPath();
        ctx.moveTo(a.x, a.y);
        ctx.lineTo(b.x, b.y);
        ctx.stroke();
      }
    }
    ctx.restore();
  }

  function drawMessages() {
    ctx.save();
    for (let i = 0; i < messages.length; i++) {
      const m = messages[i];
      const { link, t, dir } = m;
      const a = nodes[link.source];
      const b = nodes[link.target];

      const tt = Math.max(0, Math.min(1, dir === 1 ? t : 1 - t));
      const x = a.x + (b.x - a.x) * tt;
      const y = a.y + (b.y - a.y) * tt;

      const scale = 1 + Math.sin(tt * Math.PI) * 1.4;
      const radius = 2.0 * scale;

      const alpha = 0.5 + Math.sin(tt * Math.PI) * 0.5;

      const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius * 4);
      gradient.addColorStop(0, `rgba(190, 255, 255, ${alpha})`);
      gradient.addColorStop(0.3, `rgba(120, 254, 255, ${alpha * 0.75})`);
      gradient.addColorStop(1, "rgba(0, 0, 0, 0)");

      ctx.fillStyle = gradient;
      ctx.beginPath();
      ctx.arc(x, y, radius * 4, 0, Math.PI * 2);
      ctx.fill();

      ctx.fillStyle = `rgba(255, 255, 255, ${0.8 * alpha})`;
      ctx.beginPath();
      ctx.arc(x, y, radius, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.restore();
  }

  function drawNodes(neighbor) {
    ctx.save();
    for (let i = 0; i < nodes.length; i++) {
      const n = nodes[i];
      const isHighlighted = neighbor.get(i);
      const twinkle = 0.25 + 0.75 * (0.5 + 0.5 * Math.sin(n.pulse + (i * 0.61)));
      const baseRadius = n.size * (0.85 + 0.3 * twinkle);
      const glowRadius = baseRadius * 3.5;

      const alphaGlow = isHighlighted ? 0.9 : 0.4;
      const alphaCore = isHighlighted ? 1.0 : 0.8;

      const grad = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, glowRadius);
      grad.addColorStop(0, `rgba(160, 255, 255, ${alphaGlow})`);
      grad.addColorStop(0.25, `rgba(90, 240, 255, ${alphaGlow * 0.8})`);
      grad.addColorStop(0.6, `rgba(0, 180, 255, ${alphaGlow * 0.3})`);
      grad.addColorStop(1, "rgba(0, 0, 0, 0)");

      ctx.fillStyle = grad;
      ctx.beginPath();
      ctx.arc(n.x, n.y, glowRadius, 0, Math.PI * 2);
      ctx.fill();

      ctx.fillStyle = isHighlighted ? "#f8ffff" : "#e1fbff";
      ctx.beginPath();
      ctx.arc(n.x, n.y, baseRadius, 0, Math.PI * 2);
      ctx.fill();

      const innerRadius = baseRadius * (0.35 + 0.25 * twinkle);
      ctx.fillStyle = isHighlighted
        ? "rgba(0, 0, 0, 0.35)"
        : "rgba(0, 0, 0, 0.3)";
      ctx.beginPath();
      ctx.arc(n.x, n.y, innerRadius, 0, Math.PI * 2);
      ctx.fill();

      if (isHighlighted) {
        const ringRadius = baseRadius + 2.2;
        ctx.strokeStyle = "rgba(160, 255, 255, 0.85)";
        ctx.lineWidth = 0.85;
        ctx.beginPath();
        ctx.arc(n.x, n.y, ringRadius, 0, Math.PI * 2);
        ctx.stroke();
      }
    }
    ctx.restore();
  }

  let lastTime = performance.now();
  function frame(time) {
    const dt = Math.min(32, time - lastTime);
    lastTime = time;

    applyForces(dt);
    updateMessages(dt);

    if (time - lastMessageTime > MESSAGE_INTERVAL) {
      lastMessageTime = time;
      spawnMessage();
      if (Math.random() < 0.35) spawnMessage();
    }

    const neighbor = isNeighborMap();

    drawBackground();
    drawLinks(neighbor);
    drawMessages();
    drawNodes(neighbor);

    requestAnimationFrame(frame);
  }

  init();
  requestAnimationFrame(frame);
})();
</script>
</body>
</html>