← back to Model Arena
auto-save: 2026-07-24T23:30:18 (21 files) — data/challenges.json data/artifacts/dfaee602df0d/gemma3-12b.html data/artifacts/dfaee602df0d/gemma3-12b.png data/artifacts/dfaee602df0d/hermes3-8b.html data/artifacts/dfaee602df0d/hermes3-8b.png
3d2d7af417981460e9a45942e0c98f03b0ac0a56 · 2026-07-24 23:31:43 -0700 · Steve Abrams
Files touched
A data/artifacts/dfaee602df0d/gemma3-12b.htmlA data/artifacts/dfaee602df0d/gemma3-12b.pngA data/artifacts/dfaee602df0d/hermes3-8b.htmlA data/artifacts/dfaee602df0d/hermes3-8b.pngA data/artifacts/dfaee602df0d/hf-qwen-coder-32b.htmlA data/artifacts/dfaee602df0d/hf-qwen-coder-32b.pngA data/artifacts/dfaee602df0d/qwen3-14b.htmlA data/artifacts/dfaee602df0d/qwen3-14b.pngA data/artifacts/ea271db82cff/gemma3-12b.htmlA data/artifacts/ea271db82cff/gemma3-12b.pngA data/artifacts/ea271db82cff/hermes3-8b.htmlA data/artifacts/ea271db82cff/hermes3-8b.pngA data/artifacts/ea271db82cff/hf-qwen-coder-32b.htmlA data/artifacts/ea271db82cff/hf-qwen-coder-32b.pngM data/challenges.jsonA data/landings/48026228790d-gpt.htmlA data/landings/48026228790d-grok.htmlA data/landings/48026228790d-kimi.htmlA data/landings/74df3b61e7ca-hermes3-8b.htmlA data/landings/7993245ce013-claude-code.htmlA data/landings/c76287fb731d-qwen3-14b.html
Diff
commit 3d2d7af417981460e9a45942e0c98f03b0ac0a56
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jul 24 23:31:43 2026 -0700
auto-save: 2026-07-24T23:30:18 (21 files) — data/challenges.json data/artifacts/dfaee602df0d/gemma3-12b.html data/artifacts/dfaee602df0d/gemma3-12b.png data/artifacts/dfaee602df0d/hermes3-8b.html data/artifacts/dfaee602df0d/hermes3-8b.png
---
data/artifacts/dfaee602df0d/gemma3-12b.html | 179 +++++++
data/artifacts/dfaee602df0d/gemma3-12b.png | Bin 0 -> 9983 bytes
data/artifacts/dfaee602df0d/hermes3-8b.html | 180 +++++++
data/artifacts/dfaee602df0d/hermes3-8b.png | Bin 0 -> 2871 bytes
data/artifacts/dfaee602df0d/hf-qwen-coder-32b.html | 105 ++++
data/artifacts/dfaee602df0d/hf-qwen-coder-32b.png | Bin 0 -> 5019 bytes
data/artifacts/dfaee602df0d/qwen3-14b.html | 195 +++++++
data/artifacts/dfaee602df0d/qwen3-14b.png | Bin 0 -> 14879 bytes
data/artifacts/ea271db82cff/gemma3-12b.html | 370 ++++++++++++++
data/artifacts/ea271db82cff/gemma3-12b.png | Bin 0 -> 13928 bytes
data/artifacts/ea271db82cff/hermes3-8b.html | 381 ++++++++++++++
data/artifacts/ea271db82cff/hermes3-8b.png | Bin 0 -> 276573 bytes
data/artifacts/ea271db82cff/hf-qwen-coder-32b.html | 381 ++++++++++++++
data/artifacts/ea271db82cff/hf-qwen-coder-32b.png | Bin 0 -> 238579 bytes
data/challenges.json | 243 +++++++--
data/landings/48026228790d-gpt.html | 323 ++++++++++++
data/landings/48026228790d-grok.html | 563 +++++++++++++++++++++
data/landings/48026228790d-kimi.html | 322 ++++++++++++
data/landings/74df3b61e7ca-hermes3-8b.html | 113 +++++
data/landings/7993245ce013-claude-code.html | 259 ++++++++++
data/landings/c76287fb731d-qwen3-14b.html | 195 +++++++
21 files changed, 3754 insertions(+), 55 deletions(-)
diff --git a/data/artifacts/dfaee602df0d/gemma3-12b.html b/data/artifacts/dfaee602df0d/gemma3-12b.html
new file mode 100644
index 0000000..76a05bd
--- /dev/null
+++ b/data/artifacts/dfaee602df0d/gemma3-12b.html
@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>AI Swarm Snake</title>
+ <style>
+ body {
+ background-color: #222;
+ overflow: hidden;
+ margin: 0;
+ font-family: sans-serif;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ }
+
+ canvas {
+ background-color: #333;
+ border: 1px solid #555;
+ }
+
+ #score {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ font-size: 20px;
+ color: #0f0;
+ }
+ </style>
+</head>
+<body>
+ <div id="score">Score: 0</div>
+ <canvas id="gameCanvas" width="400" height="400"></canvas>
+
+ <script>
+ const canvas = document.getElementById('gameCanvas');
+ const ctx = canvas.getContext('2d');
+ let scoreDisplay = document.getElementById('score');
+
+ const gridSize = 20;
+ const scale = 15; // Adjust for smoother appearance. Larger value gives softer look but reduces detail.
+ let snake = [];
+ let task = {x: 0, y: 0};
+ let dx = 1;
+ let dy = 0;
+ let score = 0;
+ let gameInterval;
+
+ function init() {
+ snake = [{x: gridSize, y: gridSize}]; // Start position
+ task.x = Math.floor(Math.random() * (canvas.width / gridSize));
+ task.y = Math.floor(Math.random() * (canvas.height / gridSize));
+
+ gameInterval = setInterval(update, 1000 / 60); // Approximately 60fps
+ }
+
+
+ function update() {
+ // Move snake:
+ const headX = snake[0].x + dx;
+ const headY = snake[0].y + dy;
+
+ snake.unshift({x: headX, y: headY}); // Add new head
+
+ // Check for self-collision
+ if (checkCollision()) {
+ gameOver();
+ return;
+ }
+
+ //Check if task eaten. If so create a new one
+ if (snake[0].x === task.x && snake[0].y === task.y) {
+ score++;
+ scoreDisplay.textContent = `Score: ${score}`;
+ snake.push({ x: canvas.width, y: canvas.height }); // grow the swarm
+ task.x = Math.floor(Math.random() * (canvas.width / gridSize));
+ task.y = Math.floor(Math.random() * (canvas.height / gridSize));
+ } else {
+ snake.pop(); // Remove tail if no food eaten
+ }
+
+
+ draw();
+ }
+
+ function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ // Draw task:
+ ctx.fillStyle = '#ff0';
+ ctx.beginPath();
+ ctx.arc(task.x * gridSize, task.y * gridSize, gridSize/2, 0, Math.PI * 2);
+ ctx.fill();
+
+
+
+ // Draw snake (swarm):
+ for (let i = 0; i < snake.length; i++) {
+ const nodeX = snake[i].x * gridSize;
+ const nodeY = snake[i].y * gridSize;
+
+ ctx.fillStyle = `rgba(0, 255, 0, ${1 - (i/snake.length)})`; // Glowing effect
+ ctx.beginPath();
+ ctx.arc(nodeX + scale/2 , nodeY+scale/2, scale, 0, Math.PI * 2); // Node radius
+ ctx.fill();
+
+ }
+
+
+ }
+
+ function checkCollision() {
+ // Check for self-collision:
+ for (let i = 1; i < snake.length; i++) {
+ if (snake[0].x === snake[i].x && snake[0].y === snake[i].y) {
+ return true;
+ }
+ }
+
+ // Check for wall collision:
+ if (snake[0].x < 0 || snake[0].x >= canvas.width / gridSize ||
+ snake[0].y < 0 || snake[0].y >= canvas.height / gridSize) {
+ return true;
+ }
+
+ return false;
+ }
+
+
+ function gameOver() {
+ clearInterval(gameInterval);
+ ctx.fillStyle = 'red';
+ ctx.font = '30px sans-serif';
+ ctx.fillText('Game Over!', canvas.width / 2 - 150, canvas.height / 2);
+
+ const restartButton = document.createElement("button");
+ restartButton.textContent = "Restart";
+ restartButton.addEventListener("click", init); // Restart the game
+ document.body.appendChild(restartButton);
+
+ }
+
+
+ // Input handling:
+ document.addEventListener('keydown', function(event) {
+ if (event.key === 'ArrowUp' && dy !== 1) {
+ dx = 0;
+ dy = -1;
+ } else if (event.key === 'ArrowDown' && dy !== -1) {
+ dx = 0;
+ dy = 1;
+ } else if (event.key === 'ArrowLeft' && dx !== 1) {
+ dx = -1;
+ dy = 0;
+ } else if (event.key === 'ArrowRight' && dx !== -1) {
+ dx = 1;
+ dy = 0;
+ } else if (event.key === "w" && dy !== 1){
+ dx = 0;
+ dy = -1;
+ }else if(event.key ==="s"&& dy != 1) {
+ dx=0;
+ dy = 1;
+
+ } else if(event.key=="a" && dx !== 1){
+ dx=-1;
+ dy=0;
+ } else if (event.key == "d" && dx!=1){
+ dx=1;
+ dy=0;
+ }
+ });
+
+ init(); // Start the game
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/dfaee602df0d/gemma3-12b.png b/data/artifacts/dfaee602df0d/gemma3-12b.png
new file mode 100644
index 0000000..508860d
Binary files /dev/null and b/data/artifacts/dfaee602df0d/gemma3-12b.png differ
diff --git a/data/artifacts/dfaee602df0d/hermes3-8b.html b/data/artifacts/dfaee602df0d/hermes3-8b.html
new file mode 100644
index 0000000..f4b29e8
--- /dev/null
+++ b/data/artifacts/dfaee602df0d/hermes3-8b.html
@@ -0,0 +1,180 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>AI Agent Swarm Snake Game</title>
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ background-color: #000;
+ overflow: hidden;
+ font-family: 'Arial', sans-serif;
+ }
+
+ canvas {
+ display: block;
+ width: 800px;
+ height: 600px;
+ background-color: #111;
+ }
+
+ .score {
+ color: #0f0;
+ font-size: 24px;
+ margin-top: 10px;
+ }
+ </style>
+</head>
+<body>
+ <canvas></canvas>
+ <div class="score">Score: 0</div>
+
+ <script>
+ const canvas = document.querySelector('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Game settings
+ const gridSize = {x: 800 / 10, y: 600 / 10};
+ const snakeSize = 5;
+ const taskSize = 20;
+
+ let score = 0;
+
+ class AIAgent {
+ constructor(x, y) {
+ this.x = x;
+ this.y = y;
+ this.color = '#fff';
+ this.taskCount = 0;
+ }
+
+ draw() {
+ ctx.fillStyle = this.color;
+ ctx.fillRect(this.x * gridSize.x, this.y * gridSize.y, gridSize.x, gridSize.y);
+ }
+ }
+
+ class SnakeAgent extends AIAgent {
+ constructor(x, y) {
+ super(x, y);
+ this.taskCount = 0;
+ this.color = '#5f0f0f';
+ }
+
+ grow() {
+ if (this.taskCount < snakeSize - 1) {
+ this.taskCount++;
+ } else {
+ // Remove tail
+ this.x -= gridSize.x;
+ this.y -= gridSize.y;
+ this.color = '#555';
+ }
+ }
+
+ draw() {
+ super.draw();
+
+ for (let i = 0; i < this.taskCount; i++) {
+ const offsetX = Math.floor(this.x / gridSize.x) - i * gridSize.x / gridSize.x;
+ const offsetY = Math.floor(this.y / gridSize.y);
+
+ ctx.fillStyle = '#555';
+ ctx.fillRect(offsetX, offsetY, gridSize.x, gridSize.y);
+ }
+ }
+ }
+
+ class TaskAgent extends AIAgent {
+ constructor(x, y) {
+ super(x, y);
+ this.color = '#0ff';
+ }
+ }
+
+ // Game loop
+ function gameLoop() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ // Draw snake
+ for (let i = 0; i < agents.length; i++) {
+ const agent = agents[i];
+
+ if (agent instanceof SnakeAgent) {
+ agent.draw();
+ agent.grow();
+ } else {
+ agent.draw();
+ }
+ }
+
+ // Draw tasks
+ for (let i = 0; i < tasks.length; i++) {
+ tasks[i].draw();
+ }
+
+ // Check collisions
+ let collided = false;
+
+ for (let i = 0; i < agents.length; i++) {
+ const agent = agents[i];
+
+ if (agent instanceof SnakeAgent) {
+ for (let j = i + 1; j < agents.length; j++) {
+ const other = agents[j];
+
+ if (
+ other instanceof SnakeAgent &&
+ agent.x === other.x && agent.y === other.y
+ ) {
+ collided = true;
+ break;
+ }
+ }
+
+ if (collided) break;
+
+ for (let j = 0; j < tasks.length; j++) {
+ const task = tasks[j];
+
+ if (
+ task.x >= agent.x - gridSize.x / 2 &&
+ task.x <= agent.x + gridSize.x / 2 &&
+ task.y >= agent.y - gridSize.y / 2 &&
+ task.y <= agent.y + gridSize.y / 2
+ ) {
+ score++;
+ tasks.splice(j, 1);
+ break;
+ }
+ }
+ }
+ }
+
+ // Update UI
+ document.querySelector('.score').textContent = `Score: ${score}`;
+
+ // Request next animation frame
+ requestAnimationFrame(gameLoop);
+ }
+
+ // Initialize game state
+ const gridSizePx = {x: canvas.width / gridSize.x, y: canvas.height / gridSize.y};
+
+ let agents = [];
+ for (let i = 0; i < 5; i++) {
+ agents.push(new SnakeAgent(Math.random() * gridSize.x, Math.random() * gridSize.y));
+ }
+
+ let tasks = [];
+ for (let i = 0; i < 10; i++) {
+ tasks.push(new TaskAgent(Math.random() * gridSize.x, Math.random() * gridSize.y));
+ }
+
+ // Start game loop
+ requestAnimationFrame(gameLoop);
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/dfaee602df0d/hermes3-8b.png b/data/artifacts/dfaee602df0d/hermes3-8b.png
new file mode 100644
index 0000000..7cd5eb5
Binary files /dev/null and b/data/artifacts/dfaee602df0d/hermes3-8b.png differ
diff --git a/data/artifacts/dfaee602df0d/hf-qwen-coder-32b.html b/data/artifacts/dfaee602df0d/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..e591460
--- /dev/null
+++ b/data/artifacts/dfaee602df0d/hf-qwen-coder-32b.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>AI Swarm Snake</title>
+<style>
+body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #000; }
+canvas { border: 2px solid #fff; }
+#score { position: absolute; top: 10px; left: 10px; color: #fff; font-size: 1.5em; }
+</style>
+</head>
+<body>
+<div id="score">Score: 0</div>
+<canvas id="gameCanvas" width="400" height="400"></canvas>
+<script>
+const canvas = document.getElementById('gameCanvas');
+const ctx = canvas.getContext('2d');
+const gridSize = 20;
+let snake = [{ x: 9, y: 10 }, { x: 8, y: 10 }];
+let direction = { x: 1, y: 0 };
+let task = { x: Math.floor(Math.random() * 20), y: Math.floor(Math.random() * 20) };
+let score = 0;
+
+function drawRect(x, y, color) {
+ ctx.fillStyle = color;
+ ctx.fillRect(x * gridSize, y * gridSize, gridSize, gridSize);
+}
+
+function drawSnake() {
+ snake.forEach(segment => drawRect(segment.x, segment.y, '#0ff'));
+}
+
+function drawTask() {
+ drawRect(task.x, task.y, '#f0f');
+}
+
+function moveSnake() {
+ const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
+ snake.unshift(head);
+
+ if (head.x === task.x && head.y === task.y) {
+ score++;
+ document.getElementById('score').innerText = 'Score: ' + score;
+ task = { x: Math.floor(Math.random() * 20), y: Math.floor(Math.random() * 20) };
+ } else {
+ snake.pop();
+ }
+
+ if (head.x < 0 || head.y < 0 || head.x >= 20 || head.y >= 20 || checkCollision(head)) {
+ resetGame();
+ }
+}
+
+function checkCollision(head) {
+ return snake.some((segment, index) => index !== 0 && segment.x === head.x && segment.y === head.y);
+}
+
+function resetGame() {
+ snake = [{ x: 9, y: 10 }, { x: 8, y: 10 }];
+ direction = { x: 1, y: 0 };
+ score = 0;
+ document.getElementById('score').innerText = 'Score: 0';
+}
+
+function gameLoop() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawSnake();
+ drawTask();
+ moveSnake();
+}
+
+document.addEventListener('keydown', e => {
+ switch (e.key) {
+ case 'ArrowUp':
+ if (direction.y === 0) direction = { x: 0, y: -1 };
+ break;
+ case 'ArrowDown':
+ if (direction.y === 0) direction = { x: 0, y: 1 };
+ break;
+ case 'ArrowLeft':
+ if (direction.x === 0) direction = { x: -1, y: 0 };
+ break;
+ case 'ArrowRight':
+ if (direction.x === 0) direction = { x: 1, y: 0 };
+ break;
+ case 'w':
+ if (direction.y === 0) direction = { x: 0, y: -1 };
+ break;
+ case 's':
+ if (direction.y === 0) direction = { x: 0, y: 1 };
+ break;
+ case 'a':
+ if (direction.x === 0) direction = { x: -1, y: 0 };
+ break;
+ case 'd':
+ if (direction.x === 0) direction = { x: 1, y: 0 };
+ break;
+ }
+});
+
+setInterval(gameLoop, 16);
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/dfaee602df0d/hf-qwen-coder-32b.png b/data/artifacts/dfaee602df0d/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..616bd2d
Binary files /dev/null and b/data/artifacts/dfaee602df0d/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/dfaee602df0d/qwen3-14b.html b/data/artifacts/dfaee602df0d/qwen3-14b.html
new file mode 100644
index 0000000..3201436
--- /dev/null
+++ b/data/artifacts/dfaee602df0d/qwen3-14b.html
@@ -0,0 +1,195 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>AI Swarm Snake</title>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ background: #0f0f1a;
+ color: #00ffcc;
+ font-family: 'Courier New', monospace;
+ user-select: none;
+ }
+ canvas {
+ display: block;
+ margin: 0 auto;
+ background: #0f0f1a;
+ image-rendering: pixelated;
+ }
+ #ui {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ font-size: 16px;
+ color: #00ffcc;
+ text-shadow: 0 0 5px #00ffcc;
+ }
+ #restart {
+ position: absolute;
+ bottom: 10px;
+ left: 50%;
+ transform: translateX(-50%);
+ padding: 10px 20px;
+ background: #1a1a2e;
+ border: 2px solid #00ffcc;
+ color: #00ffcc;
+ font-weight: bold;
+ cursor: pointer;
+ border-radius: 5px;
+ text-shadow: 0 0 5px #00ffcc;
+ }
+ #restart:hover {
+ background: #00ffcc;
+ color: #0f0f1a;
+ }
+ </style>
+</head>
+<body>
+ <div id="ui">Score: <span id="score">0</span> | Tokens: <span id="tokens">0</span></div>
+ <div id="restart">Restart</div>
+ <canvas id="game"></canvas>
+ <script>
+ const canvas = document.getElementById('game');
+ const ctx = canvas.getContext('2d');
+ const restartBtn = document.getElementById('restart');
+ const scoreEl = document.getElementById('score');
+ const tokensEl = document.getElementById('tokens');
+
+ let gridSize = 20;
+ let cellSize = 20;
+ canvas.width = gridSize * cellSize;
+ canvas.height = gridSize * cellSize;
+
+ let snake = [{ x: 10, y: 10 }];
+ let direction = { x: 0, y: 0 };
+ let tasks = [];
+ let score = 0;
+ let tokens = 0;
+ let gameLoop;
+ let gameRunning = false;
+
+ function spawnTask() {
+ let newTask;
+ do {
+ newTask = {
+ x: Math.floor(Math.random() * gridSize),
+ y: Math.floor(Math.random() * gridSize),
+ };
+ } while (snake.some(s => s.x === newTask.x && s.y === newTask.y) || tasks.some(t => t.x === newTask.x && t.y === newTask.y));
+ tasks.push(newTask);
+ }
+
+ function drawTask(task) {
+ const r = Math.sin(Date.now() * 0.01 + task.x * 10 + task.y * 10) * 25 + 100;
+ ctx.beginPath();
+ ctx.arc(task.x * cellSize + cellSize / 2, task.y * cellSize + cellSize / 2, r, 0, Math.PI * 2);
+ ctx.fillStyle = `rgba(0, 255, 255, ${0.5 + Math.sin(Date.now() * 0.01) * 0.5})`;
+ ctx.fill();
+ }
+
+ function drawSnake() {
+ for (let i = 0; i < snake.length; i++) {
+ const segment = snake[i];
+ const alpha = 0.3 + (snake.length - i) * 0.05;
+ ctx.fillStyle = `rgba(0, 255, 255, ${alpha})`;
+ ctx.fillRect(segment.x * cellSize, segment.y * cellSize, cellSize, cellSize);
+ }
+ }
+
+ function update() {
+ const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
+
+ if (head.x < 0 || head.x >= gridSize || head.y < 0 || head.y >= gridSize || snake.some(s => s.x === head.x && s.y === head.y)) {
+ endGame();
+ return;
+ }
+
+ snake.unshift(head);
+
+ let taskEaten = false;
+ for (let i = 0; i < tasks.length; i++) {
+ if (tasks[i].x === head.x && tasks[i].y === head.y) {
+ tasks.splice(i, 1);
+ taskEaten = true;
+ tokens++;
+ tokensEl.textContent = tokens;
+ break;
+ }
+ }
+
+ if (!taskEaten && tasks.length < 5) {
+ spawnTask();
+ }
+
+ if (!taskEaten) {
+ snake.pop();
+ } else {
+ score++;
+ scoreEl.textContent = score;
+ }
+ }
+
+ function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawSnake();
+ tasks.forEach(drawTask);
+ }
+
+ function gameLoopFunc() {
+ if (!gameRunning) return;
+ update();
+ draw();
+ }
+
+ function startGame() {
+ snake = [{ x: 10, y: 10 }];
+ direction = { x: 0, y: 0 };
+ score = 0;
+ tokens = 0;
+ scoreEl.textContent = score;
+ tokensEl.textContent = tokens;
+ tasks = [];
+ spawnTask();
+ gameRunning = true;
+ gameLoop = setInterval(gameLoopFunc, 1000 / 60);
+ }
+
+ function endGame() {
+ clearInterval(gameLoop);
+ gameRunning = false;
+ }
+
+ function handleInput(e) {
+ switch (e.key) {
+ case 'ArrowUp':
+ case 'w':
+ if (direction.y === 0) direction = { x: 0, y: -1 };
+ break;
+ case 'ArrowDown':
+ case 's':
+ if (direction.y === 0) direction = { x: 0, y: 1 };
+ break;
+ case 'ArrowLeft':
+ case 'a':
+ if (direction.x === 0) direction = { x: -1, y: 0 };
+ break;
+ case 'ArrowRight':
+ case 'd':
+ if (direction.x === 0) direction = { x: 1, y: 0 };
+ break;
+ }
+ }
+
+ restartBtn.addEventListener('click', () => {
+ startGame();
+ });
+
+ document.addEventListener('keydown', handleInput);
+
+ startGame();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/dfaee602df0d/qwen3-14b.png b/data/artifacts/dfaee602df0d/qwen3-14b.png
new file mode 100644
index 0000000..b6084d4
Binary files /dev/null and b/data/artifacts/dfaee602df0d/qwen3-14b.png differ
diff --git a/data/artifacts/ea271db82cff/gemma3-12b.html b/data/artifacts/ea271db82cff/gemma3-12b.html
new file mode 100644
index 0000000..1866afa
--- /dev/null
+++ b/data/artifacts/ea271db82cff/gemma3-12b.html
@@ -0,0 +1,370 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Metaball Lava Lamp</title>
+<style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
+ #lamp { display: block; width: 100vw; height: 100vh; }
+
+ #panel {
+ position: fixed;
+ top: 18px;
+ left: 18px;
+ padding: 16px 18px 18px;
+ border-radius: 16px;
+ background: rgba(20, 12, 30, 0.42);
+ backdrop-filter: blur(14px) saturate(140%);
+ -webkit-backdrop-filter: blur(14px) saturate(140%);
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.45);
+ color: #f4eef8;
+ width: 224px;
+ user-select: none;
+ z-index: 10;
+ transition: opacity .3s;
+ }
+ #panel h1 {
+ font-size: 13px;
+ font-weight: 600;
+ letter-spacing: 1.6px;
+ text-transform: uppercase;
+ opacity: 0.85;
+ margin-bottom: 14px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ #panel h1 .dot {
+ width: 9px; height: 9px; border-radius: 50%;
+ background: radial-gradient(circle at 30% 30%, #ffe066, #ff2d55);
+ box-shadow: 0 0 10px #ff5e3a;
+ }
+ .row { margin-bottom: 14px; }
+ .row:last-child { margin-bottom: 0; }
+ label {
+ display: block;
+ font-size: 11px;
+ letter-spacing: 0.5px;
+ opacity: 0.7;
+ margin-bottom: 7px;
+ display: flex;
+ justify-content: space-between;
+ }
+ label b { opacity: 0.95; font-weight: 600; }
+ select {
+ width: 100%;
+ padding: 8px 10px;
+ border-radius: 9px;
+ background: rgba(255,255,255,0.07);
+ color: #f4eef8;
+ border: 1px solid rgba(255,255,255,0.14);
+ font-size: 13px;
+ outline: none;
+ cursor: pointer;
+ appearance: none;
+ -webkit-appearance: none;
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M2 4l4 4 4-4' stroke='%23f4eef8' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
+ background-repeat: no-repeat;
+ background-position: right 10px center;
+ }
+ select option { background: #1a1024; color: #f4eef8; }
+
+ input[type=range] {
+ width: 100%;
+ -webkit-appearance: none;
+ appearance: none;
+ height: 5px;
+ border-radius: 5px;
+ background: rgba(255,255,255,0.18);
+ outline: none;
+ cursor: pointer;
+ }
+ input[type=range]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 17px; height: 17px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 32% 30%, #ffe9a8, #ff7a00);
+ border: 2px solid rgba(255,255,255,0.5);
+ box-shadow: 0 0 8px rgba(255,120,40,0.7);
+ }
+ input[type=range]::-moz-range-thumb {
+ width: 15px; height: 15px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 32% 30%, #ffe9a8, #ff7a00);
+ border: 2px solid rgba(255,255,255,0.5);
+ box-shadow: 0 0 8px rgba(255,120,40,0.7);
+ }
+ #hint {
+ position: fixed;
+ bottom: 14px;
+ left: 0; right: 0;
+ text-align: center;
+ color: rgba(255,255,255,0.28);
+ font-size: 11px;
+ letter-spacing: 1px;
+ z-index: 5;
+ pointer-events: none;
+ }
+</style>
+</head>
+<body onload="init()">
+<canvas id="lamp"></canvas>
+
+<div id="panel">
+ <h1><span class="dot"></span>Lava Lamp</h1>
+ <div class="row">
+ <label>Color Theme</label>
+ <select id="theme">
+ <option value="classic">Classic Lava</option>
+ <option value="ember">Ember Glow</option>
+ <option value="sunset">Sunset</option>
+ <option value="amethyst">Amethyst</option>
+ <option value="ocean">Deep Ocean</option>
+ <option value="toxic">Toxic</option>
+ </select>
+ </div>
+ <div class="row">
+ <label>Blobs <b id="countVal">12</b></label>
+ <input type="range" id="count" min="3" max="26" value="12">
+ </div>
+</div>
+
+<div id="hint">metaballs rise · fall · merge</div>
+
+<script>
+let canvas, ctx, off, offCtx;
+let W, H, lowW, lowH, scale;
+let blobs = [];
+let theme;
+let ramp = new Uint8Array(256 * 3);
+const THEMES = {
+ classic: {
+ bg: ["#2a0a3a", "#160326"],
+ stops: [[0,"#7a0f3a"],[0.35,"#ff2d55"],[0.7,"#ff8a00"],[1,"#ffe066"]]
+ },
+ ember: {
+ bg: ["#220a05", "#120302"],
+ stops: [[0,"#7a1500"],[0.4,"#ff3d00"],[0.72,"#ff7a00"],[1,"#ffc46b"]]
+ },
+ sunset: {
+ bg: ["#1b1035", "#360d38"],
+ stops: [[0,"#5b1250"],[0.35,"#ff4d6d"],[0.7,"#ff9e5e"],[1,"#ffd76e"]]
+ },
+ amethyst: {
+ bg: ["#14052a", "#26063f"],
+ stops: [[0,"#5a1a8a"],[0.4,"#c04dff"],[0.72,"#ff5edb"],[1,"#ffd1f5"]]
+ },
+ ocean: {
+ bg: ["#041b2d", "#06263a"],
+ stops: [[0,"#053a5c"],[0.38,"#00c8ff"],[0.72,"#2b8cff"],[1,"#bfeaff"]]
+ },
+ toxic: {
+ bg: ["#071a07", "#0a250a"],
+ stops: [[0,"#2c5c00"],[0.4,"#7bd400"],[0.72,"#39ff14"],[1,"#eaffb0"]]
+ }
+ };
+
+function hexToRgb(h) {
+ h = h.replace("#", "");
+ return [parseInt(h.slice(0,2),16), parseInt(h.slice(2,4),16), parseInt(h.slice(4,6),16)];
+}
+
+function buildRamp(t) {
+ const stops = t.stops;
+ for (let i = 0; i < 256; i++) {
+ const p = i / 255;
+ let a = stops[0], b = stops[stops.length - 1];
+ for (let s = 0; s < stops.length - 1; s++) {
+ if (p >= stops[s][0] && p <= stops[s + 1][0]) { a = stops[s]; b = stops[s + 1]; break; }
+ }
+ const span = (b[0] - a[0]) || 1;
+ const f = (p - a[0]) / span;
+ const ca = hexToRgb(a[1]), cb = hexToRgb(b[1]);
+ ramp[i*3] = ca[0] + (cb[0] - ca[0]) * f;
+ ramp[i*3+1] = ca[1] + (cb[1] - ca[1]) * f;
+ ramp[i*3+2] = ca[2] + (cb[2] - ca[2]) * f;
+ }
+}
+
+function init() {
+ canvas = document.getElementById("lamp");
+ ctx = canvas.getContext("2d");
+ off = document.createElement("canvas");
+ offCtx = off.getContext("2d");
+
+ theme = THEMES.classic;
+ buildRamp(theme);
+ resize();
+}
+
+
+function resize() {
+ W = canvas.width = Math.max(1, window.innerWidth);
+ H = canvas.height = Math.max(1, window.innerHeight);
+ minDim = Math.min(W, H);
+
+ scale = Math.min(0.26, 200 / Math.max(W, H));
+ lowW = Math.max(2, Math.round(W * scale));
+ lowH = Math.max(2, Math.round(H * scale));
+ off.width = lowW;
+ off.height = lowH;
+
+ let bgGrad = ctx.createLinearGradient(0, 0, 0, H);
+ bgGrad.addColorStop(0, theme.bg[0]);
+ bgGrad.addColorStop(1, theme.bg[1]);
+
+
+ for (const b of blobs) recomputeRadius(b);
+}
+
+function makeBlob() {
+ const b = {
+ x: Math.random() * (W || 800),
+ y: Math.random() * (H || 600),
+ rFrac: 0.05 + Math.random() * 0.055,
+ vs: 0.25 + Math.random() * 0.5,
+ vp: Math.random() * Math.PI * 2,
+ vamp: 45 + Math.random() * 70,
+ hs: 0.15 + Math.random() * 0.35,
+ hp: Math.random() * Math.PI * 2,
+ hamp: 18 + Math.random() * 34,
+ ps: 0.4 + Math.random() * 0.8,
+ pp: Math.random() * Math.PI * 2,
+ };
+ recomputeRadius(b);
+ return b;
+ }
+
+function setCount(n) {
+ while (blobs.length < n) blobs.push(makeBlob());
+ while (blobs.length > n) blobs.pop();
+}
+
+function recomputeRadius(b) {
+ b.r = b.rFrac * minDim;
+}
+
+
+let last = performance.now();
+function frame(now) {
+ let dt = (now - last) / 1000;
+ if (dt > 0.05) dt = 0.05;
+ last = now;
+ const t = now / 1000;
+
+ update(t, dt);
+ renderField(t);
+
+ let bgGrad = ctx.createLinearGradient(0, 0, 0, H);
+ bgGrad.addColorStop(0, theme.bg[0]);
+ bgGrad.addColorStop(1, theme.bg[1]);
+ ctx.globalCompositeOperation = "source-over";
+ ctx.fillStyle = bgGrad;
+ ctx.fillRect(0, 0, W, H);
+
+ ctx.imageSmoothingEnabled = true;
+ ctx.imageSmoothingQuality = "high";
+ ctx.drawImage(off, 0, 0, lowW, lowH, 0, 0, W, H);
+
+ ctx.globalCompositeOperation = "lighter";
+ ctx.globalAlpha = 0.28;
+ ctx.drawImage(off, 0, 0, lowW, lowH, 0, 0, W, H);
+ ctx.globalAlpha = 1;
+ ctx.globalCompositeOperation = "source-over";
+
+ requestAnimationFrame(frame);
+}
+
+
+function update(t, dt) {
+ for (const b of blobs) {
+ const vy = Math.sin(t * b.vs + b.vp) * b.vamp;
+ const vx = Math.sin(t * b.hs + b.hp) * b.hamp;
+ b.x += vx * dt;
+ b.y += vy * dt;
+
+ const pad = b.r * 0.35;
+ if (b.x < pad) { b.x = pad; b.hp += Math.PI; }
+ else if (b.x > W - pad) { b.x = W - pad; b.hp += Math.PI; }
+ if (b.y < pad) { b.y = pad; b.vp += Math.PI; }
+ else if (b.y > H - pad) { b.y = H - pad; b.vp += Math.PI; }
+
+ b.rr = b.r * (1 + Math.sin(t * b.ps + b.pp) * 0.14);
+ }
+ }
+
+
+function renderField(t) {
+ const n = blobs.length;
+ const bx = new Float32Array(n), by = new Float32Array(n), br2 = new Float32Array(n);
+ for (let i = 0; i < n; i++) {
+ const b = blobs[i];
+ bx[i] = b.x * scale;
+ by[i] = b.y * scale;
+ const rl = b.rr * scale;
+ br2[i] = rl * rl;
+ }
+
+ let p = 0;
+ for (let y = 0; y < lowH; y++) {
+ for (let x = 0; x < lowW; x++) {
+ let sum = 0;
+ for (let i = 0; i < n; i++) {
+ const dx = x - bx[i], dy = y - by[i];
+ const d2 = dx * dx + dy * dy + 0.0001;
+ sum += br2[i] / d2;
+ }
+
+ let a;
+ if (sum < 0.65) {
+ a = 0;
+ } else {
+ a = sum < 1.05 ? (sum - 0.65) / 0.4 : 1;
+ let ct = (sum - 0.7) / 2.6;
+ if (ct < 0) ct = 0; else if (ct > 1) ct = 1;
+ const ci = (ct * 255) | 0;
+ const r3 = ci * 3;
+ offCtx.data[p] = ramp[r3];
+ offCtx.data[p + 1] = ramp[r3 + 1];
+ offCtx.data[p + 2] = ramp[r3 + 2];
+ }
+
+ offCtx.data[p + 3] = a * 255;
+ p += 4;
+ }
+ }
+ ctx.putImageData(offCtx.data, 0, 0);
+}
+
+
+const themeSel = document.getElementById("theme");
+const countInp = document.getElementById("count");
+const countVal = document.getElementById("countVal");
+
+themeSel.addEventListener("change", function () {
+ theme = THEMES[this.value] || THEMES.classic;
+ buildRamp(theme);
+ let bgGrad = ctx.createLinearGradient(0, 0, 0, H);
+ bgGrad.addColorStop(0, theme.bg[0]);
+ bgGrad.addColorStop(1, theme.bg[1]);
+
+});
+
+countInp.addEventListener("input", function () {
+ countVal.textContent = this.value;
+ setCount(parseInt(this.value, 10));
+});
+
+
+window.addEventListener("resize", resize);
+
+let count = parseInt(countInp.value, 10) || 12;
+setCount(count)
+init();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ea271db82cff/gemma3-12b.png b/data/artifacts/ea271db82cff/gemma3-12b.png
new file mode 100644
index 0000000..ff1e77e
Binary files /dev/null and b/data/artifacts/ea271db82cff/gemma3-12b.png differ
diff --git a/data/artifacts/ea271db82cff/hermes3-8b.html b/data/artifacts/ea271db82cff/hermes3-8b.html
new file mode 100644
index 0000000..8dead27
--- /dev/null
+++ b/data/artifacts/ea271db82cff/hermes3-8b.html
@@ -0,0 +1,381 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Improved Metaball Lava Lamp</title>
+<style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
+ #lamp { display: block; width: 100vw; height: 100vh; }
+
+ #panel {
+ position: fixed;
+ top: 18px;
+ left: 18px;
+ padding: 16px 18px 18px;
+ border-radius: 16px;
+ background: rgba(20, 12, 30, 0.42);
+ backdrop-filter: blur(14px) saturate(140%);
+ -webkit-backdrop-filter: blur(14px) saturate(140%);
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.45);
+ color: #f4eef8;
+ width: 224px;
+ user-select: none;
+ z-index: 10;
+ transition: opacity .3s;
+ }
+ #panel h1 {
+ font-size: 13px;
+ font-weight: 600;
+ letter-spacing: 1.6px;
+ text-transform: uppercase;
+ opacity: 0.85;
+ margin-bottom: 14px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ #panel h1 .dot {
+ width: 9px; height: 9px; border-radius: 50%;
+ background: radial-gradient(circle at 30% 30%, #ffe066, #ff2d55);
+ box-shadow: 0 0 10px #ff5e3a;
+ }
+ .row { margin-bottom: 14px; }
+ .row:last-child { margin-bottom: 0; }
+ label {
+ display: block;
+ font-size: 11px;
+ letter-spacing: 0.5px;
+ opacity: 0.7;
+ margin-bottom: 7px;
+ display: flex;
+ justify-content: space-between;
+ }
+ label b { opacity: 0.95; font-weight: 600; }
+ select {
+ width: 100%;
+ padding: 8px 10px;
+ border-radius: 9px;
+ background: rgba(255,255,255,0.07);
+ color: #f4eef8;
+ border: 1px solid rgba(255,255,255,0.14);
+ font-size: 13px;
+ outline: none;
+ cursor: pointer;
+ appearance: none;
+ -webkit-appearance: none;
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M2 4l4 4 4-4' stroke='%23f4eef8' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
+ background-repeat: no-repeat;
+ background-position: right 10px center;
+ }
+ select option { background: #1a1024; color: #f4eef8; }
+
+ input[type=range] {
+ width: 100%;
+ -webkit-appearance: none;
+ appearance: none;
+ height: 5px;
+ border-radius: 5px;
+ background: rgba(255,255,255,0.18);
+ outline: none;
+ cursor: pointer;
+ }
+ input[type=range]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 17px; height: 17px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 32% 30%, #ffe9a8, #ff7a00);
+ border: 2px solid rgba(255,255,255,0.5);
+ box-shadow: 0 0 8px rgba(255,120,40,0.7);
+ }
+ input[type=range]::-moz-range-thumb {
+ width: 15px; height: 15px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 32% 30%, #ffe9a8, #ff7a00);
+ border: 2px solid rgba(255,255,255,0.5);
+ box-shadow: 0 0 8px rgba(255,120,40,0.7);
+ }
+ #hint {
+ position: fixed;
+ bottom: 14px;
+ left: 0; right: 0;
+ text-align: center;
+ color: rgba(255,255,255,0.28);
+ font-size: 11px;
+ letter-spacing: 1px;
+ z-index: 5;
+ pointer-events: none;
+ }
+</style>
+</head>
+<body>
+<canvas id="lamp"></canvas>
+
+<div id="panel">
+ <h1><span class="dot"></span>Improved Lava Lamp</h1>
+ <div class="row">
+ <label>Color Theme</label>
+ <select id="theme">
+ <option value="classic">Classic Lava</option>
+ <option value="ember">Ember Glow</option>
+ <option value="sunset">Sunset</option>
+ <option value="amethyst">Amethyst</option>
+ <option value="ocean">Deep Ocean</option>
+ <option value="toxic">Toxic</option>
+ </select>
+ </div>
+ <div class="row">
+ <label>Blobs <b id="countVal">12</b></label>
+ <input type="range" id="count" min="3" max="26" value="12">
+ </div>
+</div>
+
+<div id="hint">metaballs rise · fall · merge</div>
+
+<script>
+(function () {
+ "use strict";
+
+ const canvas = document.getElementById("lamp");
+ const ctx = canvas.getContext("2d");
+
+ // Offscreen low-res buffer for the metaball field
+ const off = document.createElement("canvas");
+ const offCtx = off.getContext("2d");
+
+ // ---- Themes ----------------------------------------------------------
+ // bg: vertical background gradient [top, bottom]
+ // stops: blob color ramp (edge -> core)
+ const THEMES = {
+ classic: {
+ bg: ["#2a0a3a", "#160326"],
+ stops: [[0,"#7a0f3a"],[0.35,"#ff2d55"],[0.7,"#ff8a00"],[1,"#ffe066"]]
+ },
+ ember: {
+ bg: ["#220a05", "#120302"],
+ stops: [[0,"#7a1500"],[0.4,"#ff3d00"],[0.72,"#ff7a00"],[1,"#ffc46b"]]
+ },
+ sunset: {
+ bg: ["#1b1035", "#360d38"],
+ stops: [[0,"#5b1250"],[0.35,"#ff4d6d"],[0.7,"#ff9e5e"],[1,"#ffd76e"]]
+ },
+ amethyst: {
+ bg: ["#14052a", "#26063f"],
+ stops: [[0,"#5a1a8a"],[0.4,"#c04dff"],[0.72,"#ff5edb"],[1,"#ffd1f5"]]
+ },
+ ocean: {
+ bg: ["#041b2d", "#06263a"],
+ stops: [[0,"#053a5c"],[0.38,"#00c8ff"],[0.72,"#2b8cff"],[1,"#bfeaff"]]
+ },
+ toxic: {
+ bg: ["#071a07", "#0a250a"],
+ stops: [[0,"#2c5c00"],[0.4,"#7bd400"],[0.72,"#39ff14"],[1,"#eaffb0"]]
+ }
+ };
+
+ let theme = THEMES.classic;
+ let ramp = new Uint8Array(256 * 3); // color lookup: edge->core
+
+ function hexToRgb(h) {
+ h = h.replace("#", "");
+ return [parseInt(h.slice(0,2),16), parseInt(h.slice(2,4),16), parseInt(h.slice(4,6),16)];
+ }
+
+ function buildRamp(t) {
+ const stops = t.stops;
+ for (let i = 0; i < 256; i++) {
+ const p = i / 255;
+ let a = stops[0], b = stops[stops.length - 1];
+ for (let s = 0; s < stops.length - 1; s++) {
+ if (p >= stops[s][0] && p <= stops[s + 1][0]) { a = stops[s]; b = stops[s + 1]; break; }
+ }
+ const span = (b[0] - a[0]) || 1;
+ const f = (p - a[0]) / span;
+ const ca = hexToRgb(a[1]), cb = hexToRgb(b[1]);
+ ramp[i*3] = ca[0] + (cb[0] - ca[0]) * f;
+ ramp[i*3+1] = ca[1] + (cb[1] - ca[1]) * f;
+ ramp[i*3+2] = ca[2] + (cb[2] - ca[2]) * f;
+ }
+ }
+ buildRamp(theme);
+
+ // ---- Sizing ----------------------------------------------------------
+ let W = 0, H = 0, minDim = 0;
+ let lowW = 0, lowH = 0, scale = 0.22;
+ let imgData = null, buf = null;
+ let bgGrad = null;
+
+ function resize() {
+ W = canvas.width = Math.max(1, window.innerWidth);
+ H = canvas.height = Math.max(1, window.innerHeight);
+ minDim = Math.min(W, H);
+
+ // Keep the low-res field around ~200px on the long edge
+ scale = Math.min(0.26, 200 / Math.max(W, H));
+ lowW = Math.max(2, Math.round(W * scale));
+ lowH = Math.max(2, Math.round(H * scale));
+ off.width = lowW;
+ off.height = lowH;
+ imgData = offCtx.createImageData(lowW, lowH);
+ buf = imgData.data;
+
+ bgGrad = ctx.createLinearGradient(0, 0, 0, H);
+ bgGrad.addColorStop(0, theme.bg[0]);
+ bgGrad.addColorStop(1, theme.bg[1]);
+
+ for (const b of blobs) recomputeRadius(b);
+ }
+
+ // ---- Blobs -----------------------------------------------------------
+ const blobs = [];
+
+ function recomputeRadius(b) {
+ b.r = b.rFrac * minDim;
+ }
+
+ function makeBlob() {
+ const b = {
+ x: Math.random() * (W || 800),
+ y: Math.random() * (H || 600),
+ rFrac: 0.05 + Math.random() * 0.055,
+ vs: 0.25 + Math.random() * 0.5, // vertical osc speed
+ vp: Math.random() * Math.PI * 2, // vertical phase
+ vamp: 45 + Math.random() * 70, // vertical amplitude (px/s)
+ hs: 0.15 + Math.random() * 0.35, // horizontal speed
+ hp: Math.random() * Math.PI * 2,
+ hamp: 18 + Math.random() * 34, // horizontal drift
+ ps: 0.4 + Math.random() * 0.8, // pulse speed
+ pp: Math.random() * Math.PI * 2,
+ r: 40
+ };
+ recomputeRadius(b);
+ return b;
+ }
+
+ function setCount(n) {
+ while (blobs.length < n) blobs.push(makeBlob());
+ while (blobs.length > n) blobs.pop();
+ }
+
+ // ---- Physics ---------------------------------------------------------
+ function update(t, dt) {
+ for (const b of blobs) {
+ const vy = Math.sin(t * b.vs + b.vp) * b.vamp;
+ const vx = Math.sin(t * b.hs + b.hp) * b.hamp;
+ b.x += vx * dt;
+ b.y += vy * dt;
+
+ const pad = b.r * 0.35;
+ if (b.x < pad) { b.x = pad; b.hp += Math.PI; }
+ else if (b.x > W - pad) { b.x = W - pad; b.hp += Math.PI; }
+ if (b.y < pad) { b.y = pad; b.vp += Math.PI; }
+ else if (b.y > H - pad) { b.y = H - pad; b.vp += Math.PI; }
+
+ b.rr = b.r * (1 + Math.sin(t * b.ps + b.pp) * 0.14); // pulsating radius
+ }
+ }
+
+ // ---- Metaball render -------------------------------------------------
+ function renderField(t) {
+ // Precompute low-res blob params
+ const n = blobs.length;
+ const bx = new Float32Array(n), by = new Float32Array(n), br2 = new Float32Array(n);
+ for (let i = 0; i < n; i++) {
+ const b = blobs[i];
+ bx[i] = b.x * scale;
+ by[i] = b.y * scale;
+ const rl = b.rr * scale;
+ br2[i] = rl * rl;
+ }
+
+ let p = 0;
+ for (let y = 0; y < lowH; y++) {
+ for (let x = 0; x < lowW; x++) {
+ let sum = 0;
+ for (let i = 0; i < n; i++) {
+ const dx = x - bx[i], dy = y - by[i];
+ const d2 = dx * dx + dy * dy + 0.0001;
+ sum += br2[i] / d2;
+ }
+ let a;
+ if (sum < 0.65) {
+ a = 0;
+ } else {
+ a = sum < 1.05 ? (sum - 0.65) / 0.4 : 1;
+ let ct = (sum - 0.7) / 2.6; // color param edge->core
+ if (ct < 0) ct = 0; else if (ct > 1) ct = 1;
+ const ci = (ct * 255) | 0;
+ const r3 = ci * 3;
+ buf[p] = ramp[r3];
+ buf[p + 1] = ramp[r3 + 1];
+ buf[p + 2] = ramp[r3 + 2];
+ }
+ buf[p + 3] = a * 255;
+ p += 4;
+ }
+ }
+ offCtx.putImageData(imgData, 0, 0);
+ }
+
+ // ---- Main loop -------------------------------------------------------
+ let last = performance.now();
+ function frame(now) {
+ let dt = (now - last) / 1000;
+ if (dt > 0.05) dt = 0.05; // clamp after tab switch
+ last = now;
+ const t = now / 1000;
+
+ update(t, dt);
+ renderField(t);
+
+ // Background gradient
+ ctx.globalCompositeOperation = "source-over";
+ ctx.fillStyle = bgGrad;
+ ctx.fillRect(0, 0, W, H);
+
+ // Upscale the metaball layer smoothly on top
+ ctx.imageSmoothingEnabled = true;
+ ctx.imageSmoothingQuality = "high";
+ ctx.drawImage(off, 0, 0, lowW, lowH, 0, 0, W, H);
+
+ // Soft glow pass
+ ctx.globalCompositeOperation = "lighter";
+ ctx.globalAlpha = 0.28;
+ ctx.drawImage(off, 0, 0, lowW, lowH, 0, 0, W, H);
+ ctx.globalAlpha = 1;
+ ctx.globalCompositeOperation = "source-over";
+
+ requestAnimationFrame(frame);
+ }
+
+ // ---- Controls --------------------------------------------------------
+ const themeSel = document.getElementById("theme");
+ const countInp = document.getElementById("count");
+ const countVal = document.getElementById("countVal");
+
+ themeSel.addEventListener("change", function () {
+ theme = THEMES[themeSel.value] || THEMES.classic;
+ buildRamp(theme);
+ bgGrad = ctx.createLinearGradient(0, 0, 0, H);
+ bgGrad.addColorStop(0, theme.bg[0]);
+ bgGrad.addColorStop(1, theme.bg[1]);
+ });
+
+ countInp.addEventListener("input", function () {
+ countVal.textContent = countInp.value;
+ setCount(parseInt(countInp.value, 10));
+ });
+
+ window.addEventListener("resize", resize);
+
+ // ---- Boot ------------------------------------------------------------
+ setCount(parseInt(countInp.value, 10));
+ resize();
+ requestAnimationFrame(frame);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ea271db82cff/hermes3-8b.png b/data/artifacts/ea271db82cff/hermes3-8b.png
new file mode 100644
index 0000000..33c90cf
Binary files /dev/null and b/data/artifacts/ea271db82cff/hermes3-8b.png differ
diff --git a/data/artifacts/ea271db82cff/hf-qwen-coder-32b.html b/data/artifacts/ea271db82cff/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..86d48ca
--- /dev/null
+++ b/data/artifacts/ea271db82cff/hf-qwen-coder-32b.html
@@ -0,0 +1,381 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Metaball Lava Lamp</title>
+<style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
+ #lamp { display: block; width: 100vw; height: 100vh; }
+
+ #panel {
+ position: fixed;
+ top: 18px;
+ left: 18px;
+ padding: 16px 18px 18px;
+ border-radius: 16px;
+ background: rgba(20, 12, 30, 0.42);
+ backdrop-filter: blur(14px) saturate(140%);
+ -webkit-backdrop-filter: blur(14px) saturate(140%);
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.45);
+ color: #f4eef8;
+ width: 224px;
+ user-select: none;
+ z-index: 10;
+ transition: opacity .3s;
+ }
+ #panel h1 {
+ font-size: 13px;
+ font-weight: 600;
+ letter-spacing: 1.6px;
+ text-transform: uppercase;
+ opacity: 0.85;
+ margin-bottom: 14px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ #panel h1 .dot {
+ width: 9px; height: 9px; border-radius: 50%;
+ background: radial-gradient(circle at 30% 30%, #ffe066, #ff2d55);
+ box-shadow: 0 0 10px #ff5e3a;
+ }
+ .row { margin-bottom: 14px; }
+ .row:last-child { margin-bottom: 0; }
+ label {
+ display: block;
+ font-size: 11px;
+ letter-spacing: 0.5px;
+ opacity: 0.7;
+ margin-bottom: 7px;
+ display: flex;
+ justify-content: space-between;
+ }
+ label b { opacity: 0.95; font-weight: 600; }
+ select {
+ width: 100%;
+ padding: 8px 10px;
+ border-radius: 9px;
+ background: rgba(255,255,255,0.07);
+ color: #f4eef8;
+ border: 1px solid rgba(255,255,255,0.14);
+ font-size: 13px;
+ outline: none;
+ cursor: pointer;
+ appearance: none;
+ -webkit-appearance: none;
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M2 4l4 4 4-4' stroke='%23f4eef8' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
+ background-repeat: no-repeat;
+ background-position: right 10px center;
+ }
+ select option { background: #1a1024; color: #f4eef8; }
+
+ input[type=range] {
+ width: 100%;
+ -webkit-appearance: none;
+ appearance: none;
+ height: 5px;
+ border-radius: 5px;
+ background: rgba(255,255,255,0.18);
+ outline: none;
+ cursor: pointer;
+ }
+ input[type=range]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 17px; height: 17px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 32% 30%, #ffe9a8, #ff7a00);
+ border: 2px solid rgba(255,255,255,0.5);
+ box-shadow: 0 0 8px rgba(255,120,40,0.7);
+ }
+ input[type=range]::-moz-range-thumb {
+ width: 15px; height: 15px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 32% 30%, #ffe9a8, #ff7a00);
+ border: 2px solid rgba(255,255,255,0.5);
+ box-shadow: 0 0 8px rgba(255,120,40,0.7);
+ }
+ #hint {
+ position: fixed;
+ bottom: 14px;
+ left: 0; right: 0;
+ text-align: center;
+ color: rgba(255,255,255,0.28);
+ font-size: 11px;
+ letter-spacing: 1px;
+ z-index: 5;
+ pointer-events: none;
+ }
+</style>
+</head>
+<body>
+<canvas id="lamp"></canvas>
+
+<div id="panel">
+ <h1><span class="dot"></span>Lava Lamp</h1>
+ <div class="row">
+ <label>Color Theme</label>
+ <select id="theme">
+ <option value="classic">Classic Lava</option>
+ <option value="ember">Ember Glow</option>
+ <option value="sunset">Sunset</option>
+ <option value="amethyst">Amethyst</option>
+ <option value="ocean">Deep Ocean</option>
+ <option value="toxic">Toxic</option>
+ </select>
+ </div>
+ <div class="row">
+ <label>Blobs <b id="countVal">12</b></label>
+ <input type="range" id="count" min="3" max="26" value="12">
+ </div>
+</div>
+
+<div id="hint">metaballs rise · fall · merge</div>
+
+<script>
+(function () {
+ "use strict";
+
+ const canvas = document.getElementById("lamp");
+ const ctx = canvas.getContext("2d");
+
+ // Offscreen low-res buffer for the metaball field
+ const off = document.createElement("canvas");
+ const offCtx = off.getContext("2d");
+
+ // ---- Themes ----------------------------------------------------------
+ // bg: vertical background gradient [top, bottom]
+ // stops: blob color ramp (edge -> core)
+ const THEMES = {
+ classic: {
+ bg: ["#2a0a3a", "#160326"],
+ stops: [[0,"#7a0f3a"],[0.45,"#ff2d55"],[0.8,"#ff8a00"],[1,"#ffe066"]]
+ },
+ ember: {
+ bg: ["#220a05", "#120302"],
+ stops: [[0,"#7a1500"],[0.45,"#ff3d00"],[0.8,"#ff7a00"],[1,"#ffc46b"]]
+ },
+ sunset: {
+ bg: ["#1b1035", "#360d38"],
+ stops: [[0,"#5b1250"],[0.45,"#ff4d6d"],[0.8,"#ff9e5e"],[1,"#ffd76e"]]
+ },
+ amethyst: {
+ bg: ["#14052a", "#26063f"],
+ stops: [[0,"#5a1a8a"],[0.45,"#c04dff"],[0.8,"#ff5edb"],[1,"#ffd1f5"]]
+ },
+ ocean: {
+ bg: ["#041b2d", "#06263a"],
+ stops: [[0,"#053a5c"],[0.45,"#00c8ff"],[0.8,"#2b8cff"],[1,"#bfeaff"]]
+ },
+ toxic: {
+ bg: ["#071a07", "#0a250a"],
+ stops: [[0,"#2c5c00"],[0.45,"#7bd400"],[0.8,"#39ff14"],[1,"#eaffb0"]]
+ }
+ };
+
+ let theme = THEMES.classic;
+ let ramp = new Uint8Array(256 * 3); // color lookup: edge->core
+
+ function hexToRgb(h) {
+ h = h.replace("#", "");
+ return [parseInt(h.slice(0,2),16), parseInt(h.slice(2,4),16), parseInt(h.slice(4,6),16)];
+ }
+
+ function buildRamp(t) {
+ const stops = t.stops;
+ for (let i = 0; i < 256; i++) {
+ const p = i / 255;
+ let a = stops[0], b = stops[stops.length - 1];
+ for (let s = 0; s < stops.length - 1; s++) {
+ if (p >= stops[s][0] && p <= stops[s + 1][0]) { a = stops[s]; b = stops[s + 1]; break; }
+ }
+ const span = (b[0] - a[0]) || 1;
+ const f = (p - a[0]) / span;
+ const ca = hexToRgb(a[1]), cb = hexToRgb(b[1]);
+ ramp[i*3] = ca[0] + (cb[0] - ca[0]) * f;
+ ramp[i*3+1] = ca[1] + (cb[1] - ca[1]) * f;
+ ramp[i*3+2] = ca[2] + (cb[2] - ca[2]) * f;
+ }
+ }
+ buildRamp(theme);
+
+ // ---- Sizing ----------------------------------------------------------
+ let W = 0, H = 0, minDim = 0;
+ let lowW = 0, lowH = 0, scale = 0.22;
+ let imgData = null, buf = null;
+ let bgGrad = null;
+
+ function resize() {
+ W = canvas.width = Math.max(1, window.innerWidth);
+ H = canvas.height = Math.max(1, window.innerHeight);
+ minDim = Math.min(W, H);
+
+ // Keep the low-res field around ~200px on the long edge
+ scale = Math.min(0.26, 200 / Math.max(W, H));
+ lowW = Math.max(2, Math.round(W * scale));
+ lowH = Math.max(2, Math.round(H * scale));
+ off.width = lowW;
+ off.height = lowH;
+ imgData = offCtx.createImageData(lowW, lowH);
+ buf = imgData.data;
+
+ bgGrad = ctx.createLinearGradient(0, 0, 0, H);
+ bgGrad.addColorStop(0, theme.bg[0]);
+ bgGrad.addColorStop(1, theme.bg[1]);
+
+ for (const b of blobs) recomputeRadius(b);
+ }
+
+ // ---- Blobs -----------------------------------------------------------
+ const blobs = [];
+
+ function recomputeRadius(b) {
+ b.r = b.rFrac * minDim;
+ }
+
+ function makeBlob() {
+ const b = {
+ x: Math.random() * (W || 800),
+ y: Math.random() * (H || 600),
+ rFrac: 0.05 + Math.random() * 0.1,
+ vs: 0.2 + Math.random() * 0.4, // vertical osc speed
+ vp: Math.random() * Math.PI * 2, // vertical phase
+ vamp: 30 + Math.random() * 60, // vertical amplitude (px/s)
+ hs: 0.1 + Math.random() * 0.3, // horizontal speed
+ hp: Math.random() * Math.PI * 2,
+ hamp: 15 + Math.random() * 30, // horizontal drift
+ ps: 0.4 + Math.random() * 0.8, // pulse speed
+ pp: Math.random() * Math.PI * 2,
+ r: 40
+ };
+ recomputeRadius(b);
+ return b;
+ }
+
+ function setCount(n) {
+ while (blobs.length < n) blobs.push(makeBlob());
+ while (blobs.length > n) blobs.pop();
+ }
+
+ // ---- Physics ---------------------------------------------------------
+ function update(t, dt) {
+ for (const b of blobs) {
+ const vy = Math.sin(t * b.vs + b.vp) * b.vamp;
+ const vx = Math.sin(t * b.hs + b.hp) * b.hamp;
+ b.x += vx * dt;
+ b.y += vy * dt;
+
+ const pad = b.r * 0.35;
+ if (b.x < pad) { b.x = pad; b.hp += Math.PI; }
+ else if (b.x > W - pad) { b.x = W - pad; b.hp += Math.PI; }
+ if (b.y < pad) { b.y = pad; b.vp += Math.PI; }
+ else if (b.y > H - pad) { b.y = H - pad; b.vp += Math.PI; }
+
+ b.rr = b.r * (1 + Math.sin(t * b.ps + b.pp) * 0.1); // pulsating radius
+ }
+ }
+
+ // ---- Metaball render -------------------------------------------------
+ function renderField(t) {
+ // Precompute low-res blob params
+ const n = blobs.length;
+ const bx = new Float32Array(n), by = new Float32Array(n), br2 = new Float32Array(n);
+ for (let i = 0; i < n; i++) {
+ const b = blobs[i];
+ bx[i] = b.x * scale;
+ by[i] = b.y * scale;
+ const rl = b.rr * scale;
+ br2[i] = rl * rl;
+ }
+
+ let p = 0;
+ for (let y = 0; y < lowH; y++) {
+ for (let x = 0; x < lowW; x++) {
+ let sum = 0;
+ for (let i = 0; i < n; i++) {
+ const dx = x - bx[i], dy = y - by[i];
+ const d2 = dx * dx + dy * dy + 0.001;
+ sum += br2[i] / d2;
+ }
+ let a;
+ if (sum < 0.65) {
+ a = 0;
+ } else {
+ a = sum < 1.05 ? (sum - 0.65) / 0.4 : 1;
+ let ct = (sum - 0.7) / 2.6; // color param edge->core
+ if (ct < 0) ct = 0; else if (ct > 1) ct = 1;
+ const ci = (ct * 255) | 0;
+ const r3 = ci * 3;
+ buf[p] = ramp[r3];
+ buf[p + 1] = ramp[r3 + 1];
+ buf[p + 2] = ramp[r3 + 2];
+ }
+ buf[p + 3] = a * 255;
+ p += 4;
+ }
+ }
+ offCtx.putImageData(imgData, 0, 0);
+ }
+
+ // ---- Main loop -------------------------------------------------------
+ let last = performance.now();
+ function frame(now) {
+ let dt = (now - last) / 1000;
+ if (dt > 0.05) dt = 0.05; // clamp after tab switch
+ last = now;
+ const t = now / 1000;
+
+ update(t, dt);
+ renderField(t);
+
+ // Background gradient
+ ctx.globalCompositeOperation = "source-over";
+ ctx.fillStyle = bgGrad;
+ ctx.fillRect(0, 0, W, H);
+
+ // Upscale the metaball layer smoothly on top
+ ctx.imageSmoothingEnabled = true;
+ ctx.imageSmoothingQuality = "high";
+ ctx.drawImage(off, 0, 0, lowW, lowH, 0, 0, W, H);
+
+ // Soft glow pass
+ ctx.globalCompositeOperation = "lighter";
+ ctx.globalAlpha = 0.35;
+ ctx.drawImage(off, 0, 0, lowW, lowH, 0, 0, W, H);
+ ctx.globalAlpha = 1;
+ ctx.globalCompositeOperation = "source-over";
+
+ requestAnimationFrame(frame);
+ }
+
+ // ---- Controls --------------------------------------------------------
+ const themeSel = document.getElementById("theme");
+ const countInp = document.getElementById("count");
+ const countVal = document.getElementById("countVal");
+
+ themeSel.addEventListener("change", function () {
+ theme = THEMES[themeSel.value] || THEMES.classic;
+ buildRamp(theme);
+ bgGrad = ctx.createLinearGradient(0, 0, 0, H);
+ bgGrad.addColorStop(0, theme.bg[0]);
+ bgGrad.addColorStop(1, theme.bg[1]);
+ });
+
+ countInp.addEventListener("input", function () {
+ countVal.textContent = countInp.value;
+ setCount(parseInt(countInp.value, 10));
+ });
+
+ window.addEventListener("resize", resize);
+
+ // ---- Boot ------------------------------------------------------------
+ setCount(parseInt(countInp.value, 10));
+ resize();
+ requestAnimationFrame(frame);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ea271db82cff/hf-qwen-coder-32b.png b/data/artifacts/ea271db82cff/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..ff39959
Binary files /dev/null and b/data/artifacts/ea271db82cff/hf-qwen-coder-32b.png differ
diff --git a/data/challenges.json b/data/challenges.json
index 7ac2bba..c25b90b 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -11335,7 +11335,14 @@
"finished_at": "2026-07-25T06:13:57.155Z",
"queued_at": "2026-07-25T06:11:28.260Z",
"bytes": 7711,
- "thumb": true
+ "thumb": true,
+ "aiScore": 7,
+ "aiReason": "The game is functional but lacks some visual elements like particle explosions and a restart screen.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7
+ },
+ "aiSpread": 0
},
{
"model": "gemma3-12b",
@@ -11347,7 +11354,14 @@
"finished_at": "2026-07-25T06:15:55.094Z",
"queued_at": "2026-07-25T06:11:28.266Z",
"bytes": 7432,
- "thumb": true
+ "thumb": true,
+ "aiScore": 7,
+ "aiReason": "The game is functional but lacks the neon command-center aesthetic and particle effects.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7
+ },
+ "aiSpread": 0
},
{
"model": "hermes3-8b",
@@ -11359,7 +11373,14 @@
"finished_at": "2026-07-25T06:16:19.228Z",
"queued_at": "2026-07-25T06:11:28.271Z",
"bytes": 6555,
- "thumb": true
+ "thumb": true,
+ "aiScore": 6.5,
+ "aiReason": "The game is functional and visually appealing but lacks some elements like particle explosions on hits.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 1
},
{
"model": "qwen25-7b",
@@ -11371,7 +11392,14 @@
"finished_at": "2026-07-25T06:11:53.407Z",
"queued_at": "2026-07-25T06:11:28.278Z",
"bytes": 2988,
- "thumb": true
+ "thumb": true,
+ "aiScore": 4.5,
+ "aiReason": "The HTML file is present but lacks the required game mechanics and visual elements.",
+ "aiScores": {
+ "qwen2.5vl:7b": 3,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 3
},
{
"model": "hf-qwen-coder-32b",
@@ -11383,7 +11411,14 @@
"finished_at": "2026-07-25T06:17:35.300Z",
"queued_at": "2026-07-25T06:11:28.282Z",
"bytes": 6033,
- "thumb": true
+ "thumb": true,
+ "aiScore": 7,
+ "aiReason": "The game is functional and meets the basic requirements but lacks some visual elements like HUD or particle effects.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7
+ },
+ "aiSpread": 0
},
{
"model": "claude-code",
@@ -11396,8 +11431,9 @@
"queued_at": "2026-07-25T06:11:28.286Z"
}
],
- "judging": true,
- "judged_at": null
+ "judging": false,
+ "judged_at": "2026-07-25T06:30:46.725Z",
+ "aiPick": "qwen3-14b"
},
{
"id": "ea271db82cff",
@@ -11420,29 +11456,54 @@
"queued_at": "2026-07-25T06:17:53.526Z",
"resumes": 1,
"bytes": 11236,
- "thumb": true
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The model effectively fulfills the challenge with smooth metaball rendering and a warm gradient. The visual quality is polished and the design is coherent.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
},
{
"model": "gemma3-12b",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": 0,
- "cost": null,
+ "seconds": 119,
+ "cost": 0,
"started_at": "2026-07-25T06:19:28.462Z",
- "finished_at": null,
+ "finished_at": "2026-07-25T06:21:27.757Z",
"queued_at": "2026-07-25T06:17:53.528Z",
- "resumes": 1
+ "resumes": 1,
+ "bytes": 10284,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The HTML page effectively implements the metaball lava lamp challenge with smooth metaball rendering and a warm gradient. The visual quality is polished and the design is on-brief.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
},
{
"model": "hermes3-8b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": 0,
- "cost": null,
- "started_at": null,
- "finished_at": null,
+ "seconds": 83,
+ "cost": 0,
+ "started_at": "2026-07-25T06:21:27.762Z",
+ "finished_at": "2026-07-25T06:22:50.897Z",
"queued_at": "2026-07-25T06:17:53.529Z",
- "resumes": 1
+ "resumes": 1,
+ "bytes": 11700,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The model effectively fulfills the challenge with smooth metaball rendering and a warm gradient. The visual quality is polished and the implementation is self-contained without external libraries.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
},
{
"model": "qwen25-7b",
@@ -11455,22 +11516,40 @@
"queued_at": "2026-07-25T06:17:53.531Z",
"resumes": 1,
"bytes": 6014,
- "thumb": true
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The page is visually appealing and fulfills the challenge requirements effectively.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
},
{
"model": "hf-qwen-coder-32b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": 0,
- "cost": null,
- "started_at": null,
- "finished_at": null,
+ "seconds": 216,
+ "cost": 0,
+ "started_at": "2026-07-25T06:22:50.904Z",
+ "finished_at": "2026-07-25T06:26:27.069Z",
"queued_at": "2026-07-25T06:17:53.532Z",
- "resumes": 1
+ "resumes": 1,
+ "bytes": 11674,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The model effectively fulfills the challenge with smooth metaball rendering and a warm gradient. The visual quality is polished and the interface is user-friendly.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
}
],
- "aiPick": null,
- "category": "Games"
+ "aiPick": "qwen3-14b",
+ "category": "Games",
+ "judging": false,
+ "judged_at": "2026-07-25T06:30:50.763Z"
},
{
"id": "dfaee602df0d",
@@ -11483,33 +11562,60 @@
"runs": [
{
"model": "qwen3-14b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T06:18:24.456Z"
+ "seconds": 36,
+ "cost": 0,
+ "started_at": "2026-07-25T06:26:40.001Z",
+ "finished_at": "2026-07-25T06:27:16.112Z",
+ "queued_at": "2026-07-25T06:18:24.456Z",
+ "bytes": 4987,
+ "thumb": true,
+ "aiScore": 6.8,
+ "aiReason": "The game is functional but lacks visual polish and detail.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 0.5
},
{
"model": "gemma3-12b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T06:18:24.458Z"
+ "seconds": 39,
+ "cost": 0,
+ "started_at": "2026-07-25T06:27:16.119Z",
+ "finished_at": "2026-07-25T06:27:55.426Z",
+ "queued_at": "2026-07-25T06:18:24.458Z",
+ "bytes": 5454,
+ "thumb": true,
+ "aiScore": 6,
+ "aiReason": "The game is functional but lacks visual polish and does not fully meet the requirement of a neon dark UI.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 5
+ },
+ "aiSpread": 2
},
{
"model": "hermes3-8b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T06:18:24.460Z"
+ "seconds": 16,
+ "cost": 0,
+ "started_at": "2026-07-25T06:27:55.430Z",
+ "finished_at": "2026-07-25T06:28:11.699Z",
+ "queued_at": "2026-07-25T06:18:24.460Z",
+ "bytes": 5285,
+ "thumb": true,
+ "aiScore": 5.3,
+ "aiReason": "The HTML file is present but lacks the visual elements and functionality required for a Snake game reimagined as an AI agent swarm.",
+ "aiScores": {
+ "qwen2.5vl:7b": 4,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 2.5
},
{
"model": "qwen25-7b",
@@ -11521,17 +11627,33 @@
"finished_at": "2026-07-25T06:19:07.734Z",
"queued_at": "2026-07-25T06:18:24.462Z",
"bytes": 2758,
- "thumb": true
+ "thumb": true,
+ "aiScore": 4,
+ "aiReason": "The game is functional but lacks visual elements and does not fully represent the swarm concept.",
+ "aiScores": {
+ "qwen2.5vl:7b": 5,
+ "minicpm-v:latest": 3
+ },
+ "aiSpread": 2
},
{
"model": "hf-qwen-coder-32b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T06:18:24.463Z"
+ "seconds": 47,
+ "cost": 0,
+ "started_at": "2026-07-25T06:28:11.705Z",
+ "finished_at": "2026-07-25T06:28:58.644Z",
+ "queued_at": "2026-07-25T06:18:24.463Z",
+ "bytes": 3048,
+ "thumb": true,
+ "aiScore": 6,
+ "aiReason": "The game is functional but lacks visual polish and detail.",
+ "aiScores": {
+ "qwen2.5vl:7b": 6,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 0
},
{
"model": "claude-code",
@@ -11542,8 +11664,19 @@
"started_at": "2026-07-25T06:18:24.466Z",
"finished_at": "2026-07-25T06:19:44.371Z",
"queued_at": "2026-07-25T06:18:24.465Z",
- "bytes": 15575
+ "bytes": 15575,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The HTML file effectively fulfills the challenge by creating a Snake game reimagined as an AI agent swarm with a clear UI and smooth gameplay.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
}
- ]
+ ],
+ "judging": false,
+ "aiPick": "claude-code",
+ "judged_at": "2026-07-25T06:30:54.919Z"
}
]
\ No newline at end of file
diff --git a/data/landings/48026228790d-gpt.html b/data/landings/48026228790d-gpt.html
new file mode 100644
index 0000000..50483cd
--- /dev/null
+++ b/data/landings/48026228790d-gpt.html
@@ -0,0 +1,323 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>All-Models — Voronoi Shatter — built by GPT-5.1 · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>All-Models — Voronoi Shatter</h1><span class="by">built by <b>GPT-5.1</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>Voronoi Shatter</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ background: #05070b;
+ color: #eee;
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ }
+ #ui {
+ position: fixed;
+ top: 8px;
+ left: 10px;
+ padding: 6px 10px;
+ background: rgba(0,0,0,0.6);
+ border-radius: 6px;
+ font-size: 13px;
+ user-select: none;
+ z-index: 10;
+ }
+ #ui button {
+ margin-right: 8px;
+ padding: 4px 8px;
+ font-size: 12px;
+ background: #222;
+ color: #eee;
+ border: 1px solid #444;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ #ui button:hover {
+ background: #333;
+ }
+ #ui span {
+ opacity: 0.75;
+ }
+ canvas {
+ display: block;
+ }
+</style>
+</head>
+<body>
+<div id="ui">
+ <button id="togglePoints">Hide Seed Points</button>
+ <span>Click: add seed &amp; animate shatter</span>
+</div>
+<canvas id="c"></canvas>
+<script>
+(function() {
+ const canvas = document.getElementById('c');
+ 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;
+ drawFrame(performance.now());
+ });
+
+ const toggleBtn = document.getElementById('togglePoints');
+ let showPoints = true;
+ toggleBtn.onclick = () => {
+ showPoints = !showPoints;
+ toggleBtn.textContent = showPoints ? 'Hide Seed Points' : 'Show Seed Points';
+ drawFrame(performance.now());
+ };
+
+ // Seed points
+ let seeds = [];
+ const INITIAL_SEEDS = 20;
+
+ function randomSeed() {
+ return {
+ x: Math.random() * width,
+ y: Math.random() * height
+ };
+ }
+
+ function initSeeds() {
+ seeds = [];
+ for (let i = 0; i < INITIAL_SEEDS; i++) {
+ seeds.push(randomSeed());
+ }
+ }
+
+ initSeeds();
+
+ // Lloyd relaxation (one iteration) for nicer cells
+ function relax(points) {
+ const sites = points.map(p => ({x: p.x, y: p.y, sumx: 0, sumy: 0, count: 0}));
+ // Sample grid for approximate centroids
+ const step = 15;
+ for (let y = 0; y < height; y += step) {
+ for (let x = 0; x < width; x += step) {
+ let minD = Infinity, idx = 0;
+ for (let i = 0; i < sites.length; i++) {
+ const dx = x - sites[i].x;
+ const dy = y - sites[i].y;
+ const d = dx*dx + dy*dy;
+ if (d < minD) { minD = d; idx = i; }
+ }
+ sites[idx].sumx += x;
+ sites[idx].sumy += y;
+ sites[idx].count++;
+ }
+ }
+ const relaxed = [];
+ for (let s of sites) {
+ if (s.count > 0) {
+ relaxed.push({x: s.sumx / s.count, y: s.sumy / s.count});
+ } else {
+ relaxed.push({x: s.x, y: s.y});
+ }
+ }
+ return relaxed;
+ }
+
+ // Generate clip path polygon for each cell by sampling grid
+ function computeCellPolygons(points) {
+ const polygons = new Array(points.length);
+ for (let i = 0; i < polygons.length; i++) polygons[i] = [];
+
+ const step = 12; // sampling step; balance speed vs detail
+ for (let y = 0; y <= height; y += step) {
+ for (let x = 0; x <= width; x += step) {
+ let minD = Infinity, idx = 0;
+ for (let i = 0; i < points.length; i++) {
+ const dx = x - points[i].x;
+ const dy = y - points[i].y;
+ const d = dx*dx + dy*dy;
+ if (d < minD) { minD = d; idx = i; }
+ }
+ polygons[idx].push({x, y});
+ }
+ }
+
+ // Convex hull via Graham scan for each cell's sample points
+ function convexHull(pts) {
+ if (pts.length < 3) return pts.slice();
+ // sort by x then y
+ pts = pts.slice().sort((a, b) => a.x === b.x ? a.y - b.y : a.x - b.x);
+ const cross = (o, a, b) => (a.x - o.x)*(b.y - o.y) - (a.y - o.y)*(b.x - o.x);
+ const lower = [];
+ for (let p of pts) {
+ while (lower.length >= 2 && cross(lower[lower.length-2], lower[lower.length-1], p) <= 0)
+ lower.pop();
+ lower.push(p);
+ }
+ const upper = [];
+ for (let i = pts.length - 1; i >= 0; i--) {
+ const p = pts[i];
+ while (upper.length >= 2 && cross(upper[upper.length-2], upper[upper.length-1], p) <= 0)
+ upper.pop();
+ upper.push(p);
+ }
+ upper.pop();
+ lower.pop();
+ return lower.concat(upper);
+ }
+
+ const hulls = [];
+ for (let i = 0; i < polygons.length; i++) {
+ if (polygons[i].length === 0) {
+ hulls.push([]);
+ } else {
+ hulls.push(convexHull(polygons[i]));
+ }
+ }
+ return hulls;
+ }
+
+ // Color generation for each seed
+ function makePalette(n) {
+ const cols = [];
+ for (let i = 0; i < n; i++) {
+ const h = (i / n) * 360;
+ const s = 60 + Math.random() * 20;
+ const l1 = 30 + Math.random() * 10;
+ const l2 = 55 + Math.random() * 15;
+ cols.push({
+ from: `hsl(${h},${s}%,${l1}%)`,
+ to: `hsl(${(h+40)%360},${s}%,${l2}%)`
+ });
+ }
+ return cols;
+ }
+
+ let relaxedSeeds = relax(seeds);
+ let cellPolys = computeCellPolygons(relaxedSeeds);
+ let colors = makePalette(seeds.length);
+
+ // Animation state
+ let animStart = 0;
+ const ANIM_DURATION = 800; // ms
+ let fromSeeds = relaxedSeeds;
+ let toSeeds = relaxedSeeds;
+
+ function easeOutCubic(t) {
+ return 1 - Math.pow(1 - t, 3);
+ }
+
+ function interpolateSeeds(t) {
+ const interp = [];
+ const n = fromSeeds.length;
+ for (let i = 0; i < n; i++) {
+ const a = fromSeeds[i];
+ const b = toSeeds[i] || fromSeeds[i];
+ interp.push({
+ x: a.x + (b.x - a.x) * t,
+ y: a.y + (b.y - a.y) * t
+ });
+ }
+ return interp;
+ }
+
+ function drawFrame(now) {
+ ctx.clearRect(0, 0, width, height);
+
+ let t = 1;
+ if (animStart > 0) {
+ t = Math.min(1, (now - animStart) / ANIM_DURATION);
+ t = easeOutCubic(t);
+ if (t >= 1) animStart = 0;
+ }
+ const interpSeeds = interpolateSeeds(t);
+ const interpPolys = computeCellPolygons(interpSeeds);
+
+ for (let i = 0; i < interpSeeds.length; i++) {
+ const poly = interpPolys[i];
+ if (!poly || poly.length === 0) continue;
+
+ const seed = interpSeeds[i];
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(poly[0].x, poly[0].y);
+ for (let j = 1; j < poly.length; j++) {
+ ctx.lineTo(poly[j].x, poly[j].y);
+ }
+ ctx.closePath();
+ ctx.clip();
+
+ // Radial gradient from seed outward
+ const maxR = Math.max(width, height);
+ const grad = ctx.createRadialGradient(seed.x, seed.y, 0, seed.x, seed.y, maxR * 0.8);
+ const col = colors[i];
+ grad.addColorStop(0, col.from);
+ grad.addColorStop(1, col.to);
+
+ ctx.fillStyle = grad;
+ ctx.fillRect(0, 0, width, height);
+
+ // Subtle inner border
+ ctx.strokeStyle = 'rgba(0,0,0,0.35)';
+ ctx.lineWidth = 1;
+ ctx.stroke();
+
+ ctx.restore();
+ }
+
+ // Seed points
+ if (showPoints) {
+ ctx.fillStyle = '#ffffff';
+ for (let i = 0; i < interpSeeds.length; i++) {
+ const s = interpSeeds[i];
+ ctx.beginPath();
+ ctx.arc(s.x, s.y, 3, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.strokeStyle = 'rgba(0,0,0,0.6)';
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ }
+ }
+ }
+
+ function loop(now) {
+ drawFrame(now);
+ requestAnimationFrame(loop);
+ }
+ requestAnimationFrame(loop);
+
+ // On click: add seed and animate towards relaxed configuration
+ canvas.addEventListener('click', e => {
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ seeds.push({x, y});
+ colors = makePalette(seeds.length);
+
+ fromSeeds = relaxedSeeds.slice();
+ // ensure equal length
+ fromSeeds.push({x, y});
+
+ const relaxedNew = relax(seeds);
+ relaxedSeeds = relaxedNew;
+ toSeeds = relaxedNew.slice();
+
+ animStart = performance.now();
+ });
+
+})();
+</script>
+</body>
+</html>" title="All-Models — Voronoi Shatter"></iframe></div>
+</body></html>
\ No newline at end of file
diff --git a/data/landings/48026228790d-grok.html b/data/landings/48026228790d-grok.html
new file mode 100644
index 0000000..805ff8e
--- /dev/null
+++ b/data/landings/48026228790d-grok.html
@@ -0,0 +1,563 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>All-Models — Voronoi Shatter — built by Grok 4.5 · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>All-Models — Voronoi Shatter</h1><span class="by">built by <b>Grok 4.5</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Voronoi Shatter</title>
+<style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ html, body {
+ width: 100%; height: 100%; overflow: hidden;
+ background: #0a0a0f;
+ font-family: "Segoe UI", system-ui, sans-serif;
+ }
+ canvas {
+ display: block;
+ width: 100%; height: 100%;
+ cursor: crosshair;
+ }
+ .hud {
+ position: fixed;
+ top: 20px; left: 20px;
+ z-index: 10;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ pointer-events: none;
+ }
+ .hud h1 {
+ color: rgba(255,255,255,0.9);
+ font-size: 15px;
+ font-weight: 600;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ text-shadow: 0 2px 12px rgba(0,0,0,0.5);
+ }
+ .hud p {
+ color: rgba(255,255,255,0.45);
+ font-size: 12px;
+ letter-spacing: 0.04em;
+ }
+ .controls {
+ position: fixed;
+ bottom: 24px; left: 50%;
+ transform: translateX(-50%);
+ z-index: 10;
+ display: flex;
+ gap: 10px;
+ pointer-events: auto;
+ }
+ button {
+ background: rgba(255,255,255,0.08);
+ border: 1px solid rgba(255,255,255,0.18);
+ color: rgba(255,255,255,0.85);
+ padding: 10px 18px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 500;
+ letter-spacing: 0.06em;
+ cursor: pointer;
+ backdrop-filter: blur(12px);
+ transition: background 0.2s, border-color 0.2s, transform 0.15s;
+ }
+ button:hover {
+ background: rgba(255,255,255,0.16);
+ border-color: rgba(255,255,255,0.35);
+ }
+ button:active { transform: scale(0.96); }
+ button.active {
+ background: rgba(120,180,255,0.25);
+ border-color: rgba(140,200,255,0.55);
+ color: #cfe6ff;
+ }
+</style>
+</head>
+<body>
+<canvas id="c"></canvas>
+<div class="hud">
+ <h1>Voronoi Shatter</h1>
+ <p>Click to add a seed · cells re-tessellate</p>
+</div>
+<div class="controls">
+ <button id="toggleSeeds" class="active">Seed Points: On</button>
+ <button id="reset">Reset</button>
+</div>
+<script>
+(function () {
+ const canvas = document.getElementById("c");
+ const ctx = canvas.getContext("2d");
+ const toggleBtn = document.getElementById("toggleSeeds");
+ const resetBtn = document.getElementById("reset");
+
+ let W, H, dpr;
+ let sites = [];
+ let showSeeds = true;
+ let animT = 1;
+ let animFrom = null;
+ let animTo = null;
+ let animating = false;
+ const ANIM_MS = 480;
+
+ 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);
+ }
+
+ function randColor(i) {
+ const h = (i * 137.508 + 40) % 360;
+ const s = 55 + (i * 17) % 30;
+ const l = 48 + (i * 13) % 18;
+ return { h, s, l };
+ }
+
+ function hsl(c, a) {
+ return a == null
+ ? `hsl(${c.h},${c.s}%,${c.l}%)`
+ : `hsla(${c.h},${c.s}%,${c.l}%,${a})`;
+ }
+
+ function seedInitial(n) {
+ sites = [];
+ const pad = 40;
+ for (let i = 0; i < n; i++) {
+ sites.push({
+ x: pad + Math.random() * (W - pad * 2),
+ y: pad + Math.random() * (H - pad * 2),
+ color: randColor(i),
+ id: i
+ });
+ }
+ }
+
+ // --- Geometry helpers ---
+ function circumcircle(a, b, c) {
+ const ax = a.x, ay = a.y, bx = b.x, by = b.y, cx = c.x, cy = c.y;
+ const D = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
+ if (Math.abs(D) < 1e-12) return null;
+ const a2 = ax * ax + ay * ay;
+ const b2 = bx * bx + by * by;
+ const c2 = cx * cx + cy * cy;
+ const ux = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / D;
+ const uy = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / D;
+ const r2 = (ux - ax) * (ux - ax) + (uy - ay) * (uy - ay);
+ return { x: ux, y: uy, r2 };
+ }
+
+ function inCircle(p, circ) {
+ const dx = p.x - circ.x, dy = p.y - circ.y;
+ return dx * dx + dy * dy < circ.r2 - 1e-9;
+ }
+
+ // Bowyer–Watson Delaunay
+ function delaunay(pts) {
+ if (pts.length < 3) return [];
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
+ for (const p of pts) {
+ if (p.x < minX) minX = p.x;
+ if (p.y < minY) minY = p.y;
+ if (p.x > maxX) maxX = p.x;
+ if (p.y > maxY) maxY = p.y;
+ }
+ const dx = maxX - minX, dy = maxY - minY;
+ const dmax = Math.max(dx, dy) * 3;
+ const mx = (minX + maxX) / 2, my = (minY + maxY) / 2;
+ const superTri = [
+ { x: mx - 2 * dmax, y: my - dmax, _s: true },
+ { x: mx, y: my + 2 * dmax, _s: true },
+ { x: mx + 2 * dmax, y: my - dmax, _s: true }
+ ];
+ let tris = [{ a: superTri[0], b: superTri[1], c: superTri[2] }];
+
+ for (const p of pts) {
+ const bad = [];
+ for (const t of tris) {
+ const cc = circumcircle(t.a, t.b, t.c);
+ if (cc && inCircle(p, cc)) bad.push(t);
+ }
+ const edges = [];
+ for (const t of bad) {
+ edges.push([t.a, t.b], [t.b, t.c], [t.c, t.a]);
+ }
+ // unique edges
+ const unique = [];
+ for (let i = 0; i < edges.length; i++) {
+ let shared = false;
+ for (let j = 0; j < edges.length; j++) {
+ if (i === j) continue;
+ const e1 = edges[i], e2 = edges[j];
+ if ((e1[0] === e2[0] && e1[1] === e2[1]) ||
+ (e1[0] === e2[1] && e1[1] === e2[0])) {
+ shared = true;
+ break;
+ }
+ }
+ if (!shared) unique.push(edges[i]);
+ }
+ tris = tris.filter(t => bad.indexOf(t) === -1);
+ for (const e of unique) {
+ tris.push({ a: e[0], b: e[1], c: p });
+ }
+ }
+
+ return tris.filter(t =>
+ !t.a._s && !t.b._s && !t.c._s
+ );
+ }
+
+ // Build Voronoi cells from Delaunay dual
+ function voronoiCells(pts) {
+ const n = pts.length;
+ if (n === 0) return [];
+ if (n === 1) {
+ return [{
+ site: pts[0],
+ poly: [
+ { x: -1, y: -1 }, { x: W + 1, y: -1 },
+ { x: W + 1, y: H + 1 }, { x: -1, y: H + 1 }
+ ]
+ }];
+ }
+
+ const tris = delaunay(pts);
+ // Map site -> list of circumcenters (with triangle refs)
+ const adj = new Map();
+ for (const p of pts) adj.set(p, []);
+
+ const centers = [];
+ for (const t of tris) {
+ const cc = circumcircle(t.a, t.b, t.c);
+ if (!cc) continue;
+ const c = { x: cc.x, y: cc.y };
+ centers.push(c);
+ adj.get(t.a).push(c);
+ adj.get(t.b).push(c);
+ adj.get(t.c).push(c);
+ }
+
+ // For boundary sites, extend with clipped box intersections via half-planes
+ // Clip infinite rays against bounding box using half-plane intersection
+ const box = [
+ { x: 0, y: 0 }, { x: W, y: 0 },
+ { x: W, y: H }, { x: 0, y: H }
+ ];
+
+ const cells = [];
+ for (const site of pts) {
+ // Collect half-planes: for each other site, bisector half-plane
+ // Start with bounding box polygon, clip by each bisector
+ let poly = box.map(p => ({ x: p.x, y: p.y }));
+ for (const other of pts) {
+ if (other === site) continue;
+ // Half-plane: points closer to site than other
+ // Midpoint + direction perpendicular to (other - site)
+ const mx = (site.x + other.x) / 2;
+ const my = (site.y + other.y) / 2;
+ // Normal pointing toward site from other
+ let nx = site.x - other.x;
+ let ny = site.y - other.y;
+ // Plane: (p - mid) · n >= 0 (on site side)
+ poly = clipPoly(poly, mx, my, nx, ny);
+ if (poly.length < 3) break;
+ }
+ if (poly.length >= 3) {
+ cells.push({ site, poly });
+ }
+ }
+ return cells;
+ }
+
+ // Sutherland–Hodgman style half-plane clip
+ function clipPoly(poly, px, py, nx, ny) {
+ if (!poly.length) return [];
+ const out = [];
+ const n = poly.length;
+ for (let i = 0; i < n; i++) {
+ const cur = poly[i];
+ const prev = poly[(i + n - 1) % n];
+ const curIn = (cur.x - px) * nx + (cur.y - py) * ny >= -1e-9;
+ const prevIn = (prev.x - px) * nx + (prev.y - py) * ny >= -1e-9;
+ if (curIn) {
+ if (!prevIn) {
+ const ip = lineIntersect(prev, cur, px, py, nx, ny);
+ if (ip) out.push(ip);
+ }
+ out.push(cur);
+ } else if (prevIn) {
+ const ip = lineIntersect(prev, cur, px, py, nx, ny);
+ if (ip) out.push(ip);
+ }
+ }
+ return out;
+ }
+
+ function lineIntersect(a, b, px, py, nx, ny) {
+ const dx = b.x - a.x, dy = b.y - a.y;
+ const denom = dx * nx + dy * ny;
+ if (Math.abs(denom) < 1e-14) return { x: a.x, y: a.y };
+ const t = ((px - a.x) * nx + (py - a.y) * ny) / denom;
+ return { x: a.x + t * dx, y: a.y + t * dy };
+ }
+
+ // Match cells between frames for animation (by site identity / nearest)
+ function computeCells() {
+ return voronoiCells(sites);
+ }
+
+ let currentCells = [];
+
+ function easeOutCubic(t) {
+ return 1 - Math.pow(1 - t, 3);
+ }
+
+ function lerp(a, b, t) {
+ return a + (b - a) * t;
+ }
+
+ function drawCell(cell, alpha) {
+ const poly = cell.poly;
+ if (!poly || poly.length < 3) return;
+ const s = cell.site;
+ const c = s.color;
+
+ ctx.beginPath();
+ ctx.moveTo(poly[0].x, poly[0].y);
+ for (let i = 1; i < poly.length; i++) {
+ ctx.lineTo(poly[i].x, poly[i].y);
+ }
+ ctx.closePath();
+
+ // Radial gradient from site
+ let maxR = 0;
+ for (const p of poly) {
+ const dx = p.x - s.x, dy = p.y - s.y;
+ const r = Math.sqrt(dx * dx + dy * dy);
+ if (r > maxR) maxR = r;
+ }
+ maxR = Math.max(maxR, 40);
+
+ const g = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, maxR);
+ g.addColorStop(0, hsl({ h: c.h, s: c.s, l: Math.min(c.l + 18, 78) }, alpha));
+ g.addColorStop(0.45, hsl(c, alpha));
+ g.addColorStop(1, hsl({ h: c.h, s: c.s + 5, l: Math.max(c.l - 22, 12) }, alpha));
+ ctx.fillStyle = g;
+ ctx.fill();
+
+ // Subtle edge
+ ctx.strokeStyle = `rgba(255,255,255,${0.12 * alpha})`;
+ ctx.lineWidth = 1.25;
+ ctx.stroke();
+
+ // Inner highlight edge
+ ctx.strokeStyle = hsl({ h: c.h, s: c.s, l: Math.min(c.l + 30, 90) }, 0.15 * alpha);
+ ctx.lineWidth = 0.6;
+ ctx.stroke();
+ }
+
+ function drawSeeds(list, alpha) {
+ for (const s of list) {
+ ctx.beginPath();
+ ctx.arc(s.x, s.y, 3.5, 0, Math.PI * 2);
+ ctx.fillStyle = `rgba(255,255,255,${0.85 * alpha})`;
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(s.x, s.y, 6, 0, Math.PI * 2);
+ ctx.strokeStyle = `rgba(255,255,255,${0.25 * alpha})`;
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ }
+ }
+
+ // Interpolate polygons: match by angle around site centroid
+ function sortPoly(poly, cx, cy) {
+ return poly.slice().sort((a, b) =>
+ Math.atan2(a.y - cy, a.x - cx) - Math.atan2(b.y - cy, b.x - cx)
+ );
+ }
+
+ function resamplePoly(poly, n, cx, cy) {
+ const sorted = sortPoly(poly, cx, cy);
+ // Build cumulative edge lengths for closed poly
+ const pts = sorted.concat([sorted[0]]);
+ const segs = [];
+ let total = 0;
+ for (let i = 0; i < pts.length - 1; i++) {
+ const dx = pts[i + 1].x - pts[i].x;
+ const dy = pts[i + 1].y - pts[i].y;
+ const len = Math.sqrt(dx * dx + dy * dy);
+ segs.push({ a: pts[i], b: pts[i + 1], len, acc: total });
+ total += len;
+ }
+ if (total < 1e-6) {
+ return Array.from({ length: n }, () => ({ x: cx, y: cy }));
+ }
+ const out = [];
+ for (let i = 0; i < n; i++) {
+ const t = (i / n) * total;
+ let seg = segs[segs.length - 1];
+ for (const s of segs) {
+ if (t >= s.acc && t <= s.acc + s.len + 1e-9) { seg = s; break; }
+ }
+ const lt = seg.len < 1e-9 ? 0 : (t - seg.acc) / seg.len;
+ out.push({
+ x: lerp(seg.a.x, seg.b.x, lt),
+ y: lerp(seg.a.y, seg.b.y, lt)
+ });
+ }
+ return out;
+ }
+
+ function morphCells(fromCells, toCells, t) {
+ // Match by site id
+ const fromMap = new Map();
+ for (const c of fromCells) fromMap.set(c.site.id, c);
+ const result = [];
+ for (const tc of toCells) {
+ const fc = fromMap.get(tc.site.id);
+ if (!fc) {
+ // New cell: scale from site
+ const poly = tc.poly.map(p => ({
+ x: lerp(tc.site.x, p.x, t),
+ y: lerp(tc.site.y, p.y, t)
+ }));
+ result.push({ site: tc.site, poly });
+ continue;
+ }
+ const n = Math.max(fc.poly.length, tc.poly.length, 8);
+ const a = resamplePoly(fc.poly, n, fc.site.x, fc.site.y);
+ const b = resamplePoly(tc.poly, n, tc.site.x, tc.site.y);
+ const poly = a.map((p, i) => ({
+ x: lerp(p.x, b[i].x, t),
+ y: lerp(p.y, b[i].y, t)
+ }));
+ // Lerp site position too (in case)
+ const site = {
+ x: lerp(fc.site.x, tc.site.x, t),
+ y: lerp(fc.site.y, tc.site.y, t),
+ color: tc.site.color,
+ id: tc.site.id
+ };
+ result.push({ site, poly });
+ }
+ return result;
+ }
+
+ let animStart = 0;
+ let prevCells = [];
+ let targetCells = [];
+
+ function render(now) {
+ if (animating) {
+ const elapsed = now - animStart;
+ animT = Math.min(1, elapsed / ANIM_MS);
+ if (animT >= 1) {
+ animating = false;
+ animT = 1;
+ currentCells = targetCells;
+ } else {
+ currentCells = morphCells(prevCells, targetCells, easeOutCubic(animT));
+ }
+ }
+
+ // Background
+ ctx.fillStyle = "#0a0a0f";
+ ctx.fillRect(0, 0, W, H);
+
+ // Soft vignette base
+ const vg = ctx.createRadialGradient(W / 2, H / 2, 0, W / 2, H / 2, Math.max(W, H) * 0.7);
+ vg.addColorStop(0, "rgba(20,22,40,0.4)");
+ vg.addColorStop(1, "rgba(5,5,10,0)");
+ ctx.fillStyle = vg;
+ ctx.fillRect(0, 0, W, H);
+
+ for (const cell of currentCells) {
+ drawCell(cell, 1);
+ }
+
+ if (showSeeds) {
+ drawSeeds(sites, 1);
+ }
+
+ // Flash ring on newest site during anim
+ if (animating && sites.length) {
+ const newest = sites[sites.length - 1];
+ const pulse = easeOutCubic(animT);
+ ctx.beginPath();
+ ctx.arc(newest.x, newest.y, 8 + pulse * 40, 0, Math.PI * 2);
+ ctx.strokeStyle = `rgba(255,255,255,${(1 - pulse) * 0.5})`;
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ }
+
+ requestAnimationFrame(render);
+ }
+
+ function recompute(animate) {
+ const next = computeCells();
+ if (animate && currentCells.length) {
+ prevCells = currentCells.map(c => ({
+ site: c.site,
+ poly: c.poly.map(p => ({ x: p.x, y: p.y }))
+ }));
+ targetCells = next;
+ animT = 0;
+ animStart = performance.now();
+ animating = true;
+ } else {
+ currentCells = next;
+ targetCells = next;
+ animating = false;
+ }
+ }
+
+ canvas.addEventListener("click", (e) => {
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ const id = sites.length ? sites[sites.length - 1].id + 1 : 0;
+ sites.push({
+ x, y,
+ color: randColor(id + Math.floor(Math.random() * 50)),
+ id
+ });
+ recompute(true);
+ });
+
+ toggleBtn.addEventListener("click", () => {
+ showSeeds = !showSeeds;
+ toggleBtn.textContent = showSeeds ? "Seed Points: On" : "Seed Points: Off";
+ toggleBtn.classList.toggle("active", showSeeds);
+ });
+
+ resetBtn.addEventListener("click", () => {
+ seedInitial(8 + Math.floor(Math.random() * 5));
+ recompute(true);
+ });
+
+ window.addEventListener("resize", () => {
+ resize();
+ recompute(false);
+ });
+
+ resize();
+ seedInitial(10);
+ recompute(false);
+ requestAnimationFrame(render);
+})();
+</script>
+</body>
+</html>" title="All-Models — Voronoi Shatter"></iframe></div>
+</body></html>
\ No newline at end of file
diff --git a/data/landings/48026228790d-kimi.html b/data/landings/48026228790d-kimi.html
new file mode 100644
index 0000000..d8c181c
--- /dev/null
+++ b/data/landings/48026228790d-kimi.html
@@ -0,0 +1,322 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>All-Models — Voronoi Shatter — built by Kimi K2.5 · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>All-Models — Voronoi Shatter</h1><span class="by">built by <b>Kimi K2.5</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Interactive Voronoi Shatter</title>
+<style>
+body {
+ margin: 0;
+ overflow: hidden;
+ background: #050505;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
+canvas {
+ display: block;
+ width: 100vw;
+ height: 100vh;
+ cursor: crosshair;
+}
+.controls {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ background: rgba(20, 20, 20, 0.9);
+ backdrop-filter: blur(10px);
+ padding: 20px;
+ border-radius: 12px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ color: #fff;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.5);
+ user-select: none;
+}
+button {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border: none;
+ color: white;
+ padding: 10px 18px;
+ margin: 5px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 600;
+ transition: all 0.2s;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+button:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
+}
+button:active {
+ transform: translateY(0);
+}
+.info {
+ margin-top: 12px;
+ font-size: 11px;
+ opacity: 0.6;
+ line-height: 1.4;
+}
+</style>
+</head>
+<body>
+<canvas id="canvas"></canvas>
+<div class="controls">
+ <button id="togglePoints">Hide Points</button>
+ <button id="reset">Reset</button>
+ <div class="info">Click anywhere to shatter<br>Cells: <span id="count">0</span></div>
+</div>
+<script>
+const canvas = document.getElementById('canvas');
+const ctx = canvas.getContext('2d');
+let width, height;
+let points = [];
+let showPoints = true;
+let animating = false;
+let animationFrame = 0;
+const ANIMATION_DURATION = 40;
+
+// Initialize with random points
+function init() {
+ resize();
+ // Create initial scattered points
+ for (let i = 0; i < 8; i++) {
+ points.push({
+ x: Math.random() * width * 0.8 + width * 0.1,
+ y: Math.random() * height * 0.8 + height * 0.1,
+ hue: Math.random() * 360,
+ born: -100 // negative means fully grown
+ });
+ }
+ render();
+}
+
+function resize() {
+ width = canvas.width = window.innerWidth;
+ height = canvas.height = window.innerHeight;
+ if (!animating) render();
+}
+
+window.addEventListener('resize', resize);
+
+// Clip polygon to half-plane (closer to p1 than p2)
+function clipPolygon(poly, p1, p2) {
+ if (poly.length === 0) return [];
+ const newPoly = [];
+ const mx = (p1.x + p2.x) / 2;
+ const my = (p1.y + p2.y) / 2;
+ const dx = p2.x - p1.x;
+ const dy = p2.y - p1.y;
+
+ for (let i = 0; i < poly.length; i++) {
+ const curr = poly[i];
+ const next = poly[(i + 1) % poly.length];
+ const currVal = (curr[0] - mx) * dx + (curr[1] - my) * dy;
+ const nextVal = (next[0] - mx) * dx + (next[1] - my) * dy;
+ const currIn = currVal <= 0;
+ const nextIn = nextVal <= 0;
+
+ if (currIn && nextIn) {
+ newPoly.push(next);
+ } else if (currIn && !nextIn) {
+ // Leaving half-plane: add intersection
+ const t = currVal / (currVal - nextVal);
+ if (t >= 0 && t <= 1) {
+ newPoly.push([
+ curr[0] + (next[0] - curr[0]) * t,
+ curr[1] + (next[1] - curr[1]) * t
+ ]);
+ }
+ } else if (!currIn && nextIn) {
+ // Entering half-plane: add intersection then next
+ const t = currVal / (currVal - nextVal);
+ if (t >= 0 && t <= 1) {
+ newPoly.push([
+ curr[0] + (next[0] - curr[0]) * t,
+ curr[1] + (next[1] - curr[1]) * t
+ ]);
+ }
+ newPoly.push(next);
+ }
+ }
+ return newPoly;
+}
+
+// Compute Voronoi cell for point at index
+function computeCell(index, allPoints) {
+ const bounds = [[0, 0], [width, 0], [width, height], [0, height]];
+ let cell = bounds;
+ const p1 = allPoints[index];
+
+ for (let j = 0; j < allPoints.length; j++) {
+ if (index === j) continue;
+ cell = clipPolygon(cell, p1, allPoints[j]);
+ if (cell.length === 0) break;
+ }
+ return cell;
+}
+
+// Calculate approximate max radius from point to cell vertices
+function getCellRadius(cell, cx, cy) {
+ let maxDist = 0;
+ for (const v of cell) {
+ const dx = v[0] - cx;
+ const dy = v[1] - cy;
+ maxDist = Math.max(maxDist, Math.sqrt(dx*dx + dy*dy));
+ }
+ return maxDist || 300;
+}
+
+function render() {
+ ctx.fillStyle = '#050505';
+ ctx.fillRect(0, 0, width, height);
+
+ if (points.length === 0) {
+ document.getElementById('count').textContent = '0';
+ return;
+ }
+
+ // Animation progress for newest point (0 to 1)
+ let growth = 1;
+ if (animating && points.length > 0) {
+ const newest = points[points.length - 1];
+ if (newest.born >= 0) {
+ const age = Date.now() - newest.born;
+ growth = Math.min(1, age / (ANIMATION_DURATION * 16.67)); // 60fps approx
+ if (growth >= 1) animating = false;
+ }
+ }
+
+ // Draw Voronoi cells
+ for (let i = 0; i < points.length; i++) {
+ const cell = computeCell(i, points);
+ if (cell.length < 3) continue;
+
+ const p = points[i];
+
+ // Animation mask for newest cell
+ if (i === points.length - 1 && growth < 1) {
+ ctx.save();
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, getCellRadius(cell, p.x, p.y) * easeOutBack(growth), 0, Math.PI * 2);
+ ctx.clip();
+ }
+
+ // Draw cell
+ ctx.beginPath();
+ ctx.moveTo(cell[0][0], cell[0][1]);
+ for (let j = 1; j < cell.length; j++) {
+ ctx.lineTo(cell[j][0], cell[j][1]);
+ }
+ ctx.closePath();
+
+ // Glass-like gradient fill
+ const angle = p.hue * Math.PI / 180;
+ const grad = ctx.createLinearGradient(
+ p.x - Math.cos(angle) * 100,
+ p.y - Math.sin(angle) * 100,
+ p.x + Math.cos(angle) * 100,
+ p.y + Math.sin(angle) * 100
+ );
+ grad.addColorStop(0, `hsla(${p.hue}, 80%, 60%, 0.4)`);
+ grad.addColorStop(0.5, `hsla(${p.hue + 30}, 70%, 50%, 0.3)`);
+ grad.addColorStop(1, `hsla(${p.hue + 60}, 60%, 40%, 0.5)`);
+
+ ctx.fillStyle = grad;
+ ctx.fill();
+
+ // Edge glow
+ ctx.strokeStyle = `hsla(${p.hue}, 80%, 80%, 0.3)`;
+ ctx.lineWidth = 1.5;
+ ctx.stroke();
+
+ // Inner highlight
+ ctx.strokeStyle = `hsla(${p.hue}, 100%, 90%, 0.1)`;
+ ctx.lineWidth = 0.5;
+ ctx.stroke();
+
+ if (i === points.length - 1 && growth < 1) {
+ ctx.restore();
+
+ // Draw shatter lines during animation
+ ctx.save();
+ ctx.strokeStyle = `hsla(${p.hue}, 100%, 90%, ${1 - growth})`;
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ for (let j = 0; j < cell.length; j++) {
+ const midX = (p.x + cell[j][0]) / 2;
+ const midY = (p.y + cell[j][1]) / 2;
+ ctx.moveTo(p.x, p.y);
+ ctx.lineTo(midX, midY);
+ }
+ ctx.stroke();
+ ctx.restore();
+ }
+ }
+
+ // Draw seed points
+ if (showPoints) {
+ for (const p of points) {
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, 4, 0, Math.PI * 2);
+ ctx.fillStyle = '#fff';
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, 8, 0, Math.PI * 2);
+ ctx.strokeStyle = 'rgba(255,255,255,0.3)';
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ }
+ }
+
+ document.getElementById('count').textContent = points.length;
+
+ if (animating) {
+ requestAnimationFrame(render);
+ }
+}
+
+function easeOutBack(x) {
+ const c1 = 1.70158;
+ const c3 = c1 + 1;
+ return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2);
+}
+
+canvas.addEventListener('click', (e) => {
+ const rect = canvas.getBoundingClientRect();
+ const newPoint = {
+ x: e.clientX - rect.left,
+ y: e.clientY - rect.top,
+ hue: Math.random() * 360,
+ born: Date.now()
+ };
+ points.push(newPoint);
+ animating = true;
+ render();
+});
+
+document.getElementById('togglePoints').addEventListener('click', (e) => {
+ showPoints = !showPoints;
+ e.target.textContent = showPoints ? 'Hide Points' : 'Show Points';
+ if (!animating) render();
+});
+
+document.getElementById('reset').addEventListener('click', () => {
+ points = [];
+ animating = false;
+ init();
+});
+
+init();
+</script>
+</body>
+</html>" title="All-Models — Voronoi Shatter"></iframe></div>
+</body></html>
\ No newline at end of file
diff --git a/data/landings/74df3b61e7ca-hermes3-8b.html b/data/landings/74df3b61e7ca-hermes3-8b.html
new file mode 100644
index 0000000..995704f
--- /dev/null
+++ b/data/landings/74df3b61e7ca-hermes3-8b.html
@@ -0,0 +1,113 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Synthwave Visualizer — built by Hermes3 8B · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>Synthwave Visualizer</h1><span class="by">built by <b>Hermes3 8B</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="<!DOCTYPE html>
+<html>
+<head>
+ <title>Synthwave Music Visualizer</title>
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ background-color: #000;
+ overflow: hidden;
+ }
+
+ canvas {
+ display: block;
+ width: 800px;
+ height: 400px;
+ }
+ </style>
+</head>
+<body>
+ <canvas></canvas>
+
+ <script>
+ const canvas = document.querySelector('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Set up the WebAudio API
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
+
+ const oscillator = audioContext.createOscillator();
+ const gainNode = audioContext.createGain();
+
+ oscillator.connect(gainNode);
+ gainNode.connect(audioContext.destination);
+
+ oscillator.type = 'sawtooth';
+ oscillator.frequency.value = 440; // A4 note
+
+ let isPlaying = false;
+
+ function playMusic() {
+ if (!isPlaying) {
+ isPlaying = true;
+ oscillator.start();
+
+ animate();
+ }
+ }
+
+ function stopMusic() {
+ if (isPlaying) {
+ isPlaying = false;
+ oscillator.stop();
+ cancelAnimationFrame(animationId);
+ }
+ }
+
+ canvas.addEventListener('click', () => {
+ if (isPlaying) {
+ stopMusic();
+ } else {
+ playMusic();
+ }
+ });
+
+ function animate() {
+ animationId = requestAnimationFrame(animate);
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ const currentTime = audioContext.currentTime;
+
+ // Draw the retro sun
+ const sunSize = 100 + Math.sin(currentTime * 0.001) * 20;
+ ctx.beginPath();
+ ctx.arc(canvas.width / 2, 50, sunSize / 2, 0, Math.PI * 2);
+ ctx.fillStyle = `hsl(${currentTime * 150 % 360}, 100%, 50%)`;
+ ctx.fill();
+
+ // Draw the animated perspective grid
+ for (let x = 0; x < canvas.width; x++) {
+ for (let y = 0; y < canvas.height; y++) {
+ const distance = Math.sqrt(Math.pow(x - canvas.width / 2, 2) + Math.pow(y - 50, 2));
+ const angle = Math.atan2(y - 50, x - canvas.width / 2);
+
+ const gridX = canvas.width / 2 + distance * Math.cos(angle + currentTime * 0.01);
+ const gridY = 50 + distance * Math.sin(angle + currentTime * 0.01);
+
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = `rgba(255, 255, 255, ${(distance % 10) / 10})`;
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.lineTo(gridX, gridY);
+ ctx.stroke();
+ }
+ }
+
+ requestAnimationFrame(animate);
+ }
+
+ playMusic(); // Start the music when the page loads
+ </script>
+</body>
+</html>" title="Synthwave Visualizer"></iframe></div>
+</body></html>
\ No newline at end of file
diff --git a/data/landings/7993245ce013-claude-code.html b/data/landings/7993245ce013-claude-code.html
new file mode 100644
index 0000000..b75dd94
--- /dev/null
+++ b/data/landings/7993245ce013-claude-code.html
@@ -0,0 +1,259 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Free-Roster — Emoji Memory Game — built by Claude Opus (Max plan) · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>Free-Roster — Emoji Memory Game</h1><span class="by">built by <b>Claude Opus (Max plan)</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Memory Match</title>
+<style>
+ :root{
+ --bg1:#1e1b4b; --bg2:#312e81; --accent:#a78bfa; --accent2:#f472b6;
+ --card-back:linear-gradient(135deg,#7c3aed,#db2777);
+ --card-face:#ffffff; --matched:#34d399;
+ }
+ *{box-sizing:border-box;margin:0;padding:0}
+ body{
+ min-height:100vh;
+ font-family:'Segoe UI',system-ui,-apple-system,sans-serif;
+ background:linear-gradient(160deg,var(--bg1),var(--bg2));
+ color:#f8fafc;
+ display:flex;flex-direction:column;align-items:center;
+ padding:20px;gap:18px;
+ }
+ h1{
+ font-size:clamp(1.6rem,5vw,2.6rem);
+ font-weight:800;letter-spacing:.5px;
+ background:linear-gradient(90deg,var(--accent),var(--accent2));
+ -webkit-background-clip:text;background-clip:text;color:transparent;
+ text-align:center;
+ }
+ .controls{
+ display:flex;flex-wrap:wrap;gap:12px;align-items:center;justify-content:center;
+ }
+ .seg{
+ display:inline-flex;background:rgba(255,255,255,.08);
+ border-radius:999px;padding:4px;gap:4px;
+ border:1px solid rgba(255,255,255,.12);
+ }
+ .seg button{
+ border:none;background:transparent;color:#e2e8f0;
+ padding:8px 18px;border-radius:999px;cursor:pointer;
+ font-size:.95rem;font-weight:600;transition:.2s;
+ }
+ .seg button.active{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;box-shadow:0 4px 14px rgba(167,139,250,.4)}
+ .btn{
+ border:none;cursor:pointer;font-weight:700;font-size:.95rem;
+ padding:10px 22px;border-radius:999px;color:#fff;
+ background:linear-gradient(135deg,var(--accent),var(--accent2));
+ box-shadow:0 4px 14px rgba(244,114,182,.35);transition:transform .15s,box-shadow .15s;
+ }
+ .btn:hover{transform:translateY(-2px);box-shadow:0 8px 22px rgba(244,114,182,.5)}
+ .btn:active{transform:translateY(0)}
+ .stats{
+ display:flex;gap:26px;font-size:1.05rem;font-weight:600;
+ background:rgba(255,255,255,.07);padding:12px 26px;border-radius:16px;
+ border:1px solid rgba(255,255,255,.1);
+ }
+ .stats span{color:var(--accent);font-variant-numeric:tabular-nums}
+ .board-wrap{position:relative;width:100%;max-width:560px}
+ #board{
+ display:grid;gap:10px;perspective:900px;width:100%;
+ }
+ .card{
+ position:relative;aspect-ratio:1;cursor:pointer;
+ transform-style:preserve-3d;transition:transform .45s cubic-bezier(.4,.2,.2,1);
+ border-radius:14px;
+ }
+ .card.flipped,.card.matched{transform:rotateY(180deg)}
+ .card.matched{cursor:default}
+ .face{
+ position:absolute;inset:0;border-radius:14px;
+ display:flex;align-items:center;justify-content:center;
+ backface-visibility:hidden;-webkit-backface-visibility:hidden;
+ user-select:none;
+ }
+ .back{
+ background:var(--card-back);
+ box-shadow:0 4px 12px rgba(0,0,0,.3), inset 0 0 0 2px rgba(255,255,255,.15);
+ }
+ .back::after{content:"?";font-size:clamp(1.2rem,5vw,2rem);font-weight:800;color:rgba(255,255,255,.85)}
+ .front{
+ background:var(--card-face);color:#111;
+ transform:rotateY(180deg);
+ font-size:clamp(1.6rem,7vw,2.8rem);
+ box-shadow:0 4px 12px rgba(0,0,0,.3);
+ }
+ .card.matched .front{background:var(--matched);animation:pop .4s ease}
+ @keyframes pop{0%{transform:rotateY(180deg) scale(1)}50%{transform:rotateY(180deg) scale(1.12)}100%{transform:rotateY(180deg) scale(1)}}
+ .overlay{
+ position:fixed;inset:0;background:rgba(15,12,41,.8);
+ display:flex;align-items:center;justify-content:center;
+ opacity:0;pointer-events:none;transition:opacity .35s;backdrop-filter:blur(4px);z-index:10;
+ }
+ .overlay.show{opacity:1;pointer-events:auto}
+ .win-card{
+ background:linear-gradient(160deg,#312e81,#1e1b4b);
+ border:1px solid rgba(255,255,255,.15);
+ padding:36px 40px;border-radius:24px;text-align:center;
+ box-shadow:0 20px 60px rgba(0,0,0,.5);transform:scale(.85);transition:transform .35s;
+ max-width:90vw;
+ }
+ .overlay.show .win-card{transform:scale(1)}
+ .win-card h2{font-size:2rem;margin-bottom:8px}
+ .win-card .emoji{font-size:3rem;margin-bottom:6px}
+ .win-card p{color:#cbd5e1;margin-bottom:6px;font-size:1.05rem}
+ .win-card p b{color:var(--accent)}
+ .win-card .btn{margin-top:18px}
+</style>
+</head>
+<body>
+ <h1>🧠 Memory Match</h1>
+
+ <div class="controls">
+ <div class="seg" id="difficulty">
+ <button data-size="4" class="active">4 × 4</button>
+ <button data-size="6">6 × 6</button>
+ </div>
+ <button class="btn" id="restart">↻ Restart</button>
+ </div>
+
+ <div class="stats">
+ <div>Moves: <span id="moves">0</span></div>
+ <div>Time: <span id="time">0:00</span></div>
+ <div>Pairs: <span id="pairs">0</span></div>
+ </div>
+
+ <div class="board-wrap">
+ <div id="board"></div>
+ </div>
+
+ <div class="overlay" id="overlay">
+ <div class="win-card">
+ <div class="emoji">🎉</div>
+ <h2>You Won!</h2>
+ <p>Solved in <b id="winMoves">0</b> moves</p>
+ <p>Time: <b id="winTime">0:00</b></p>
+ <button class="btn" id="playAgain">Play Again</button>
+ </div>
+ </div>
+
+<script>
+ const EMOJIS = ['🍕','🎸','🚀','🐱','🌈','⚽','🎮','🍩','🦊','🌵','🐙','🎈',
+ '🍔','🦄','🎧','🍦','🐳','🔥','🌸','🎲','🧩','🍭','🐢','🎁'];
+
+ const board = document.getElementById('board');
+ const movesEl = document.getElementById('moves');
+ const timeEl = document.getElementById('time');
+ const pairsEl = document.getElementById('pairs');
+ const overlay = document.getElementById('overlay');
+
+ let size = 4, deck = [], first = null, lock = false;
+ let moves = 0, matched = 0, totalPairs = 0;
+ let timer = null, seconds = 0, started = false;
+
+ function fmt(s){ const m = Math.floor(s/60); const r = s%60; return m+':'+String(r).padStart(2,'0'); }
+
+ function shuffle(a){
+ for(let i=a.length-1;i>0;i--){
+ const j = Math.floor(Math.random()*(i+1));
+ [a[i],a[j]]=[a[j],a[i]];
+ }
+ return a;
+ }
+
+ function startTimer(){
+ if(started) return;
+ started = true;
+ timer = setInterval(()=>{ seconds++; timeEl.textContent = fmt(seconds); }, 1000);
+ }
+
+ function stopTimer(){ clearInterval(timer); }
+
+ function newGame(){
+ stopTimer();
+ started = false; seconds = 0; moves = 0; matched = 0; first = null; lock = false;
+ totalPairs = (size*size)/2;
+ movesEl.textContent = '0';
+ timeEl.textContent = '0:00';
+ pairsEl.textContent = '0';
+ overlay.classList.remove('show');
+
+ const chosen = shuffle([...EMOJIS]).slice(0, totalPairs);
+ deck = shuffle([...chosen, ...chosen]);
+
+ board.style.gridTemplateColumns = `repeat(${size}, 1fr)`;
+ board.innerHTML = '';
+
+ deck.forEach((emoji)=>{
+ const card = document.createElement('div');
+ card.className = 'card';
+ card.dataset.emoji = emoji;
+ card.innerHTML = `<div class="face back"></div><div class="face front">${emoji}</div>`;
+ card.addEventListener('click', ()=>flip(card));
+ board.appendChild(card);
+ });
+ }
+
+ function flip(card){
+ if(lock) return;
+ if(card.classList.contains('flipped') || card.classList.contains('matched')) return;
+
+ startTimer();
+ card.classList.add('flipped');
+
+ if(!first){ first = card; return; }
+
+ // second card
+ moves++;
+ movesEl.textContent = moves;
+
+ if(first.dataset.emoji === card.dataset.emoji){
+ first.classList.add('matched');
+ card.classList.add('matched');
+ first = null;
+ matched++;
+ pairsEl.textContent = matched;
+ if(matched === totalPairs) win();
+ } else {
+ lock = true;
+ const a = first, b = card;
+ first = null;
+ setTimeout(()=>{
+ a.classList.remove('flipped');
+ b.classList.remove('flipped');
+ lock = false;
+ }, 800);
+ }
+ }
+
+ function win(){
+ stopTimer();
+ document.getElementById('winMoves').textContent = moves;
+ document.getElementById('winTime').textContent = fmt(seconds);
+ setTimeout(()=>overlay.classList.add('show'), 500);
+ }
+
+ document.getElementById('difficulty').addEventListener('click', (e)=>{
+ const b = e.target.closest('button');
+ if(!b) return;
+ document.querySelectorAll('#difficulty button').forEach(x=>x.classList.remove('active'));
+ b.classList.add('active');
+ size = parseInt(b.dataset.size, 10);
+ newGame();
+ });
+
+ document.getElementById('restart').addEventListener('click', newGame);
+ document.getElementById('playAgain').addEventListener('click', newGame);
+
+ newGame();
+</script>
+</body>
+</html>" title="Free-Roster — Emoji Memory Game"></iframe></div>
+</body></html>
\ No newline at end of file
diff --git a/data/landings/c76287fb731d-qwen3-14b.html b/data/landings/c76287fb731d-qwen3-14b.html
new file mode 100644
index 0000000..73e5581
--- /dev/null
+++ b/data/landings/c76287fb731d-qwen3-14b.html
@@ -0,0 +1,195 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Generative Art Canvas — built by Qwen3 14B · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>Generative Art Canvas</h1><span class="by">built by <b>Qwen3 14B</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Flow Field Art</title>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ background: #000;
+ }
+ canvas {
+ display: block;
+ }
+ #controls {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 10;
+ color: white;
+ background: rgba(0,0,0,0.5);
+ padding: 10px;
+ border-radius: 5px;
+ }
+ button {
+ margin: 5px;
+ padding: 8px 12px;
+ font-size: 14px;
+ background: #444;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ button:hover {
+ background: #666;
+ }
+ </style>
+</head>
+<body>
+ <div id="controls">
+ <button onclick="resetField()">New Field</button>
+ <button onclick="saveCanvas()">Save as PNG</button>
+ </div>
+ <canvas id="canvas"></canvas>
+ <script>
+ const canvas = document.getElementById('canvas');
+ const ctx = canvas.getContext('2d');
+ let width, height;
+ let particles = [];
+ let palette;
+
+ function resizeCanvas() {
+ width = canvas.width = window.innerWidth;
+ height = canvas.height = window.innerHeight;
+ }
+ window.addEventListener('resize', resizeCanvas);
+ resizeCanvas();
+
+ function resetField() {
+ palette = generatePalette();
+ particles = [];
+ for (let i = 0; i < 1000; i++) {
+ particles.push(new Particle());
+ }
+ }
+
+ function generatePalette() {
+ const hues = [];
+ for (let i = 0; i < 10; i++) {
+ hues.push(Math.random() * 360);
+ }
+ return hues;
+ }
+
+ function mapValue(value, min1, max1, min2, max2) {
+ return (value - min1) / (max1 - min1) * (max2 - min2) + min2;
+ }
+
+ function noise(x, y) {
+ const nx = x / 100;
+ const ny = y / 100;
+ const sinx = Math.sin(nx);
+ const siny = Math.sin(ny);
+ return Math.sin(sinx + siny) * 0.5 + 0.5;
+ }
+
+ function getDirection(x, y) {
+ const step = 10;
+ const dx = noise(x + step, y) - noise(x - step, y);
+ const dy = noise(x, y + step) - noise(x, y - step);
+ const angle = Math.atan2(dy, dx);
+ return { angle, magnitude: 1 };
+ }
+
+ function lerpColor(a, b, t) {
+ return [
+ a[0] + t * (b[0] - a[0]),
+ a[1] + t * (b[1] - a[1]),
+ a[2] + t * (b[2] - a[2])
+ ];
+ }
+
+ function getColor(x, y) {
+ const nx = x / 100;
+ const ny = y / 100;
+ const t = (Math.sin(nx) + Math.cos(ny)) * 0.5 + 0.5;
+ const hue = palette[Math.floor(t * palette.length)];
+ return [hue, 1, 1];
+ }
+
+ function Particle() {
+ this.x = Math.random() * width;
+ this.y = Math.random() * height;
+ this.vx = 0;
+ this.vy = 0;
+ this.alpha = 1;
+ }
+
+ Particle.prototype.update = function() {
+ const { angle } = getDirection(this.x, this.y);
+ this.vx = Math.cos(angle);
+ this.vy = Math.sin(angle);
+ this.x += this.vx;
+ this.y += this.vy;
+ this.alpha -= 0.005;
+ if (this.alpha <= 0) {
+ this.x = Math.random() * width;
+ this.y = Math.random() * height;
+ this.alpha = 1;
+ }
+ };
+
+ Particle.prototype.draw = function(ctx) {
+ const color = getColor(this.x, this.y);
+ const [h, s, l] = color;
+ const rgb = hsvToRgb(h, s, l);
+ ctx.fillStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${this.alpha})`;
+ ctx.fillRect(this.x, this.y, 2, 2);
+ };
+
+ function hsvToRgb(h, s, l) {
+ let r, g, b;
+ if (s === 0) {
+ r = g = b = l;
+ } else {
+ const hue2rgb = function (p, q, t) {
+ if (t < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1/6) return p + (q - p) * 6 * t;
+ if (t < 1/2) return q;
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+ return p;
+ };
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ const p = 2 * l - q;
+ r = hue2rgb(p, q, h + 1/3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1/3);
+ }
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
+ }
+
+ function draw() {
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
+ ctx.fillRect(0, 0, width, height);
+ for (let i = 0; i < particles.length; i++) {
+ particles[i].update();
+ particles[i].draw(ctx);
+ }
+ requestAnimationFrame(draw);
+ }
+
+ resetField();
+ draw();
+
+ function saveCanvas() {
+ const link = document.createElement('a');
+ link.download = 'flow-field-art.png';
+ link.href = canvas.toDataURL();
+ link.click();
+ }
+ </script>
+</body>
+</html>" title="Generative Art Canvas"></iframe></div>
+</body></html>
\ No newline at end of file
← 5917c15 night-loop: cycle 23:19 — judged=4a6e2cfce746 · (backlog 2 —
·
back to Model Arena
·
night-loop: cycle 23:39 — judged=e85044630905 · FIRED idx=3/ 435b51f →