← 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>