[object Object]

← 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

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="&lt;!DOCTYPE html&gt;
+&lt;html lang=&quot;en&quot;&gt;
+&lt;head&gt;
+&lt;meta charset=&quot;UTF-8&quot;&gt;
+&lt;title&gt;Voronoi Shatter&lt;/title&gt;
+&lt;style&gt;
+    html, body {
+        margin: 0;
+        padding: 0;
+        overflow: hidden;
+        background: #05070b;
+        color: #eee;
+        font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, 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;
+    }
+&lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;div id=&quot;ui&quot;&gt;
+    &lt;button id=&quot;togglePoints&quot;&gt;Hide Seed Points&lt;/button&gt;
+    &lt;span&gt;Click: add seed &amp;amp; animate shatter&lt;/span&gt;
+&lt;/div&gt;
+&lt;canvas id=&quot;c&quot;&gt;&lt;/canvas&gt;
+&lt;script&gt;
+(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', () =&gt; {
+        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 = () =&gt; {
+        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 &lt; INITIAL_SEEDS; i++) {
+            seeds.push(randomSeed());
+        }
+    }
+
+    initSeeds();
+
+    // Lloyd relaxation (one iteration) for nicer cells
+    function relax(points) {
+        const sites = points.map(p =&gt; ({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 &lt; height; y += step) {
+            for (let x = 0; x &lt; width; x += step) {
+                let minD = Infinity, idx = 0;
+                for (let i = 0; i &lt; sites.length; i++) {
+                    const dx = x - sites[i].x;
+                    const dy = y - sites[i].y;
+                    const d = dx*dx + dy*dy;
+                    if (d &lt; 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 &gt; 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 &lt; polygons.length; i++) polygons[i] = [];
+
+        const step = 12; // sampling step; balance speed vs detail
+        for (let y = 0; y &lt;= height; y += step) {
+            for (let x = 0; x &lt;= width; x += step) {
+                let minD = Infinity, idx = 0;
+                for (let i = 0; i &lt; points.length; i++) {
+                    const dx = x - points[i].x;
+                    const dy = y - points[i].y;
+                    const d = dx*dx + dy*dy;
+                    if (d &lt; 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 &lt; 3) return pts.slice();
+            // sort by x then y
+            pts = pts.slice().sort((a, b) =&gt; a.x === b.x ? a.y - b.y : a.x - b.x);
+            const cross = (o, a, b) =&gt; (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 &gt;= 2 &amp;&amp; cross(lower[lower.length-2], lower[lower.length-1], p) &lt;= 0)
+                    lower.pop();
+                lower.push(p);
+            }
+            const upper = [];
+            for (let i = pts.length - 1; i &gt;= 0; i--) {
+                const p = pts[i];
+                while (upper.length &gt;= 2 &amp;&amp; cross(upper[upper.length-2], upper[upper.length-1], p) &lt;= 0)
+                    upper.pop();
+                upper.push(p);
+            }
+            upper.pop();
+            lower.pop();
+            return lower.concat(upper);
+        }
+
+        const hulls = [];
+        for (let i = 0; i &lt; 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 &lt; 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 &lt; 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 &gt; 0) {
+            t = Math.min(1, (now - animStart) / ANIM_DURATION);
+            t = easeOutCubic(t);
+            if (t &gt;= 1) animStart = 0;
+        }
+        const interpSeeds = interpolateSeeds(t);
+        const interpPolys = computeCellPolygons(interpSeeds);
+
+        for (let i = 0; i &lt; 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 &lt; 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 &lt; 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 =&gt; {
+        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();
+    });
+
+})();
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" 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="&lt;!DOCTYPE html&gt;
+&lt;html lang=&quot;en&quot;&gt;
+&lt;head&gt;
+&lt;meta charset=&quot;UTF-8&quot;&gt;
+&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
+&lt;title&gt;Voronoi Shatter&lt;/title&gt;
+&lt;style&gt;
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  html, body {
+    width: 100%; height: 100%; overflow: hidden;
+    background: #0a0a0f;
+    font-family: &quot;Segoe UI&quot;, 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;
+  }
+&lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;canvas id=&quot;c&quot;&gt;&lt;/canvas&gt;
+&lt;div class=&quot;hud&quot;&gt;
+  &lt;h1&gt;Voronoi Shatter&lt;/h1&gt;
+  &lt;p&gt;Click to add a seed · cells re-tessellate&lt;/p&gt;
+&lt;/div&gt;
+&lt;div class=&quot;controls&quot;&gt;
+  &lt;button id=&quot;toggleSeeds&quot; class=&quot;active&quot;&gt;Seed Points: On&lt;/button&gt;
+  &lt;button id=&quot;reset&quot;&gt;Reset&lt;/button&gt;
+&lt;/div&gt;
+&lt;script&gt;
+(function () {
+  const canvas = document.getElementById(&quot;c&quot;);
+  const ctx = canvas.getContext(&quot;2d&quot;);
+  const toggleBtn = document.getElementById(&quot;toggleSeeds&quot;);
+  const resetBtn = document.getElementById(&quot;reset&quot;);
+
+  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 + &quot;px&quot;;
+    canvas.style.height = H + &quot;px&quot;;
+    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 &lt; 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) &lt; 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 &lt; circ.r2 - 1e-9;
+  }
+
+  // Bowyer–Watson Delaunay
+  function delaunay(pts) {
+    if (pts.length &lt; 3) return [];
+    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
+    for (const p of pts) {
+      if (p.x &lt; minX) minX = p.x;
+      if (p.y &lt; minY) minY = p.y;
+      if (p.x &gt; maxX) maxX = p.x;
+      if (p.y &gt; 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 &amp;&amp; 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 &lt; edges.length; i++) {
+        let shared = false;
+        for (let j = 0; j &lt; edges.length; j++) {
+          if (i === j) continue;
+          const e1 = edges[i], e2 = edges[j];
+          if ((e1[0] === e2[0] &amp;&amp; e1[1] === e2[1]) ||
+              (e1[0] === e2[1] &amp;&amp; e1[1] === e2[0])) {
+            shared = true;
+            break;
+          }
+        }
+        if (!shared) unique.push(edges[i]);
+      }
+      tris = tris.filter(t =&gt; bad.indexOf(t) === -1);
+      for (const e of unique) {
+        tris.push({ a: e[0], b: e[1], c: p });
+      }
+    }
+
+    return tris.filter(t =&gt;
+      !t.a._s &amp;&amp; !t.b._s &amp;&amp; !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 -&gt; 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 =&gt; ({ 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 &gt;= 0  (on site side)
+        poly = clipPoly(poly, mx, my, nx, ny);
+        if (poly.length &lt; 3) break;
+      }
+      if (poly.length &gt;= 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 &lt; n; i++) {
+      const cur = poly[i];
+      const prev = poly[(i + n - 1) % n];
+      const curIn = (cur.x - px) * nx + (cur.y - py) * ny &gt;= -1e-9;
+      const prevIn = (prev.x - px) * nx + (prev.y - py) * ny &gt;= -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) &lt; 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 &lt; 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 &lt; 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 &gt; 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) =&gt;
+      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 &lt; 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 &lt; 1e-6) {
+      return Array.from({ length: n }, () =&gt; ({ x: cx, y: cy }));
+    }
+    const out = [];
+    for (let i = 0; i &lt; n; i++) {
+      const t = (i / n) * total;
+      let seg = segs[segs.length - 1];
+      for (const s of segs) {
+        if (t &gt;= s.acc &amp;&amp; t &lt;= s.acc + s.len + 1e-9) { seg = s; break; }
+      }
+      const lt = seg.len &lt; 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 =&gt; ({
+          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) =&gt; ({
+        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 &gt;= 1) {
+        animating = false;
+        animT = 1;
+        currentCells = targetCells;
+      } else {
+        currentCells = morphCells(prevCells, targetCells, easeOutCubic(animT));
+      }
+    }
+
+    // Background
+    ctx.fillStyle = &quot;#0a0a0f&quot;;
+    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, &quot;rgba(20,22,40,0.4)&quot;);
+    vg.addColorStop(1, &quot;rgba(5,5,10,0)&quot;);
+    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 &amp;&amp; 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 &amp;&amp; currentCells.length) {
+      prevCells = currentCells.map(c =&gt; ({
+        site: c.site,
+        poly: c.poly.map(p =&gt; ({ x: p.x, y: p.y }))
+      }));
+      targetCells = next;
+      animT = 0;
+      animStart = performance.now();
+      animating = true;
+    } else {
+      currentCells = next;
+      targetCells = next;
+      animating = false;
+    }
+  }
+
+  canvas.addEventListener(&quot;click&quot;, (e) =&gt; {
+    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(&quot;click&quot;, () =&gt; {
+    showSeeds = !showSeeds;
+    toggleBtn.textContent = showSeeds ? &quot;Seed Points: On&quot; : &quot;Seed Points: Off&quot;;
+    toggleBtn.classList.toggle(&quot;active&quot;, showSeeds);
+  });
+
+  resetBtn.addEventListener(&quot;click&quot;, () =&gt; {
+    seedInitial(8 + Math.floor(Math.random() * 5));
+    recompute(true);
+  });
+
+  window.addEventListener(&quot;resize&quot;, () =&gt; {
+    resize();
+    recompute(false);
+  });
+
+  resize();
+  seedInitial(10);
+  recompute(false);
+  requestAnimationFrame(render);
+})();
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" 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="&lt;!DOCTYPE html&gt;
+&lt;html lang=&quot;en&quot;&gt;
+&lt;head&gt;
+&lt;meta charset=&quot;UTF-8&quot;&gt;
+&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
+&lt;title&gt;Interactive Voronoi Shatter&lt;/title&gt;
+&lt;style&gt;
+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;
+}
+&lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;canvas id=&quot;canvas&quot;&gt;&lt;/canvas&gt;
+&lt;div class=&quot;controls&quot;&gt;
+  &lt;button id=&quot;togglePoints&quot;&gt;Hide Points&lt;/button&gt;
+  &lt;button id=&quot;reset&quot;&gt;Reset&lt;/button&gt;
+  &lt;div class=&quot;info&quot;&gt;Click anywhere to shatter&lt;br&gt;Cells: &lt;span id=&quot;count&quot;&gt;0&lt;/span&gt;&lt;/div&gt;
+&lt;/div&gt;
+&lt;script&gt;
+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 &lt; 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 &lt; 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 &lt;= 0;
+    const nextIn = nextVal &lt;= 0;
+    
+    if (currIn &amp;&amp; nextIn) {
+      newPoly.push(next);
+    } else if (currIn &amp;&amp; !nextIn) {
+      // Leaving half-plane: add intersection
+      const t = currVal / (currVal - nextVal);
+      if (t &gt;= 0 &amp;&amp; t &lt;= 1) {
+        newPoly.push([
+          curr[0] + (next[0] - curr[0]) * t,
+          curr[1] + (next[1] - curr[1]) * t
+        ]);
+      }
+    } else if (!currIn &amp;&amp; nextIn) {
+      // Entering half-plane: add intersection then next
+      const t = currVal / (currVal - nextVal);
+      if (t &gt;= 0 &amp;&amp; t &lt;= 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 &lt; 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 &amp;&amp; points.length &gt; 0) {
+    const newest = points[points.length - 1];
+    if (newest.born &gt;= 0) {
+      const age = Date.now() - newest.born;
+      growth = Math.min(1, age / (ANIMATION_DURATION * 16.67)); // 60fps approx
+      if (growth &gt;= 1) animating = false;
+    }
+  }
+  
+  // Draw Voronoi cells
+  for (let i = 0; i &lt; points.length; i++) {
+    const cell = computeCell(i, points);
+    if (cell.length &lt; 3) continue;
+    
+    const p = points[i];
+    
+    // Animation mask for newest cell
+    if (i === points.length - 1 &amp;&amp; growth &lt; 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 &lt; 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 &amp;&amp; growth &lt; 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 &lt; 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) =&gt; {
+  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) =&gt; {
+  showPoints = !showPoints;
+  e.target.textContent = showPoints ? 'Hide Points' : 'Show Points';
+  if (!animating) render();
+});
+
+document.getElementById('reset').addEventListener('click', () =&gt; {
+  points = [];
+  animating = false;
+  init();
+});
+
+init();
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" 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="&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+  &lt;title&gt;Synthwave Music Visualizer&lt;/title&gt;
+  &lt;style&gt;
+    body {
+      margin: 0;
+      padding: 0;
+      background-color: #000;
+      overflow: hidden;
+    }
+
+    canvas {
+      display: block;
+      width: 800px;
+      height: 400px;
+    }
+  &lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+  &lt;canvas&gt;&lt;/canvas&gt;
+
+  &lt;script&gt;
+    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', () =&gt; {
+      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 &lt; canvas.width; x++) {
+        for (let y = 0; y &lt; 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
+  &lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" 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="&lt;!DOCTYPE html&gt;
+&lt;html lang=&quot;en&quot;&gt;
+&lt;head&gt;
+&lt;meta charset=&quot;UTF-8&quot;&gt;
+&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
+&lt;title&gt;Memory Match&lt;/title&gt;
+&lt;style&gt;
+  :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:&quot;?&quot;;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}
+&lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+  &lt;h1&gt;🧠 Memory Match&lt;/h1&gt;
+
+  &lt;div class=&quot;controls&quot;&gt;
+    &lt;div class=&quot;seg&quot; id=&quot;difficulty&quot;&gt;
+      &lt;button data-size=&quot;4&quot; class=&quot;active&quot;&gt;4 × 4&lt;/button&gt;
+      &lt;button data-size=&quot;6&quot;&gt;6 × 6&lt;/button&gt;
+    &lt;/div&gt;
+    &lt;button class=&quot;btn&quot; id=&quot;restart&quot;&gt;↻ Restart&lt;/button&gt;
+  &lt;/div&gt;
+
+  &lt;div class=&quot;stats&quot;&gt;
+    &lt;div&gt;Moves: &lt;span id=&quot;moves&quot;&gt;0&lt;/span&gt;&lt;/div&gt;
+    &lt;div&gt;Time: &lt;span id=&quot;time&quot;&gt;0:00&lt;/span&gt;&lt;/div&gt;
+    &lt;div&gt;Pairs: &lt;span id=&quot;pairs&quot;&gt;0&lt;/span&gt;&lt;/div&gt;
+  &lt;/div&gt;
+
+  &lt;div class=&quot;board-wrap&quot;&gt;
+    &lt;div id=&quot;board&quot;&gt;&lt;/div&gt;
+  &lt;/div&gt;
+
+  &lt;div class=&quot;overlay&quot; id=&quot;overlay&quot;&gt;
+    &lt;div class=&quot;win-card&quot;&gt;
+      &lt;div class=&quot;emoji&quot;&gt;🎉&lt;/div&gt;
+      &lt;h2&gt;You Won!&lt;/h2&gt;
+      &lt;p&gt;Solved in &lt;b id=&quot;winMoves&quot;&gt;0&lt;/b&gt; moves&lt;/p&gt;
+      &lt;p&gt;Time: &lt;b id=&quot;winTime&quot;&gt;0:00&lt;/b&gt;&lt;/p&gt;
+      &lt;button class=&quot;btn&quot; id=&quot;playAgain&quot;&gt;Play Again&lt;/button&gt;
+    &lt;/div&gt;
+  &lt;/div&gt;
+
+&lt;script&gt;
+  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&gt;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(()=&gt;{ 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)=&gt;{
+      const card = document.createElement('div');
+      card.className = 'card';
+      card.dataset.emoji = emoji;
+      card.innerHTML = `&lt;div class=&quot;face back&quot;&gt;&lt;/div&gt;&lt;div class=&quot;face front&quot;&gt;${emoji}&lt;/div&gt;`;
+      card.addEventListener('click', ()=&gt;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(()=&gt;{
+        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(()=&gt;overlay.classList.add('show'), 500);
+  }
+
+  document.getElementById('difficulty').addEventListener('click', (e)=&gt;{
+    const b = e.target.closest('button');
+    if(!b) return;
+    document.querySelectorAll('#difficulty button').forEach(x=&gt;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();
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" 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="&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+  &lt;meta charset=&quot;UTF-8&quot;&gt;
+  &lt;title&gt;Flow Field Art&lt;/title&gt;
+  &lt;style&gt;
+    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;
+    }
+  &lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+  &lt;div id=&quot;controls&quot;&gt;
+    &lt;button onclick=&quot;resetField()&quot;&gt;New Field&lt;/button&gt;
+    &lt;button onclick=&quot;saveCanvas()&quot;&gt;Save as PNG&lt;/button&gt;
+  &lt;/div&gt;
+  &lt;canvas id=&quot;canvas&quot;&gt;&lt;/canvas&gt;
+  &lt;script&gt;
+    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 &lt; 1000; i++) {
+        particles.push(new Particle());
+      }
+    }
+
+    function generatePalette() {
+      const hues = [];
+      for (let i = 0; i &lt; 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 &lt;= 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 &lt; 0) t += 1;
+          if (t &gt; 1) t -= 1;
+          if (t &lt; 1/6) return p + (q - p) * 6 * t;
+          if (t &lt; 1/2) return q;
+          if (t &lt; 2/3) return p + (q - p) * (2/3 - t) * 6;
+          return p;
+        };
+        const q = l &lt; 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 &lt; 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();
+    }
+  &lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" 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 →