← back to Model Arena
Add Games Arcade: /arcade gallery + /api/arcade auto-discovery of crowned game challenges, /game/:slug standalone champions, DW+AA idea pool + all-night loop harness
0e921bc9edc4daf97b45971e49e695e0cf6ebe2a · 2026-07-24 23:18:24 -0700 · Steve Abrams
Files touched
A data/arcade-games.jsonA data/artifacts/4a6e2cfce746/gemma3-12b.htmlA data/artifacts/4a6e2cfce746/gemma3-12b.pngA data/artifacts/4a6e2cfce746/hermes3-8b.htmlA data/artifacts/4a6e2cfce746/hermes3-8b.pngA data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.htmlA data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.pngA data/artifacts/4a6e2cfce746/qwen25-7b.htmlA data/artifacts/4a6e2cfce746/qwen25-7b.pngA data/artifacts/4a6e2cfce746/qwen3-14b.htmlA data/artifacts/4a6e2cfce746/qwen3-14b.pngM data/challenges.jsonA fleet-defender.htmlA idea-run/.dw-aa-pointerA idea-run/dw-aa-challenges.txtA idea-run/run-next.shA public/arcade.htmlR100 star-clock.html public/games/star-clock.htmlR100 tetris.html public/games/tetris.htmlM public/index.htmlM server.js
Diff
commit 0e921bc9edc4daf97b45971e49e695e0cf6ebe2a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jul 24 23:18:24 2026 -0700
Add Games Arcade: /arcade gallery + /api/arcade auto-discovery of crowned game challenges, /game/:slug standalone champions, DW+AA idea pool + all-night loop harness
---
data/arcade-games.json | 6 +
data/artifacts/4a6e2cfce746/gemma3-12b.html | 297 +++++++++++++
data/artifacts/4a6e2cfce746/gemma3-12b.png | Bin 0 -> 9726 bytes
data/artifacts/4a6e2cfce746/hermes3-8b.html | 282 ++++++++++++
data/artifacts/4a6e2cfce746/hermes3-8b.png | Bin 0 -> 8442 bytes
data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.html | 209 +++++++++
data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.png | Bin 0 -> 6338 bytes
data/artifacts/4a6e2cfce746/qwen25-7b.html | 98 +++++
data/artifacts/4a6e2cfce746/qwen25-7b.png | Bin 0 -> 5368 bytes
data/artifacts/4a6e2cfce746/qwen3-14b.html | 315 +++++++++++++
data/artifacts/4a6e2cfce746/qwen3-14b.png | Bin 0 -> 14748 bytes
data/challenges.json | 223 ++++++++++
fleet-defender.html | 490 +++++++++++++++++++++
idea-run/.dw-aa-pointer | 1 +
idea-run/dw-aa-challenges.txt | 16 +
idea-run/run-next.sh | 28 ++
public/arcade.html | 146 ++++++
star-clock.html => public/games/star-clock.html | 0
tetris.html => public/games/tetris.html | 0
public/index.html | 1 +
server.js | 58 +++
21 files changed, 2170 insertions(+)
diff --git a/data/arcade-games.json b/data/arcade-games.json
new file mode 100644
index 0000000..7198b05
--- /dev/null
+++ b/data/arcade-games.json
@@ -0,0 +1,6 @@
+{
+ "featured": [
+ { "slug": "tetris", "title": "Tetris", "credit": "claude-code", "note": "Arena champion — All-Models Tetris" },
+ { "slug": "star-clock", "title": "Constellation Clock", "credit": "claude-code", "note": "Arena champion — Free-Roster Constellation Clock" }
+ ]
+}
diff --git a/data/artifacts/4a6e2cfce746/gemma3-12b.html b/data/artifacts/4a6e2cfce746/gemma3-12b.html
new file mode 100644
index 0000000..4a9acf0
--- /dev/null
+++ b/data/artifacts/4a6e2cfce746/gemma3-12b.html
@@ -0,0 +1,297 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Fleet Defender - AI Command Center</title>
+ <style>
+ body {
+ margin: 0;
+ overflow: hidden;
+ background-color: navy;
+ font-family: monospace;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ }
+
+ #gameCanvas {
+ border: 1px solid cyan;
+ background-color: navy; /* Ensure canvas background is navy */
+ }
+
+ #hud {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ font-size: 16px;
+ color: cyan;
+ text-shadow: 2px 2px 4px black;
+ }
+
+ #gameoverScreen {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: rgba(0, 0, 60, 0.8);
+ padding: 20px;
+ border: 2px solid cyan;
+ text-align: center;
+ display: none; /* Hidden initially */
+ }
+
+ </style>
+</head>
+<body>
+ <div id="hud">Score: <span id="score">0</span> | Wave: <span id="wave">1</span> | Lives: <span id="lives">3</span></div>
+ <canvas id="gameCanvas" width="640" height="480"></canvas>
+
+ <div id="gameoverScreen">
+ <h1>Fleet Defender - Game Over</h1>
+ <p id="finalScore">Your Score: <span id="gameOverScore">0</span></p>
+ <button onclick="restartGame()">Restart</button>
+ </div>
+
+
+ <script>
+ const canvas = document.getElementById('gameCanvas');
+ const ctx = canvas.getContext('2d');
+ ctx.canvas.width = 640;
+ ctx.canvas.height = 480;
+
+ let score = 0;
+ let wave = 1;
+ let lives = 3;
+
+ // Player Ship
+ const player = {
+ x: canvas.width / 2,
+ y: canvas.height - 50,
+ width: 30,
+ height: 20,
+ speed: 5,
+ color: 'cyan'
+ };
+
+ // Bug Enemies
+ const bugs = [];
+
+ // Projectiles (Player Shots)
+ const projectiles = [];
+
+ // Particle Effects
+ const particles = [];
+
+
+ function update() {
+ if(lives <= 0){
+ showGameOver();
+ return;
+ }
+ // Player Movement
+ if (keys['ArrowLeft']) player.x -= player.speed;
+ if (keys['ArrowRight']) player.x += player.speed;
+
+ player.x = Math.max(0, Math.min(player.x, canvas.width - player.width));
+
+
+ // Bug Spawning
+ if (Math.random() < 0.01 * wave) { // Increase spawn rate with wave
+ bugs.push({
+ x: Math.random() * canvas.width,
+ y: 0,
+ width: 20,
+ height: 20,
+ speed: 1 + wave * 0.5, //Increase speed with wave
+ color: 'red'
+ });
+ }
+
+ // Update Bugs
+ for (let i = bugs.length - 1; i >= 0; i--) {
+ bugs[i].y += bugs[i].speed;
+ if (bugs[i].y > canvas.height) {
+ bugs.splice(i, 1);
+ lives--;
+ updateLivesDisplay();
+ }
+ }
+
+ // Update Projectiles
+ for (let i = projectiles.length - 1; i >= 0; i--) {
+ projectiles[i].y -= 8; //projectile speed
+ if (projectiles[i].y < 0) {
+ projectiles.splice(i, 1);
+ }
+ }
+
+ // Collision Detection - Player/Bug
+ for (let i = bugs.length - 1; i >= 0; i--) {
+ if (
+ player.x < bugs[i].x + bugs[i].width &&
+ player.x + player.width > bugs[i].x &&
+ player.y < bugs[i].y + bugs[i].height &&
+ player.y + player.height > bugs[i].y
+ ) {
+ bugs.splice(i, 1);
+ lives--;
+ updateLivesDisplay();
+ }
+ }
+
+ // Collision Detection - Projectile/Bug
+ for (let i = projectiles.length - 1; i >= 0; i--) {
+ for (let j = bugs.length - 1; j >= 0; j--) {
+ if (
+ projectiles[i].x < bugs[j].x + bugs[j].width &&
+ projectiles[i].x + projectiles[i].width > bugs[j].x &&
+ projectiles[i].y < bugs[j].y + bugs[j].height &&
+ projectiles[i].y + projectiles[i].height > bugs[j].y
+ ) {
+ // Collision!
+ score += 10 * wave;
+ updateScoreDisplay();
+
+ // Create Explosion Particles
+ const numParticles = 20;
+ for (let k = 0; k < numParticles; k++) {
+ particles.push({
+ x: bugs[j].x + Math.random() * bugs[j].width,
+ y: bugs[j].y + Math.random() * bugs[j].height,
+ size: Math.random() * 5,
+ color: 'white',
+ alpha: 1
+ });
+ }
+
+ bugs.splice(j, 1);
+ projectiles.splice(i, 1);
+ break; // Important to break inner loop after collision
+ }
+ }
+ }
+
+ // Update Particles (Explosions)
+ for (let i = particles.length - 1; i >= 0; i--) {
+ particles[i].alpha -= 0.05;
+ if (particles[i].alpha <= 0) {
+ particles.splice(i, 1);
+ }
+ }
+
+ //Increase Wave Every X Seconds
+ const startTime = Date.now();
+ if(Date.now() - startTime > 10000){ //every 10 seconds increase wave
+ wave++;
+ updateWaveDisplay();
+ }
+
+ }
+
+
+
+ function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+
+ // Draw Player
+ ctx.fillStyle = player.color;
+ ctx.fillRect(player.x, player.y, player.width, player.height);
+
+ // Draw Bugs
+ for (const bug of bugs) {
+ ctx.fillStyle = bug.color;
+ ctx.fillRect(bug.x, bug.y, bug.width, bug.height);
+ }
+
+ // Draw Projectiles
+ for (const projectile of projectiles) {
+ ctx.fillStyle = 'cyan';
+ ctx.fillRect(projectile.x, projectile.y, 3, 5); // Thin rectangle for projectile
+ }
+
+
+ // Draw Particles
+ for (const particle of particles) {
+ ctx.globalAlpha = particle.alpha;
+ ctx.fillStyle = particle.color;
+ ctx.beginPath();
+ ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.globalAlpha = 1; // Reset alpha
+ }
+
+
+ }
+
+ function fire() {
+ projectiles.push({
+ x: player.x + player.width / 2 - 3,
+ y: player.y
+ });
+ }
+
+ function updateDisplay(){
+ document.getElementById('score').textContent = score;
+ document.getElementById('wave').textContent = wave;
+ document.getElementById('lives').textContent = lives;
+ }
+
+ function updateScoreDisplay(){
+ document.getElementById('score').textContent = score;
+ }
+
+ function updateWaveDisplay(){
+ document.getElementById('wave').textContent = wave;
+ }
+
+ function updateLivesDisplay(){
+ document.getElementById('lives').textContent = lives;
+ }
+
+
+ let keys = {};
+ window.addEventListener('keydown', (e) => {
+ keys[e.key] = true;
+ if (e.code === 'Space') {
+ fire();
+ }
+ });
+
+ window.addEventListener('keyup', (e) => {
+ keys[e.key] = false;
+ });
+
+
+ function restartGame() {
+ score = 0;
+ wave = 1;
+ lives = 3;
+ bugs.length = 0;
+ projectiles.length = 0;
+ particles.length = 0;
+
+ updateDisplay();
+ document.getElementById('gameoverScreen').style.display = 'none';
+ }
+
+ function showGameOver(){
+ document.getElementById('gameOverScore').textContent = score;
+ document.getElementById('gameoverScreen').style.display = 'block';
+
+ }
+
+
+ let animationFrameId;
+
+ function gameLoop() {
+ update();
+ draw();
+ animationFrameId = requestAnimationFrame(gameLoop);
+ }
+
+
+ gameLoop(); //Start the Game Loop
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4a6e2cfce746/gemma3-12b.png b/data/artifacts/4a6e2cfce746/gemma3-12b.png
new file mode 100644
index 0000000..afe3037
Binary files /dev/null and b/data/artifacts/4a6e2cfce746/gemma3-12b.png differ
diff --git a/data/artifacts/4a6e2cfce746/hermes3-8b.html b/data/artifacts/4a6e2cfce746/hermes3-8b.html
new file mode 100644
index 0000000..a77e77d
--- /dev/null
+++ b/data/artifacts/4a6e2cfce746/hermes3-8b.html
@@ -0,0 +1,282 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>FLEET DEFENDER</title>
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ background-color: #00008B;
+ overflow: hidden;
+ }
+
+ canvas {
+ display: block;
+ background-color: #00FF00;
+ }
+
+ .hud {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ color: #FFA500;
+ font-size: 24px;
+ }
+
+ .game-over {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 48px;
+ text-align: center;
+ color: #FFA500;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="gameCanvas"></canvas>
+ <div class="hud">
+ <span id="score">Score: 0</span>
+ <span id="wave">Wave: 1</span>
+ <span id="lives">Lives: 3</span>
+ </div>
+ <div class="game-over" style="display: none;">Game Over! Click to Restart.</div>
+
+ <script>
+ const canvas = document.getElementById('gameCanvas');
+ const ctx = canvas.getContext('2d');
+
+ canvas.width = window.innerWidth * 0.8;
+ canvas.height = window.innerHeight * 0.7;
+
+ let score = 0;
+ let wave = 1;
+ let lives = 3;
+ let gameLoop;
+
+ class GameObject {
+ constructor(x, y, width, height, color) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ this.color = color;
+ }
+
+ draw() {
+ ctx.fillStyle = this.color;
+ ctx.fillRect(this.x, this.y, this.width, this.height);
+ }
+ }
+
+ class PlayerShip extends GameObject {
+ constructor() {
+ super(canvas.width / 2 - 25, canvas.height * 0.8, 50, 50, 'cyan');
+ }
+
+ moveLeft() {
+ if (this.x > 0) {
+ this.x -= 5;
+ }
+ }
+
+ moveRight() {
+ if (this.x < canvas.width - 50) {
+ this.x += 5;
+ }
+ }
+
+ draw() {
+ super.draw();
+ ctx.fillStyle = 'yellow';
+ ctx.fillRect(this.x + 15, this.y + 25, 20, 10);
+ ctx.fillRect(this.x + 30, this.y + 25, 20, 10);
+ }
+ }
+
+ class Bug extends GameObject {
+ constructor(x, y) {
+ super(x, y, 50, 50, 'red');
+ }
+ }
+
+ class Explosion extends GameObject {
+ constructor(x, y) {
+ super(x, y, 0, 0, 'yellow');
+ this.width = 20;
+ this.height = 20;
+ this.frame = 0;
+ this.frames = 5;
+ this.time = 0;
+ this.speed = 10;
+ }
+
+ update() {
+ if (this.frame === 0) {
+ this.width = Math.floor(this.width * 1.2);
+ this.height = Math.floor(this.height * 1.2);
+ }
+ if (++this.time === this.speed) {
+ this.frame++;
+ this.time = 0;
+ }
+ if (this.frame >= this.frames) {
+ this.width = 0;
+ this.height = 0;
+ }
+ }
+
+ draw() {
+ super.draw();
+ this.update();
+ ctx.fillRect(this.x, this.y, this.width, this.height);
+ }
+ }
+
+ const playerShip = new PlayerShip();
+ let bugs = [];
+ let explosions = [];
+
+ function createBug() {
+ const x = Math.random() * canvas.width;
+ const y = -Math.random() * 500;
+ const bugSize = Math.random() * 0.5 + 0.5;
+ bugs.push(new Bug(x, y));
+ bugs[bugs.length - 1].height *= bugSize;
+ bugs[bugs.length - 1].width *= bugSize;
+ }
+
+ function collisionDetection() {
+ for (let i = bugs.length - 1; i >= 0; i--) {
+ const bug = bugs[i];
+ if (
+ playerShip.x < bug.x + bug.width &&
+ playerShip.x + playerShip.width > bug.x &&
+ playerShip.y < bug.y + bug.height &&
+ playerShip.y + playerShip.height > bug.y
+ ) {
+ explosions.push(new Explosion(bug.x, bug.y));
+ bugs.splice(i, 1);
+ lives--;
+ if (lives === 0) {
+ stopGame();
+ return;
+ }
+ }
+ }
+ }
+
+ function drawBugs() {
+ for (const bug of bugs) {
+ bug.draw();
+ }
+ }
+
+ function checkBugBounds() {
+ for (let i = bugs.length - 1; i >= 0; i--) {
+ const bug = bugs[i];
+ if (bug.y + bug.height > canvas.height) {
+ explosions.push(new Explosion(bug.x, bug.y));
+ bugs.splice(i, 1);
+ score += 10;
+ }
+ }
+ }
+
+ function drawExplosions() {
+ for (const explosion of explosions) {
+ explosion.draw();
+ }
+ }
+
+ function stopGame() {
+ clearInterval(gameLoop);
+ document.querySelector('.game-over').style.display = 'flex';
+ document.querySelector('.game-over').addEventListener('click', resetGame);
+ }
+
+ function resetGame() {
+ location.reload();
+ }
+
+ function startGame() {
+ wave++;
+ score = 0;
+ lives = 3;
+ bugs = [];
+ explosions = [];
+
+ for (let i = 0; i < wave * 2; i++) {
+ createBug();
+ }
+
+ gameLoop = setInterval(gameEngine, 20);
+ }
+
+ function gameEngine() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ playerShip.draw();
+
+ drawBugs();
+
+ checkBugBounds();
+
+ collisionDetection();
+
+ drawExplosions();
+
+ ctx.fillStyle = 'cyan';
+ ctx.fillRect(playerShip.x, playerShip.y - 70, 50, 5);
+ ctx.fillRect(playerShip.x + 25, playerShip.y - 80, 10, 15);
+
+ for (const bug of bugs) {
+ if (
+ playerShip.x < bug.x + bug.width &&
+ playerShip.x + playerShip.width > bug.x &&
+ playerShip.y - 70 < bug.y + bug.height &&
+ playerShip.y - 70 + 5 > bug.y
+ ) {
+ score += 10;
+ }
+ }
+
+ document.getElementById('score').textContent = `Score: ${score}`;
+ document.getElementById('wave').textContent = `Wave: ${wave}`;
+ document.getElementById('lives').textContent = `Lives: ${lives}`;
+
+ if (bugs.length === 0) {
+ stopGame();
+ } else if (lives === 0) {
+ document.querySelector('.game-over').style.display = 'flex';
+ }
+ }
+
+ document.addEventListener('keydown', event => {
+ switch (event.key) {
+ case 'ArrowLeft':
+ playerShip.moveLeft();
+ break;
+ case 'ArrowRight':
+ playerShip.moveRight();
+ break;
+ case ' ':
+ if (bugs.length > 0 && bugs[0].y + bugs[0].height < playerShip.y) {
+ explosions.push(new Explosion(bugs[0].x, bugs[0].y));
+ bugs.splice(0, 1);
+ score += 50;
+ }
+ break;
+ }
+ });
+
+ startGame();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4a6e2cfce746/hermes3-8b.png b/data/artifacts/4a6e2cfce746/hermes3-8b.png
new file mode 100644
index 0000000..3bc899e
Binary files /dev/null and b/data/artifacts/4a6e2cfce746/hermes3-8b.png differ
diff --git a/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.html b/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..11e52d7
--- /dev/null
+++ b/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.html
@@ -0,0 +1,209 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>FLEET DEFENDER</title>
+<style>
+body { margin: 0; overflow: hidden; display: flex; justify-content: center; align-items: center; background: #002B36; color: cyan; font-family: 'Courier New', Courier, monospace; }
+canvas { border: 4px solid #586E75; }
+#hud { position: absolute; top: 10px; left: 10px; }
+#gameOver { display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 43, 54, 0.8); padding: 20px; border: 2px solid cyan; text-align: center; }
+</style>
+</head>
+<body>
+<div id="hud">
+ Score: <span id="score">0</span><br>
+ Wave: <span id="wave">1</span><br>
+ Lives: <span id="lives">3</span>
+</div>
+<canvas id="gameCanvas" width="800" height="600"></canvas>
+<div id="gameOver">
+ Game Over<br>
+ Score: <span id="finalScore">0</span><br>
+ Press Space to Restart
+</div>
+<script>
+const canvas = document.getElementById('gameCanvas');
+const ctx = canvas.getContext('2d');
+const scoreDisplay = document.getElementById('score');
+const waveDisplay = document.getElementById('wave');
+const livesDisplay = document.getElementById('lives');
+const finalScoreDisplay = document.getElementById('finalScore');
+const gameOverScreen = document.getElementById('gameOver');
+
+let score = 0;
+let wave = 1;
+let lives = 3;
+let lastTime = 0;
+let ship = { x: canvas.width / 2, y: canvas.height - 50, width: 40, height: 20, speed: 5 };
+let bullets = [];
+let enemies = [];
+let explosions = [];
+
+const keys = {
+ ArrowLeft: false,
+ ArrowRight: false,
+ Spacebar: false
+};
+
+window.addEventListener('keydown', function(e) { keys[e.key] = true; });
+window.addEventListener('keyup', function(e) { keys[e.key] = false; });
+
+function spawnEnemies() {
+ for (let i = 0; i < wave * 3; i++) {
+ enemies.push({ x: Math.random() * canvas.width, y: -Math.random() * 100, width: 20, height: 20, speedY: 2 + wave / 5 });
+ }
+}
+
+function drawShip() {
+ ctx.fillStyle = 'cyan';
+ ctx.fillRect(ship.x, ship.y, ship.width, ship.height);
+ ctx.beginPath();
+ ctx.moveTo(ship.x - 10, ship.y);
+ ctx.lineTo(ship.x + 20, ship.y + 10);
+ ctx.lineTo(ship.x - 10, ship.y + 20);
+ ctx.closePath();
+ ctx.fill();
+}
+
+function drawBullets() {
+ bullets.forEach(bullet => {
+ ctx.fillStyle = 'amber';
+ ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
+ });
+}
+
+function moveShip() {
+ if (keys.ArrowLeft) ship.x -= ship.speed;
+ if (keys.ArrowRight) ship.x += ship.speed;
+ if (ship.x < 0) ship.x = 0;
+ if (ship.x + ship.width > canvas.width) ship.x = canvas.width - ship.width;
+}
+
+function moveBullets() {
+ bullets.forEach(bullet => bullet.y -= 5);
+ bullets = bullets.filter(bullet => bullet.y > 0);
+}
+
+function spawnBullet() {
+ if (!keys.Spacebar) return;
+ keys.Spacebar = false;
+ bullets.push({ x: ship.x + ship.width / 2 - 1, y: ship.y - 5, width: 2, height: 10 });
+}
+
+function drawEnemies() {
+ enemies.forEach(enemy => {
+ ctx.fillStyle = 'red';
+ ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
+ });
+}
+
+function moveEnemies() {
+ enemies.forEach(enemy => enemy.y += enemy.speedY);
+ enemies = enemies.filter(enemy => enemy.y < canvas.height);
+}
+
+function checkCollisions() {
+ bullets.forEach((bullet, index) => {
+ enemies.forEach((enemy, eIndex) => {
+ if (bullet.x < enemy.x + enemy.width &&
+ bullet.x + bullet.width > enemy.x &&
+ bullet.y < enemy.y + enemy.height &&
+ bullet.y + bullet.height > enemy.y) {
+ score += 10;
+ bullets.splice(index, 1);
+ enemies.splice(eIndex, 1);
+ createExplosion(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2);
+ }
+ });
+ });
+
+ enemies.forEach((enemy, index) => {
+ if (enemy.y + enemy.height > ship.y &&
+ enemy.x < ship.x + ship.width &&
+ enemy.x + enemy.width > ship.x) {
+ lives--;
+ if (lives <= 0) endGame();
+ else enemies.splice(index, 1);
+ }
+ });
+}
+
+function createExplosion(x, y) {
+ explosions.push({ x: x, y: y, radius: 2, maxRadius: 20 + Math.random() * 10, color: `rgba(255, 69, 0, ${Math.random()})` });
+}
+
+function drawExplosions() {
+ explosions.forEach(explosion => {
+ ctx.beginPath();
+ ctx.arc(explosion.x, explosion.y, explosion.radius, 0, Math.PI * 2);
+ ctx.fillStyle = explosion.color;
+ ctx.fill();
+ explosion.radius += 1.5;
+ });
+ explosions = explosions.filter(explosion => explosion.radius <= explosion.maxRadius);
+}
+
+function nextWave() {
+ wave++;
+ spawnEnemies();
+}
+
+function endGame() {
+ gameOverScreen.style.display = 'block';
+ finalScoreDisplay.textContent = score;
+ window.removeEventListener('keydown', function(e) { keys[e.key] = true; });
+ window.addEventListener('keydown', restart);
+}
+
+function restart(e) {
+ if (e.code === 'Space') {
+ lives = 3;
+ wave = 1;
+ score = 0;
+ bullets = [];
+ enemies = [];
+ explosions = [];
+ spawnEnemies();
+ gameOverScreen.style.display = 'none';
+ window.removeEventListener('keydown', restart);
+ requestAnimationFrame(gameLoop);
+ }
+}
+
+function gameLoop(currentTime) {
+ if (!lastTime) lastTime = currentTime;
+ let deltaTime = (currentTime - lastTime) / 1000;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ moveShip();
+ drawShip();
+
+ spawnBullet();
+ moveBullets();
+ drawBullets();
+
+ drawEnemies();
+ moveEnemies();
+
+ checkCollisions();
+
+ drawExplosions();
+
+ scoreDisplay.textContent = score;
+ waveDisplay.textContent = wave;
+ livesDisplay.textContent = lives;
+
+ if (enemies.length === 0) nextWave();
+
+ lastTime = currentTime;
+ requestAnimationFrame(gameLoop);
+}
+
+spawnEnemies();
+requestAnimationFrame(gameLoop);
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.png b/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..c1300a3
Binary files /dev/null and b/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/4a6e2cfce746/qwen25-7b.html b/data/artifacts/4a6e2cfce746/qwen25-7b.html
new file mode 100644
index 0000000..efdf237
--- /dev/null
+++ b/data/artifacts/4a6e2cfce746/qwen25-7b.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>FLEET DEFENDER</title>
+ <style>
+ body { margin: 0; overflow: hidden; }
+ canvas { display: block; }
+ .hud {
+ position: absolute;
+ left: 5px; bottom: 20px; font-size: 18px; color: cyan;
+ pointer-events: none;
+ }
+ </style>
+</head>
+<body>
+<canvas id="gameCanvas" width="800" height="600"></canvas>
+<div class="hud">Score: <span id="score">0</span><br>Wave: <span id="wave">1</span><br>Lives: <span id="lives">3</span></div>
+
+<script>
+const canvas = document.getElementById('gameCanvas');
+const ctx = canvas.getContext('2d');
+
+canvas.width = 800;
+canvas.height = 600;
+
+let shipX = canvas.width / 2 - 15, shipY = 570, score = 0, wave = 1, lives = 3, bullets = [], bugs = [], bugCount = 5, bugSpeed = 4, bulletSpeed = 8, hitEffect = [];
+const bg = new ImageData(new Uint8ClampedArray(canvas.width * canvas.height * 4), canvas.width, canvas.height);
+bg.data.set([0, 255, 0, 255].repeat(bg.width * bg.height));
+
+function draw() {
+ ctx.putImageData(bg, 0, 0);
+ ctx.fillStyle = 'cyan';
+ ctx.fillRect(10, 10, 180, 40);
+
+ for (let i = 0; i < bullets.length; i++) {
+ const { x, y } = bullets[i];
+ ctx.fillStyle = 'white';
+ ctx.fillRect(x - 2, y - 2, 4, 4);
+ if (++bullets[i].y > canvas.height) bullets.splice(i--, 1);
+ }
+
+ for (let i = bugs.length - 1; i >= 0; --i) {
+ const { x, y } = bugs[i];
+ ctx.fillStyle = 'red';
+ ctx.fillRect(x - 5, y - 5, 10, 10);
+ if (++bugs[i].y > canvas.height) {
+ score += 10;
+ bugs.splice(i--, 1);
+ }
+ }
+
+ for (let i = hitEffect.length - 1; i >= 0; --i) {
+ const { x, y } = hitEffect[i];
+ ctx.fillStyle = 'yellow';
+ ctx.fillRect(x - 2, y - 2, 4, 4);
+ if (++hitEffect[i].y > canvas.height) hitEffect.splice(i--, 1);
+ }
+
+ ctx.fillStyle = 'white';
+ ctx.fillText(`Score: ${score}`, 20, 35);
+ ctx.fillText(`Wave: ${wave}`, 20, 60);
+ ctx.fillText(`Lives: ${lives}`, 20, 85);
+
+ if (bugs.length === 0) {
+ wave++;
+ bugCount += 1;
+ bugs = Array(bugCount).fill().map(() => ({ x: Math.random() * canvas.width, y: -30 }));
+ }
+
+ if (!lives || !bugs.length && wave > 1) endGame();
+
+ ctx.fillStyle = 'cyan';
+ ctx.fillRect(canvas.width / 2 - 15, shipY, 30, 10);
+
+ requestAnimationFrame(draw);
+}
+
+function moveShip(e) {
+ const speed = 6;
+ if (e.key === 'ArrowLeft') shipX -= speed;
+ else if (e.key === 'ArrowRight') shipX += speed;
+ if (shipX < 15) shipX = 15;
+ if (shipX > canvas.width - 30) shipX = canvas.width - 30;
+}
+
+function shootBullet() {
+ bullets.push({ x: shipX + 12, y: shipY });
+}
+
+document.addEventListener('keydown', moveShip);
+document.addEventListener('keyup', e => { if (e.key === 'Space') shootBullet(); });
+
+draw();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4a6e2cfce746/qwen25-7b.png b/data/artifacts/4a6e2cfce746/qwen25-7b.png
new file mode 100644
index 0000000..10fdc55
Binary files /dev/null and b/data/artifacts/4a6e2cfce746/qwen25-7b.png differ
diff --git a/data/artifacts/4a6e2cfce746/qwen3-14b.html b/data/artifacts/4a6e2cfce746/qwen3-14b.html
new file mode 100644
index 0000000..d6a2819
--- /dev/null
+++ b/data/artifacts/4a6e2cfce746/qwen3-14b.html
@@ -0,0 +1,315 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>Fleet Defender</title>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ background: #0a0f2c;
+ color: #00ffcc;
+ font-family: 'Courier New', monospace;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ user-select: none;
+ }
+ canvas {
+ display: block;
+ background: #0a0f2c;
+ }
+ .hud {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ font-size: 20px;
+ color: #00ffcc;
+ text-shadow: 0 0 10px #00ffcc;
+ }
+ .game-over {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 40px;
+ text-align: center;
+ color: #ffcc00;
+ text-shadow: 0 0 15px #ffcc00;
+ }
+ .restart {
+ position: absolute;
+ top: 60%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 24px;
+ color: #00ffcc;
+ text-shadow: 0 0 10px #00ffcc;
+ cursor: pointer;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="gameCanvas"></canvas>
+ <div class="hud" id="hud">Score: 0 | Wave: 1 | Lives: 3</div>
+ <div class="game-over" id="gameOver" style="display: none;">Game Over</div>
+ <div class="restart" id="restart" style="display: none;" onclick="init()">Press Space to Restart</div>
+ <script>
+ const canvas = document.getElementById('gameCanvas');
+ const ctx = canvas.getContext('2d');
+ const hud = document.getElementById('hud');
+ const gameOver = document.getElementById('gameOver');
+ const restart = document.getElementById('restart');
+
+ let width = canvas.width = window.innerWidth;
+ let height = canvas.height = window.innerHeight;
+ let score = 0;
+ let wave = 1;
+ let lives = 3;
+ let gameOverState = false;
+
+ const ship = {
+ x: width / 2,
+ y: height - 50,
+ width: 40,
+ height: 20,
+ speed: 5,
+ color: '#00ffff',
+ bullets: []
+ };
+
+ const bugs = [];
+ let spawnTimer = 0;
+ let spawnInterval = 1000;
+ let bugSpeed = 2;
+ let waveTimer = 0;
+ let waveInterval = 5000;
+
+ const explosions = [];
+
+ const keys = {};
+
+ window.addEventListener('keydown', (e) => {
+ keys[e.code] = true;
+ });
+
+ window.addEventListener('keyup', (e) => {
+ keys[e.code] = false;
+ });
+
+ function init() {
+ score = 0;
+ wave = 1;
+ lives = 3;
+ gameOverState = false;
+ gameOver.style.display = 'none';
+ restart.style.display = 'none';
+ hud.style.display = 'block';
+ bugs.length = 0;
+ explosions.length = 0;
+ ship.bullets.length = 0;
+ spawnTimer = 0;
+ waveTimer = 0;
+ requestAnimationFrame(gameLoop);
+ }
+
+ function spawnBug() {
+ const size = 20 + Math.random() * 10;
+ const x = Math.random() * (width - size);
+ bugs.push({
+ x,
+ y: -size,
+ width: size,
+ height: size,
+ speed: bugSpeed,
+ color: '#ff4444'
+ });
+ }
+
+ function spawnWave() {
+ for (let i = 0; i < wave * 2; i++) {
+ spawnBug();
+ }
+ }
+
+ function fireBullet() {
+ ship.bullets.push({
+ x: ship.x + ship.width / 2 - 2,
+ y: ship.y,
+ width: 4,
+ height: 15,
+ speed: 7,
+ color: '#00ffcc'
+ });
+ }
+
+ function update() {
+ // Move ship
+ if (keys['ArrowLeft']) {
+ ship.x -= ship.speed;
+ if (ship.x < 0) ship.x = 0;
+ }
+ if (keys['ArrowRight']) {
+ ship.x += ship.speed;
+ if (ship.x + ship.width > width) ship.x = width - ship.width;
+ }
+ if (keys['Space']) {
+ fireBullet();
+ }
+
+ // Update bullets
+ for (let i = ship.bullets.length - 1; i >= 0; i--) {
+ const bullet = ship.bullets[i];
+ bullet.y -= bullet.speed;
+ if (bullet.y < 0) {
+ ship.bullets.splice(i, 1);
+ }
+ }
+
+ // Update bugs
+ for (let i = bugs.length - 1; i >= 0; i--) {
+ const bug = bugs[i];
+ bug.y += bug.speed;
+
+ // Collision with bullet
+ for (let j = ship.bullets.length - 1; j >= 0; j--) {
+ const bullet = ship.bullets[j];
+ if (
+ bullet.x < bug.x + bug.width &&
+ bullet.x + bullet.width > bug.x &&
+ bullet.y < bug.y + bug.height &&
+ bullet.y + bullet.height > bug.y
+ ) {
+ explosions.push({
+ x: bug.x + bug.width / 2,
+ y: bug.y + bug.height / 2,
+ size: 20,
+ color: '#ffcc00',
+ alpha: 1,
+ duration: 0.5
+ });
+ score += 10;
+ ship.bullets.splice(j, 1);
+ bugs.splice(i, 1);
+ break;
+ }
+ }
+
+ // Collision with ship
+ if (
+ bug.x < ship.x + ship.width &&
+ bug.x + bug.width > ship.x &&
+ bug.y < ship.y + ship.height &&
+ bug.y + bug.height > ship.y
+ ) {
+ explosions.push({
+ x: ship.x + ship.width / 2,
+ y: ship.y + ship.height / 2,
+ size: 20,
+ color: '#ff4444',
+ alpha: 1,
+ duration: 0.5
+ });
+ lives--;
+ bugs.splice(i, 1);
+ if (lives <= 0) {
+ gameOverState = true;
+ gameOver.style.display = 'block';
+ restart.style.display = 'block';
+ hud.style.display = 'none';
+ }
+ }
+
+ // Remove bug if off screen
+ if (bug.y > height) {
+ bugs.splice(i, 1);
+ }
+ }
+
+ // Update explosions
+ for (let i = explosions.length - 1; i >= 0; i--) {
+ const exp = explosions[i];
+ exp.alpha -= 0.05;
+ if (exp.alpha <= 0) {
+ explosions.splice(i, 1);
+ }
+ }
+
+ // Spawn timer
+ spawnTimer += 1;
+ if (spawnTimer >= spawnInterval) {
+ spawnBug();
+ spawnTimer = 0;
+ }
+
+ // Wave timer
+ waveTimer += 1;
+ if (waveTimer >= waveInterval) {
+ wave++;
+ spawnWave();
+ waveTimer = 0;
+ spawnInterval = Math.max(200, spawnInterval * 0.95);
+ bugSpeed += 0.1;
+ }
+
+ // Update HUD
+ hud.textContent = `Score: ${score} | Wave: ${wave} | Lives: ${lives}`;
+ }
+
+ function draw() {
+ ctx.clearRect(0, 0, width, height);
+
+ // Draw ship
+ ctx.fillStyle = ship.color;
+ ctx.fillRect(ship.x, ship.y, ship.width, ship.height);
+
+ // Draw bullets
+ for (const bullet of ship.bullets) {
+ ctx.fillStyle = bullet.color;
+ ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
+ }
+
+ // Draw bugs
+ for (const bug of bugs) {
+ ctx.fillStyle = bug.color;
+ ctx.fillRect(bug.x, bug.y, bug.width, bug.height);
+ }
+
+ // Draw explosions
+ for (const exp of explosions) {
+ ctx.fillStyle = `rgba(${exp.color.slice(1)}, ${exp.alpha})`;
+ ctx.beginPath();
+ ctx.arc(exp.x, exp.y, exp.size, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ // Draw grid
+ ctx.strokeStyle = '#00ffcc';
+ ctx.lineWidth = 1;
+ for (let i = 0; i < 10; i++) {
+ ctx.beginPath();
+ ctx.moveTo(0, i * (height / 10));
+ ctx.lineTo(width, i * (height / 10));
+ ctx.stroke();
+ }
+ for (let i = 0; i < 10; i++) {
+ ctx.beginPath();
+ ctx.moveTo(i * (width / 10), 0);
+ ctx.lineTo(i * (width / 10), height);
+ ctx.stroke();
+ }
+ }
+
+ function gameLoop() {
+ if (gameOverState) return;
+ update();
+ draw();
+ requestAnimationFrame(gameLoop);
+ }
+
+ init();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4a6e2cfce746/qwen3-14b.png b/data/artifacts/4a6e2cfce746/qwen3-14b.png
new file mode 100644
index 0000000..e690a49
Binary files /dev/null and b/data/artifacts/4a6e2cfce746/qwen3-14b.png differ
diff --git a/data/challenges.json b/data/challenges.json
index d6a3a2d..cc7e775 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -11315,5 +11315,228 @@
"judging": false,
"aiPick": "qwen3-14b",
"judged_at": "2026-07-23T19:54:52.473Z"
+ },
+ {
+ "id": "4a6e2cfce746",
+ "title": "Agent Abrams — Fleet Defender",
+ "prompt": "Build a single self-contained HTML file: a playable arcade game called FLEET DEFENDER themed as an AI-agent command center. The player pilots a glowing command ship at the bottom, moving left/right with arrow keys and firing with spacebar to defend the fleet from waves of descending 'bug' invaders. Neon command-center aesthetic (dark navy background, cyan/amber HUD, agent-node grid). Show score, wave number, and lives. Increasing difficulty per wave, particle explosions on hits, and a game-over + restart screen. 60fps canvas, keyboard controls, no external assets. Output ONLY the HTML.",
+ "category": "Games",
+ "designTools": false,
+ "created_at": "2026-07-25T06:11:28.221Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 149,
+ "cost": 0,
+ "started_at": "2026-07-25T06:11:28.290Z",
+ "finished_at": "2026-07-25T06:13:57.155Z",
+ "queued_at": "2026-07-25T06:11:28.260Z",
+ "bytes": 7711,
+ "thumb": true
+ },
+ {
+ "model": "gemma3-12b",
+ "status": "done",
+ "error": null,
+ "seconds": 118,
+ "cost": 0,
+ "started_at": "2026-07-25T06:13:57.160Z",
+ "finished_at": "2026-07-25T06:15:55.094Z",
+ "queued_at": "2026-07-25T06:11:28.266Z",
+ "bytes": 7432,
+ "thumb": true
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "done",
+ "error": null,
+ "seconds": 24,
+ "cost": 0,
+ "started_at": "2026-07-25T06:15:55.099Z",
+ "finished_at": "2026-07-25T06:16:19.228Z",
+ "queued_at": "2026-07-25T06:11:28.271Z",
+ "bytes": 6555,
+ "thumb": true
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 25,
+ "cost": 0,
+ "started_at": "2026-07-25T06:11:28.294Z",
+ "finished_at": "2026-07-25T06:11:53.407Z",
+ "queued_at": "2026-07-25T06:11:28.278Z",
+ "bytes": 2988,
+ "thumb": true
+ },
+ {
+ "model": "hf-qwen-coder-32b",
+ "status": "done",
+ "error": null,
+ "seconds": 76,
+ "cost": 0,
+ "started_at": "2026-07-25T06:16:19.234Z",
+ "finished_at": "2026-07-25T06:17:35.300Z",
+ "queued_at": "2026-07-25T06:11:28.282Z",
+ "bytes": 6033,
+ "thumb": true
+ },
+ {
+ "model": "claude-code",
+ "status": "error",
+ "error": "no HTML document in model output (1686 chars)",
+ "seconds": 126,
+ "cost": null,
+ "started_at": "2026-07-25T06:11:28.296Z",
+ "finished_at": "2026-07-25T06:13:33.939Z",
+ "queued_at": "2026-07-25T06:11:28.286Z"
+ }
+ ],
+ "judging": true,
+ "judged_at": null
+ },
+ {
+ "id": "ea271db82cff",
+ "title": "Remix: Free-Roster — Lava Lamp",
+ "prompt": "Build a single-file HTML page: a metaball lava lamp. Blobs rise and fall and merge with smooth metaball rendering and a warm gradient; a color-theme selector and a blob-count slider. Canvas-based, self-contained, no external libraries.\n\nHere is a strong previous attempt at this exact challenge. IMPROVE on it — keep what works, fix its weaknesses, and elevate the visual quality and polish. Return a complete, improved single-file HTML document.\n\n=== PREVIOUS ATTEMPT (Claude Opus (Max plan)) ===\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Metaball Lava Lamp</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; }\n #lamp { display: block; width: 100vw; height: 100vh; }\n\n #panel {\n position: fixed;\n top: 18px;\n left: 18px;\n padding: 16px 18px 18px;\n border-radius: 16px;\n background: rgba(20, 12, 30, 0.42);\n backdrop-filter: blur(14px) saturate(140%);\n -webkit-backdrop-filter: blur(14px) saturate(140%);\n border: 1px solid rgba(255, 255, 255, 0.12);\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.45);\n color: #f4eef8;\n width: 224px;\n user-select: none;\n z-index: 10;\n transition: opacity .3s;\n }\n #panel h1 {\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 1.6px;\n text-transform: uppercase;\n opacity: 0.85;\n margin-bottom: 14px;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n #panel h1 .dot {\n width: 9px; height: 9px; border-radius: 50%;\n background: radial-gradient(circle at 30% 30%, #ffe066, #ff2d55);\n box-shadow: 0 0 10px #ff5e3a;\n }\n .row { margin-bottom: 14px; }\n .row:last-child { margin-bottom: 0; }\n label {\n display: block;\n font-size: 11px;\n letter-spacing: 0.5px;\n opacity: 0.7;\n margin-bottom: 7px;\n display: flex;\n justify-content: space-between;\n }\n label b { opacity: 0.95; font-weight: 600; }\n select {\n width: 100%;\n padding: 8px 10px;\n border-radius: 9px;\n background: rgba(255,255,255,0.07);\n color: #f4eef8;\n border: 1px solid rgba(255,255,255,0.14);\n font-size: 13px;\n outline: none;\n cursor: pointer;\n appearance: none;\n -webkit-appearance: none;\n 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>\");\n background-repeat: no-repeat;\n background-position: right 10px center;\n }\n select option { background: #1a1024; color: #f4eef8; }\n\n input[type=range] {\n width: 100%;\n -webkit-appearance: none;\n appearance: none;\n height: 5px;\n border-radius: 5px;\n background: rgba(255,255,255,0.18);\n outline: none;\n cursor: pointer;\n }\n input[type=range]::-webkit-slider-thumb {\n -webkit-appearance: none;\n width: 17px; height: 17px;\n border-radius: 50%;\n background: radial-gradient(circle at 32% 30%, #ffe9a8, #ff7a00);\n border: 2px solid rgba(255,255,255,0.5);\n box-shadow: 0 0 8px rgba(255,120,40,0.7);\n }\n input[type=range]::-moz-range-thumb {\n width: 15px; height: 15px;\n border-radius: 50%;\n background: radial-gradient(circle at 32% 30%, #ffe9a8, #ff7a00);\n border: 2px solid rgba(255,255,255,0.5);\n box-shadow: 0 0 8px rgba(255,120,40,0.7);\n }\n #hint {\n position: fixed;\n bottom: 14px;\n left: 0; right: 0;\n text-align: center;\n color: rgba(255,255,255,0.28);\n font-size: 11px;\n letter-spacing: 1px;\n z-index: 5;\n pointer-events: none;\n }\n</style>\n</head>\n<body>\n<canvas id=\"lamp\"></canvas>\n\n<div id=\"panel\">\n <h1><span class=\"dot\"></span>Lava Lamp</h1>\n <div class=\"row\">\n <label>Color Theme</label>\n <select id=\"theme\">\n <option value=\"classic\">Classic Lava</option>\n <option value=\"ember\">Ember Glow</option>\n <option value=\"sunset\">Sunset</option>\n <option value=\"amethyst\">Amethyst</option>\n <option value=\"ocean\">Deep Ocean</option>\n <option value=\"toxic\">Toxic</option>\n </select>\n </div>\n <div class=\"row\">\n <label>Blobs <b id=\"countVal\">12</b></label>\n <input type=\"range\" id=\"count\" min=\"3\" max=\"26\" value=\"12\">\n </div>\n</div>\n\n<div id=\"hint\">metaballs rise · fall · merge</div>\n\n<script>\n(function () {\n \"use strict\";\n\n const canvas = document.getElementById(\"lamp\");\n const ctx = canvas.getContext(\"2d\");\n\n // Offscreen low-res buffer for the metaball field\n const off = document.createElement(\"canvas\");\n const offCtx = off.getContext(\"2d\");\n\n // ---- Themes ----------------------------------------------------------\n // bg: vertical background gradient [top, bottom]\n // stops: blob color ramp (edge -> core)\n const THEMES = {\n classic: {\n bg: [\"#2a0a3a\", \"#160326\"],\n stops: [[0,\"#7a0f3a\"],[0.35,\"#ff2d55\"],[0.7,\"#ff8a00\"],[1,\"#ffe066\"]]\n },\n ember: {\n bg: [\"#220a05\", \"#120302\"],\n stops: [[0,\"#7a1500\"],[0.4,\"#ff3d00\"],[0.72,\"#ff7a00\"],[1,\"#ffc46b\"]]\n },\n sunset: {\n bg: [\"#1b1035\", \"#360d38\"],\n stops: [[0,\"#5b1250\"],[0.35,\"#ff4d6d\"],[0.7,\"#ff9e5e\"],[1,\"#ffd76e\"]]\n },\n amethyst: {\n bg: [\"#14052a\", \"#26063f\"],\n stops: [[0,\"#5a1a8a\"],[0.4,\"#c04dff\"],[0.72,\"#ff5edb\"],[1,\"#ffd1f5\"]]\n },\n ocean: {\n bg: [\"#041b2d\", \"#06263a\"],\n stops: [[0,\"#053a5c\"],[0.38,\"#00c8ff\"],[0.72,\"#2b8cff\"],[1,\"#bfeaff\"]]\n },\n toxic: {\n bg: [\"#071a07\", \"#0a250a\"],\n stops: [[0,\"#2c5c00\"],[0.4,\"#7bd400\"],[0.72,\"#39ff14\"],[1,\"#eaffb0\"]]\n }\n };\n\n let theme = THEMES.classic;\n let ramp = new Uint8Array(256 * 3); // color lookup: edge->core\n\n function hexToRgb(h) {\n h = h.replace(\"#\", \"\");\n return [parseInt(h.slice(0,2),16), parseInt(h.slice(2,4),16), parseInt(h.slice(4,6),16)];\n }\n\n function buildRamp(t) {\n const stops = t.stops;\n for (let i = 0; i < 256; i++) {\n const p = i / 255;\n let a = stops[0], b = stops[stops.length - 1];\n for (let s = 0; s < stops.length - 1; s++) {\n if (p >= stops[s][0] && p <= stops[s + 1][0]) { a = stops[s]; b = stops[s + 1]; break; }\n }\n const span = (b[0] - a[0]) || 1;\n const f = (p - a[0]) / span;\n const ca = hexToRgb(a[1]), cb = hexToRgb(b[1]);\n ramp[i*3] = ca[0] + (cb[0] - ca[0]) * f;\n ramp[i*3+1] = ca[1] + (cb[1] - ca[1]) * f;\n ramp[i*3+2] = ca[2] + (cb[2] - ca[2]) * f;\n }\n }\n buildRamp(theme);\n\n // ---- Sizing ----------------------------------------------------------\n let W = 0, H = 0, minDim = 0;\n let lowW = 0, lowH = 0, scale = 0.22;\n let imgData = null, buf = null;\n let bgGrad = null;\n\n function resize() {\n W = canvas.width = Math.max(1, window.innerWidth);\n H = canvas.height = Math.max(1, window.innerHeight);\n minDim = Math.min(W, H);\n\n // Keep the low-res field around ~200px on the long edge\n scale = Math.min(0.26, 200 / Math.max(W, H));\n lowW = Math.max(2, Math.round(W * scale));\n lowH = Math.max(2, Math.round(H * scale));\n off.width = lowW;\n off.height = lowH;\n imgData = offCtx.createImageData(lowW, lowH);\n buf = imgData.data;\n\n bgGrad = ctx.createLinearGradient(0, 0, 0, H);\n bgGrad.addColorStop(0, theme.bg[0]);\n bgGrad.addColorStop(1, theme.bg[1]);\n\n for (const b of blobs) recomputeRadius(b);\n }\n\n // ---- Blobs -----------------------------------------------------------\n const blobs = [];\n\n function recomputeRadius(b) {\n b.r = b.rFrac * minDim;\n }\n\n function makeBlob() {\n const b = {\n x: Math.random() * (W || 800),\n y: Math.random() * (H || 600),\n rFrac: 0.05 + Math.random() * 0.055,\n vs: 0.25 + Math.random() * 0.5, // vertical osc speed\n vp: Math.random() * Math.PI * 2, // vertical phase\n vamp: 45 + Math.random() * 70, // vertical amplitude (px/s)\n hs: 0.15 + Math.random() * 0.35, // horizontal speed\n hp: Math.random() * Math.PI * 2,\n hamp: 18 + Math.random() * 34, // horizontal drift\n ps: 0.4 + Math.random() * 0.8, // pulse speed\n pp: Math.random() * Math.PI * 2,\n r: 40\n };\n recomputeRadius(b);\n return b;\n }\n\n function setCount(n) {\n while (blobs.length < n) blobs.push(makeBlob());\n while (blobs.length > n) blobs.pop();\n }\n\n // ---- Physics ---------------------------------------------------------\n function update(t, dt) {\n for (const b of blobs) {\n const vy = Math.sin(t * b.vs + b.vp) * b.vamp;\n const vx = Math.sin(t * b.hs + b.hp) * b.hamp;\n b.x += vx * dt;\n b.y += vy * dt;\n\n const pad = b.r * 0.35;\n if (b.x < pad) { b.x = pad; b.hp += Math.PI; }\n else if (b.x > W - pad) { b.x = W - pad; b.hp += Math.PI; }\n if (b.y < pad) { b.y = pad; b.vp += Math.PI; }\n else if (b.y > H - pad) { b.y = H - pad; b.vp += Math.PI; }\n\n b.rr = b.r * (1 + Math.sin(t * b.ps + b.pp) * 0.14); // pulsating radius\n }\n }\n\n // ---- Metaball render -------------------------------------------------\n function renderField(t) {\n // Precompute low-res blob params\n const n = blobs.length;\n const bx = new Float32Array(n), by = new Float32Array(n), br2 = new Float32Array(n);\n for (let i = 0; i < n; i++) {\n const b = blobs[i];\n bx[i] = b.x * scale;\n by[i] = b.y * scale;\n const rl = b.rr * scale;\n br2[i] = rl * rl;\n }\n\n let p = 0;\n for (let y = 0; y < lowH; y++) {\n for (let x = 0; x < lowW; x++) {\n let sum = 0;\n for (let i = 0; i < n; i++) {\n const dx = x - bx[i], dy = y - by[i];\n const d2 = dx * dx + dy * dy + 0.0001;\n sum += br2[i] / d2;\n }\n let a;\n if (sum < 0.65) {\n a = 0;\n } else {\n a = sum < 1.05 ? (sum - 0.65) / 0.4 : 1;\n let ct = (sum - 0.7) / 2.6; // color param edge->core\n if (ct < 0) ct = 0; else if (ct > 1) ct = 1;\n const ci = (ct * 255) | 0;\n const r3 = ci * 3;\n buf[p] = ramp[r3];\n buf[p + 1] = ramp[r3 + 1];\n buf[p + 2] = ramp[r3 + 2];\n }\n buf[p + 3] = a * 255;\n p += 4;\n }\n }\n offCtx.putImageData(imgData, 0, 0);\n }\n\n // ---- Main loop -------------------------------------------------------\n let last = performance.now();\n function frame(now) {\n let dt = (now - last) / 1000;\n if (dt > 0.05) dt = 0.05; // clamp after tab switch\n last = now;\n const t = now / 1000;\n\n update(t, dt);\n renderField(t);\n\n // Background gradient\n ctx.globalCompositeOperation = \"source-over\";\n ctx.fillStyle = bgGrad;\n ctx.fillRect(0, 0, W, H);\n\n // Upscale the metaball layer smoothly on top\n ctx.imageSmoothingEnabled = true;\n ctx.imageSmoothingQuality = \"high\";\n ctx.drawImage(off, 0, 0, lowW, lowH, 0, 0, W, H);\n\n // Soft glow pass\n ctx.globalCompositeOperation = \"lighter\";\n ctx.globalAlpha = 0.28;\n ctx.drawImage(off, 0, 0, lowW, lowH, 0, 0, W, H);\n ctx.globalAlpha = 1;\n ctx.globalCompositeOperation = \"source-over\";\n\n requestAnimationFrame(frame);\n }\n\n // ---- Controls --------------------------------------------------------\n const themeSel = document.getElementById(\"theme\");\n const countInp = document.getElementById(\"count\");\n const countVal = document.getElementById(\"countVal\");\n\n themeSel.addEventListener(\"change\", function () {\n theme = THEMES[themeSel.value] || THEMES.classic;\n buildRamp(theme);\n bgGrad = ctx.createLinearGradient(0, 0, 0, H);\n bgGrad.addColorStop(0, theme.bg[0]);\n bgGrad.addColorStop(1, theme.bg[1]);\n });\n\n countInp.addEventListener(\"input\", function () {\n countVal.textContent = countInp.value;\n setCount(parseInt(countInp.value, 10));\n });\n\n window.addEventListener(\"resize\", resize);\n\n // ---- Boot ------------------------------------------------------------\n setCount(parseInt(countInp.value, 10));\n resize();\n requestAnimationFrame(frame);\n})();\n</script>\n</body>\n</html>",
+ "remixOf": "78dd0efa6a47",
+ "remixSource": "claude-code",
+ "designTools": false,
+ "created_at": "2026-07-25T06:17:24.143Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "running",
+ "error": null,
+ "seconds": 0,
+ "cost": null,
+ "started_at": "2026-07-25T06:17:53.534Z",
+ "finished_at": null,
+ "queued_at": "2026-07-25T06:17:53.526Z",
+ "resumes": 1
+ },
+ {
+ "model": "gemma3-12b",
+ "status": "queued",
+ "error": null,
+ "seconds": 0,
+ "cost": null,
+ "started_at": null,
+ "finished_at": null,
+ "queued_at": "2026-07-25T06:17:53.528Z",
+ "resumes": 1
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "queued",
+ "error": null,
+ "seconds": 0,
+ "cost": null,
+ "started_at": null,
+ "finished_at": null,
+ "queued_at": "2026-07-25T06:17:53.529Z",
+ "resumes": 1
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "running",
+ "error": null,
+ "seconds": 0,
+ "cost": null,
+ "started_at": "2026-07-25T06:17:53.541Z",
+ "finished_at": null,
+ "queued_at": "2026-07-25T06:17:53.531Z",
+ "resumes": 1
+ },
+ {
+ "model": "hf-qwen-coder-32b",
+ "status": "queued",
+ "error": null,
+ "seconds": 0,
+ "cost": null,
+ "started_at": null,
+ "finished_at": null,
+ "queued_at": "2026-07-25T06:17:53.532Z",
+ "resumes": 1
+ }
+ ],
+ "aiPick": null,
+ "category": "Games"
+ },
+ {
+ "id": "dfaee602df0d",
+ "title": "Agent Abrams — Agent Swarm Snake",
+ "prompt": "Build a single self-contained HTML file: a Snake game reimagined as an AI agent swarm. The snake is a chain of glowing agent nodes collecting 'tasks' (pulsing tokens); each task grows the swarm and bumps a score/tokens-processed counter. Neon dark UI, smooth grid movement (arrow/WASD), self-collision game-over, restart. 60fps, no external assets. Output ONLY the HTML.",
+ "category": "Games",
+ "designTools": false,
+ "created_at": "2026-07-25T06:18:24.454Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "queued",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": null,
+ "finished_at": null,
+ "queued_at": "2026-07-25T06:18:24.456Z"
+ },
+ {
+ "model": "gemma3-12b",
+ "status": "queued",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": null,
+ "finished_at": null,
+ "queued_at": "2026-07-25T06:18:24.458Z"
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "queued",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": null,
+ "finished_at": null,
+ "queued_at": "2026-07-25T06:18:24.460Z"
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "queued",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": null,
+ "finished_at": null,
+ "queued_at": "2026-07-25T06:18:24.462Z"
+ },
+ {
+ "model": "hf-qwen-coder-32b",
+ "status": "queued",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": null,
+ "finished_at": null,
+ "queued_at": "2026-07-25T06:18:24.463Z"
+ },
+ {
+ "model": "claude-code",
+ "status": "running",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": "2026-07-25T06:18:24.466Z",
+ "finished_at": null,
+ "queued_at": "2026-07-25T06:18:24.465Z"
+ }
+ ]
}
]
\ No newline at end of file
diff --git a/fleet-defender.html b/fleet-defender.html
new file mode 100644
index 0000000..463778e
--- /dev/null
+++ b/fleet-defender.html
@@ -0,0 +1,490 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>FLEET DEFENDER — AI Command Center</title>
+<style>
+ :root{
+ --navy:#050a1a; --navy2:#0a1430; --cyan:#22e0ff; --amber:#ffb028;
+ --grid:#0f2247; --pink:#ff4d7d; --green:#5affc0;
+ }
+ *{margin:0;padding:0;box-sizing:border-box}
+ html,body{height:100%;background:var(--navy);overflow:hidden;
+ font-family:"Courier New",ui-monospace,monospace;color:var(--cyan)}
+ #wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center}
+ #stage{position:relative;width:min(100vw,calc(100vh*0.66));height:100%;
+ max-height:100vh;box-shadow:0 0 60px #0a1e4a inset}
+ canvas{display:block;width:100%;height:100%;
+ background:radial-gradient(120% 90% at 50% 8%,#0b1636 0%,#050a1a 70%)}
+ .hud{position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;
+ padding:14px 18px;pointer-events:none;font-size:14px;letter-spacing:2px;
+ text-transform:uppercase;text-shadow:0 0 8px currentColor}
+ .hud .lbl{opacity:.55;font-size:10px;display:block}
+ .hud .amber{color:var(--amber)}
+ .hud .val{font-size:20px;font-weight:bold}
+ .lives i{display:inline-block;width:12px;height:12px;margin-left:5px;
+ background:var(--cyan);clip-path:polygon(50% 0,100% 78%,50% 60%,0 78%);
+ box-shadow:0 0 6px var(--cyan)}
+ .overlay{position:absolute;inset:0;display:flex;flex-direction:column;
+ align-items:center;justify-content:center;text-align:center;gap:18px;
+ background:radial-gradient(60% 60% at 50% 45%,rgba(8,16,40,.82),rgba(5,10,26,.96));
+ backdrop-filter:blur(2px)}
+ .overlay.hide{display:none}
+ h1{font-size:clamp(30px,7vw,58px);letter-spacing:6px;color:var(--cyan);
+ text-shadow:0 0 18px var(--cyan),0 0 40px #0af}
+ h1 span{color:var(--amber);text-shadow:0 0 18px var(--amber)}
+ .sub{color:var(--amber);letter-spacing:3px;font-size:13px;opacity:.85}
+ .panel{border:1px solid #17335f;background:rgba(10,22,52,.5);padding:16px 22px;
+ max-width:80%;line-height:1.9;font-size:13px;letter-spacing:1px;color:#9fd6ff}
+ .k{color:var(--amber)}
+ button{pointer-events:auto;cursor:pointer;font-family:inherit;font-size:15px;
+ letter-spacing:3px;text-transform:uppercase;color:var(--navy);font-weight:bold;
+ background:linear-gradient(180deg,var(--cyan),#12a6c9);border:none;
+ padding:14px 34px;border-radius:2px;box-shadow:0 0 20px var(--cyan)}
+ button:hover{background:linear-gradient(180deg,#8ff3ff,var(--cyan))}
+ .go{color:var(--pink);text-shadow:0 0 18px var(--pink),0 0 44px #900}
+ .flash{color:var(--amber);animation:bl 1s steps(2) infinite}
+ @keyframes bl{50%{opacity:.25}}
+</style>
+</head>
+<body>
+<div id="wrap">
+<div id="stage">
+ <canvas id="c"></canvas>
+ <div class="hud">
+ <div><span class="lbl">Score</span><span class="val" id="score">0</span></div>
+ <div style="text-align:center"><span class="lbl amber">Wave</span><span class="val amber" id="wave">1</span></div>
+ <div style="text-align:right"><span class="lbl">Fleet</span><span class="lives" id="lives"></span></div>
+ </div>
+
+ <div class="overlay" id="startScreen">
+ <h1>FLEET <span>DEFENDER</span></h1>
+ <div class="sub">// AI AGENT COMMAND CENTER //</div>
+ <div class="panel">
+ Rogue <span class="k">BUG</span> processes are descending on the agent grid.<br>
+ Pilot the command ship. Purge every bug before it breaches the fleet.<br><br>
+ <span class="k">← →</span> move · <span class="k">SPACE</span> fire · <span class="k">P</span> pause
+ </div>
+ <button id="startBtn">Initialize</button>
+ </div>
+
+ <div class="overlay hide" id="overScreen">
+ <h1 class="go">SYSTEM BREACH</h1>
+ <div class="sub" id="overStats"></div>
+ <div class="panel" id="overMsg"></div>
+ <button id="restartBtn">Redeploy</button>
+ </div>
+
+ <div class="overlay hide" id="pauseScreen">
+ <h1 class="flash">PAUSED</h1>
+ <div class="sub">Press P to resume</div>
+ </div>
+</div>
+</div>
+
+<script>
+(function(){
+"use strict";
+const canvas=document.getElementById("c"), ctx=canvas.getContext("2d");
+const $=id=>document.getElementById(id);
+let W=0,H=0,DPR=1;
+
+// logical resolution — we scale drawing to fit
+const VW=660, VH=1000;
+
+function resize(){
+ const r=canvas.getBoundingClientRect();
+ DPR=Math.min(window.devicePixelRatio||1,2);
+ canvas.width=Math.round(r.width*DPR);
+ canvas.height=Math.round(r.height*DPR);
+ W=canvas.width; H=canvas.height;
+}
+window.addEventListener("resize",resize);
+
+// ---- input ----
+const keys={};
+addEventListener("keydown",e=>{
+ if(["ArrowLeft","ArrowRight","Space","ArrowUp"].includes(e.code))e.preventDefault();
+ keys[e.code]=true;
+ if(e.code==="KeyP"&&state==="play")togglePause();
+ else if(e.code==="KeyP"&&state==="pause")togglePause();
+});
+addEventListener("keyup",e=>{keys[e.code]=false});
+
+// ---- game state ----
+let state="start"; // start | play | pause | over
+let score=0, wave=1, lives=3, best=0;
+let ship, bugs=[], pShots=[], eShots=[], parts=[], stars=[];
+let fireCd=0, waveClearTimer=0, flashTimer=0, shakeT=0;
+let time=0;
+
+function rnd(a,b){return a+Math.random()*(b-a)}
+
+function initStars(){
+ stars=[];
+ for(let i=0;i<80;i++)stars.push({x:rnd(0,VW),y:rnd(0,VH),z:rnd(.3,1),s:rnd(.5,1.6)});
+}
+
+function newShip(){
+ return {x:VW/2,y:VH-70,w:46,h:34,speed:7,cool:0};
+}
+
+function spawnWave(n){
+ bugs=[];
+ const cols=Math.min(6+Math.floor(n/2),10);
+ const rows=Math.min(2+Math.floor((n+1)/2),6);
+ const marginX=70, gapX=(VW-marginX*2)/(cols-1||1);
+ const startY=130, gapY=64;
+ const hpBonus=Math.floor(n/4);
+ for(let r=0;r<rows;r++){
+ for(let c=0;c<cols;c++){
+ bugs.push({
+ x:marginX+c*gapX, y:startY+r*gapY,
+ bx:marginX+c*gapX, // base anchor for sway
+ w:34,h:28,
+ hp:1+ (r===0?hpBonus:0),
+ maxhp:1+(r===0?hpBonus:0),
+ row:r,col:c,
+ phase:rnd(0,Math.PI*2),
+ alive:true, dead:false
+ });
+ }
+ }
+ // horde motion params scale with wave
+ horde.dir=1;
+ horde.speed=0.5+n*0.18;
+ horde.drop=14+n*1.5;
+ horde.fireRate=Math.max(0.010,0.006+n*0.0016);
+ horde.diveChance=Math.min(0.002+n*0.0006,0.008);
+}
+const horde={dir:1,speed:1,drop:16,fireRate:0.01,diveChance:0.002,offY:0};
+
+function startGame(){
+ score=0;wave=1;lives=3;
+ ship=newShip();pShots=[];eShots=[];parts=[];
+ fireCd=0;waveClearTimer=0;flashTimer=0;
+ initStars();
+ spawnWave(wave);
+ state="play";
+ $("startScreen").classList.add("hide");
+ $("overScreen").classList.add("hide");
+ $("pauseScreen").classList.add("hide");
+ updateHUD();
+}
+function togglePause(){
+ if(state==="play"){state="pause";$("pauseScreen").classList.remove("hide");}
+ else if(state==="pause"){state="play";$("pauseScreen").classList.add("hide");}
+}
+function gameOver(){
+ state="over";
+ best=Math.max(best,score);
+ $("overStats").textContent="SCORE "+score+" · WAVE "+wave+" · BEST "+best;
+ $("overMsg").innerHTML="The bug swarm overran the agent grid.<br>Recompile the fleet and defend again.";
+ $("overScreen").classList.remove("hide");
+}
+
+function updateHUD(){
+ $("score").textContent=score;
+ $("wave").textContent=wave;
+ let h="";for(let i=0;i<lives;i++)h+="<i></i>";
+ $("lives").innerHTML=h;
+}
+
+// ---- particles ----
+function burst(x,y,color,n,spd){
+ for(let i=0;i<n;i++){
+ const a=rnd(0,Math.PI*2),v=rnd(.4,1)*spd;
+ parts.push({x,y,vx:Math.cos(a)*v,vy:Math.sin(a)*v,
+ life:1,decay:rnd(.02,.05),color,size:rnd(1.5,3.5)});
+ }
+}
+
+// ---- update ----
+function update(dt){
+ time+=dt;
+ // stars
+ for(const s of stars){s.y+=s.z*0.6*dt;if(s.y>VH){s.y=0;s.x=rnd(0,VW);}}
+
+ // ship
+ if(keys.ArrowLeft)ship.x-=ship.speed*dt;
+ if(keys.ArrowRight)ship.x+=ship.speed*dt;
+ ship.x=Math.max(ship.w/2,Math.min(VW-ship.w/2,ship.x));
+ if(ship.cool>0)ship.cool-=dt;
+ if((keys.Space||keys.ArrowUp)&&ship.cool<=0){
+ pShots.push({x:ship.x,y:ship.y-ship.h/2,vy:-12,w:4,h:14});
+ pShots.push({x:ship.x-10,y:ship.y-ship.h/2+6,vy:-12,w:3,h:10});
+ pShots.push({x:ship.x+10,y:ship.y-ship.h/2+6,vy:-12,w:3,h:10});
+ ship.cool=Math.max(9-wave*0.4,4);
+ burst(ship.x,ship.y-ship.h/2,"#22e0ff",4,2);
+ }
+
+ // player shots
+ for(const s of pShots){s.y+=s.vy*dt;}
+ pShots=pShots.filter(s=>s.y>-20);
+
+ // horde movement (classic sweep + drop)
+ const live=bugs.filter(b=>b.alive);
+ if(live.length){
+ let minX=Infinity,maxX=-Infinity;
+ for(const b of live){minX=Math.min(minX,b.bx);maxX=Math.max(maxX,b.bx);}
+ let move=horde.dir*horde.speed*dt;
+ if(maxX+move>VW-40||minX+move<40){
+ horde.dir*=-1;
+ horde.offY+=horde.drop;
+ }else{
+ for(const b of bugs)b.bx+=move;
+ }
+ // apply position with sway
+ for(const b of bugs){
+ if(!b.alive)continue;
+ b.x=b.bx+Math.sin(time*0.03+b.phase)*6;
+ b.y=b.baseY(horde.offY,b);
+ }
+ }
+
+ // enemy fire + occasional dive
+ for(const b of live){
+ if(Math.random()<horde.fireRate*0.02*dt){
+ eShots.push({x:b.x,y:b.y+b.h/2,vy:3+wave*0.15,w:4,h:12});
+ }
+ if(!b.diving && Math.random()<horde.diveChance*0.02*dt){
+ b.diving=true;b.dvx=(ship.x-b.x)*0.008;
+ }
+ if(b.diving){
+ b.divY=(b.divY||0)+ (2.4+wave*0.1)*dt;
+ b.x+=b.dvx*dt;
+ }
+ }
+
+ for(const s of eShots)s.y+=s.vy*dt;
+ eShots=eShots.filter(s=>s.y<VH+20);
+
+ // collisions: player shots vs bugs
+ for(const s of pShots){
+ for(const b of live){
+ if(Math.abs(s.x-b.x)<b.w/2+3 && Math.abs(s.y-getBugY(b))<b.h/2+6){
+ b.hp--; s.dead=true;
+ burst(s.x,getBugY(b),"#ffb028",6,2.5);
+ if(b.hp<=0){
+ b.alive=false;
+ score+=10*wave + b.row*5;
+ burst(b.x,getBugY(b),"#ff4d7d",18,4);
+ burst(b.x,getBugY(b),"#22e0ff",10,3);
+ shakeT=Math.min(shakeT+3,8);
+ updateHUD();
+ }
+ break;
+ }
+ }
+ }
+ pShots=pShots.filter(s=>!s.dead);
+
+ // enemy shots vs ship
+ for(const s of eShots){
+ if(Math.abs(s.x-ship.x)<ship.w/2 && Math.abs(s.y-ship.y)<ship.h/2){
+ s.dead=true; hitShip();
+ }
+ }
+ // bug reaches ship / bottom
+ for(const b of live){
+ const by=getBugY(b);
+ if(by>VH-40){ hitShip(); b.alive=false; burst(b.x,by,"#ff4d7d",20,4);}
+ else if(Math.abs(b.x-ship.x)<ship.w/2+b.w/2 && Math.abs(by-ship.y)<ship.h/2+b.h/2){
+ b.alive=false; burst(b.x,by,"#ff4d7d",20,4); hitShip();
+ }
+ }
+ eShots=eShots.filter(s=>!s.dead);
+
+ // particles
+ for(const p of parts){p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=0.05*dt;p.life-=p.decay*dt;}
+ parts=parts.filter(p=>p.life>0);
+
+ if(shakeT>0)shakeT-=dt*0.5;
+ if(flashTimer>0)flashTimer-=dt;
+
+ // wave clear
+ if(bugs.length && bugs.every(b=>!b.alive)){
+ if(waveClearTimer===0)waveClearTimer=1;
+ waveClearTimer+=dt;
+ if(waveClearTimer>60){
+ wave++; horde.offY=0;
+ spawnWave(wave);
+ waveClearTimer=0;
+ updateHUD();
+ }
+ }
+}
+
+// bug vertical position helper (base + drop + dive)
+function getBugY(b){ return b.startY + horde.offY + (b.divY||0); }
+function hitShip(){
+ lives--; flashTimer=20;
+ burst(ship.x,ship.y,"#22e0ff",26,5);
+ shakeT=10;
+ updateHUD();
+ ship.x=VW/2;
+ eShots=[];
+ if(lives<=0)gameOver();
+}
+
+// patch spawnWave to set startY & helper — redefine spawn to include startY
+function fixBugs(){/* handled inline below */}
+
+// override: assign startY on spawn
+const _spawn=spawnWave;
+spawnWave=function(n){
+ _spawn(n);
+ for(const b of bugs){ b.startY=b.y; b.divY=0; b.diving=false; b.baseY=(off,bb)=>bb.startY+off+(bb.divY||0);}
+};
+
+// ---- render ----
+function drawGrid(){
+ ctx.save();
+ ctx.strokeStyle="rgba(30,70,140,0.22)";ctx.lineWidth=1;
+ const step=VW/11;
+ for(let x=0;x<=VW;x+=step){ctx.beginPath();ctx.moveTo(x,90);ctx.lineTo(x,VH);ctx.stroke();}
+ for(let y=90;y<=VH;y+=step){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(VW,y);ctx.stroke();}
+ // agent nodes at intersections
+ ctx.fillStyle="rgba(34,224,255,0.14)";
+ for(let x=0;x<=VW;x+=step*2){for(let y=110;y<=VH;y+=step*2){
+ ctx.beginPath();ctx.arc(x,y,2,0,7);ctx.fill();
+ }}
+ ctx.restore();
+}
+
+function drawShip(){
+ ctx.save();
+ ctx.translate(ship.x,ship.y);
+ const blink = flashTimer>0 && Math.floor(flashTimer/3)%2===0;
+ ctx.globalAlpha = blink?0.35:1;
+ // glow
+ ctx.shadowColor="#22e0ff";ctx.shadowBlur=18;
+ ctx.fillStyle="#22e0ff";
+ ctx.beginPath();
+ ctx.moveTo(0,-ship.h/2);
+ ctx.lineTo(ship.w/2,ship.h/2);
+ ctx.lineTo(ship.w*0.18,ship.h*0.3);
+ ctx.lineTo(-ship.w*0.18,ship.h*0.3);
+ ctx.lineTo(-ship.w/2,ship.h/2);
+ ctx.closePath();ctx.fill();
+ // core
+ ctx.shadowBlur=10;ctx.fillStyle="#eafcff";
+ ctx.beginPath();ctx.arc(0,-2,5,0,7);ctx.fill();
+ // engine flare
+ ctx.shadowColor="#ffb028";ctx.fillStyle="#ffb028";
+ const f=6+Math.sin(time*0.4)*3;
+ ctx.beginPath();ctx.moveTo(-6,ship.h/2);ctx.lineTo(0,ship.h/2+f);ctx.lineTo(6,ship.h/2);ctx.fill();
+ ctx.restore();
+}
+
+function drawBug(b){
+ const y=getBugY(b);
+ ctx.save();
+ ctx.translate(b.x,y);
+ const pulse=0.5+0.5*Math.sin(time*0.08+b.phase);
+ ctx.shadowColor="#ff4d7d";ctx.shadowBlur=10;
+ // body — hexagonal bug node
+ ctx.fillStyle= b.maxhp>1? "#ff6a4d":"#ff4d7d";
+ ctx.beginPath();
+ const r=b.w/2;
+ for(let i=0;i<6;i++){const a=i/6*Math.PI*2+time*0.01;
+ const px=Math.cos(a)*r, py=Math.sin(a)*r*0.8;
+ i?ctx.lineTo(px,py):ctx.moveTo(px,py);}
+ ctx.closePath();ctx.fill();
+ // eye
+ ctx.shadowBlur=6;ctx.fillStyle="#0a1430";
+ ctx.beginPath();ctx.arc(0,0,r*0.42,0,7);ctx.fill();
+ ctx.fillStyle=`rgba(90,255,192,${0.5+pulse*0.5})`;
+ ctx.beginPath();ctx.arc(0,0,r*0.22,0,7);ctx.fill();
+ // legs
+ ctx.strokeStyle="#ff4d7d";ctx.lineWidth=2;ctx.shadowBlur=4;
+ for(const sx of [-1,1]){
+ ctx.beginPath();
+ ctx.moveTo(sx*r*0.6,0);ctx.lineTo(sx*(r+6),-4+Math.sin(time*0.1+b.phase)*3);
+ ctx.moveTo(sx*r*0.6,3);ctx.lineTo(sx*(r+6),8+Math.sin(time*0.1+b.phase)*3);
+ ctx.stroke();
+ }
+ ctx.restore();
+}
+
+function render(){
+ ctx.save();
+ ctx.scale(W/VW,H/VH);
+ ctx.clearRect(0,0,VW,VH);
+
+ if(shakeT>0){ctx.translate(rnd(-shakeT,shakeT),rnd(-shakeT,shakeT));}
+
+ // stars
+ for(const s of stars){
+ ctx.globalAlpha=s.z;ctx.fillStyle="#7fd8ff";
+ ctx.fillRect(s.x,s.y,s.s,s.s);
+ }
+ ctx.globalAlpha=1;
+
+ drawGrid();
+
+ // player shots
+ ctx.shadowColor="#22e0ff";ctx.shadowBlur=8;
+ for(const s of pShots){ctx.fillStyle="#eafcff";ctx.fillRect(s.x-s.w/2,s.y-s.h/2,s.w,s.h);}
+ // enemy shots
+ ctx.shadowColor="#ffb028";
+ for(const s of eShots){ctx.fillStyle="#ffb028";ctx.fillRect(s.x-s.w/2,s.y-s.h/2,s.w,s.h);}
+ ctx.shadowBlur=0;
+
+ for(const b of bugs)if(b.alive)drawBug(b);
+ if(ship)drawShip();
+
+ // particles
+ for(const p of parts){
+ ctx.globalAlpha=Math.max(0,p.life);
+ ctx.fillStyle=p.color;
+ ctx.fillRect(p.x-p.size/2,p.y-p.size/2,p.size,p.size);
+ }
+ ctx.globalAlpha=1;
+
+ // wave banner
+ if(waveClearTimer>0){
+ ctx.fillStyle="rgba(34,224,255,0.9)";ctx.font="bold 34px 'Courier New',monospace";
+ ctx.textAlign="center";ctx.shadowColor="#22e0ff";ctx.shadowBlur=20;
+ ctx.fillText("WAVE "+(wave)+" CLEARED",VW/2,VH/2);
+ ctx.font="14px 'Courier New',monospace";ctx.fillStyle="#ffb028";
+ ctx.fillText("INCOMING: WAVE "+(wave+1),VW/2,VH/2+30);
+ ctx.shadowBlur=0;
+ }
+ ctx.restore();
+
+ // subtle breach vignette flash
+ if(flashTimer>0){
+ ctx.save();ctx.globalAlpha=Math.min(flashTimer/40,0.4);
+ ctx.fillStyle="#ff4d7d";ctx.fillRect(0,0,W,H);ctx.restore();
+ }
+}
+
+// ---- loop (fixed-step for consistent 60fps feel) ----
+let last=performance.now(),acc=0;
+const STEP=1000/60;
+function loop(now){
+ let delta=now-last;last=now;
+ if(delta>100)delta=100;
+ if(state==="play"){
+ acc+=delta;
+ while(acc>=STEP){update(1);acc-=STEP;}
+ }
+ render();
+ requestAnimationFrame(loop);
+}
+
+// ---- boot ----
+$("startBtn").onclick=startGame;
+$("restartBtn").onclick=startGame;
+resize();
+initStars();
+ship=newShip();
+spawnWave(1);
+// draw idle background behind start screen
+requestAnimationFrame(loop);
+})();
+</script>
+</body>
+</html>
diff --git a/idea-run/.dw-aa-pointer b/idea-run/.dw-aa-pointer
new file mode 100644
index 0000000..00750ed
--- /dev/null
+++ b/idea-run/.dw-aa-pointer
@@ -0,0 +1 @@
+3
diff --git a/idea-run/dw-aa-challenges.txt b/idea-run/dw-aa-challenges.txt
new file mode 100644
index 0000000..235836d
--- /dev/null
+++ b/idea-run/dw-aa-challenges.txt
@@ -0,0 +1,16 @@
+Agent Abrams — Fleet Defender|Build a single self-contained HTML file: a playable arcade game FLEET DEFENDER themed as an AI-agent command center. Player pilots a glowing command ship at the bottom (arrow keys move, spacebar fires) defending the fleet from descending 'bug' invaders. Neon command-center look (dark navy, cyan/amber HUD, agent-node grid), score, wave number, lives, rising difficulty, particle explosions, game-over + restart. 60fps canvas, keyboard controls, no external assets. Output ONLY the HTML.
+Agent Abrams — Agent Swarm Snake|Build a single self-contained HTML file: a Snake game reimagined as an AI agent swarm. The snake is a chain of glowing agent nodes collecting 'tasks' (pulsing tokens); each task grows the swarm and bumps a score/tokens-processed counter. Neon dark UI, smooth grid movement (arrow/WASD), self-collision game-over, restart. 60fps, no external assets. Output ONLY the HTML.
+Designer Wallcoverings — Pattern Tile Match|Build a single self-contained HTML file: a relaxing tile-matching puzzle where the player swaps adjacent wallpaper-swatch tiles (elegant damask/floral/geometric CSS-drawn patterns) to make rows of 3+; matches clear with a shimmer and add score. 6x6 grid, mouse/touch swap, move counter, restart. Luxury muted palette (oatmeal, sage, blush, charcoal). Output ONLY the HTML.
+Designer Wallcoverings — Room Repaint Visualizer|Build a single self-contained HTML file: an interactive room visualizer. A CSS/canvas-drawn living room with a large feature wall; a side palette of 8 wallcovering swatches (CSS-generated damask/grasscloth/geometric textures); clicking a swatch instantly re-skins the feature wall with a smooth crossfade. Include a day/night lighting toggle. Luxury interior aesthetic. Output ONLY the HTML.
+Designer Wallcoverings — Seamless Pattern Studio|Build a single self-contained HTML file: a generative seamless-wallpaper pattern maker. Sliders control motif type (dots/leaves/diamonds/waves), scale, rotation, and a 5-color luxury palette; the canvas renders a live tiling repeat that visibly seams correctly. A 'Randomize' and 'Download PNG' button. 60fps. Output ONLY the HTML.
+Designer Wallcoverings — Color Story Builder|Build a single self-contained HTML file: an interior color-palette tool. Pick a base hue on a wheel and it generates a curated 5-swatch wallcovering color story (analogous/complementary/triadic toggle) with tasteful names (Oatmeal, Celadon, Alabaster, Greige). Copy-hex on click, live preview strip. Elegant editorial layout. Output ONLY the HTML.
+Agent Abrams — Fleet Command Dashboard|Build a single self-contained HTML file: an AI-agent fleet command-center dashboard mockup. A dark grid of ~12 agent status cards (name, uptime, tasks/hr, a live-updating mini sparkline via random walk), a top KPI ribbon (agents online, tasks today, $ saved), and a pulsing 'healthy/degraded' status dot per card. Cyan/amber neon on navy. Pure CSS/JS, self-animating. Output ONLY the HTML.
+Agent Abrams — Agent Network Constellation|Build a single self-contained HTML file: an animated force-directed constellation of AI agents. Nodes (agents) connected by glowing edges (message passing); nodes gently drift and edges pulse when a 'message' travels along them. Hover a node to highlight its neighbors. Dark space aesthetic, cyan glow, 60fps canvas. Output ONLY the HTML.
+Designer Wallcoverings — Sortable Product Catalog|Build a single self-contained HTML file: a luxury wallcovering product catalog grid of 12 sample products (CSS-drawn swatch, name, collection, price). Include a working sort dropdown (Newest, Price low-high, Name A-Z, Color) AND a density slider that changes the grid column count, both persisted. Elegant editorial card design, hover lift. Output ONLY the HTML.
+Designer Wallcoverings — Wallpaper Calculator|Build a single self-contained HTML file: a wallpaper-quantity calculator. Inputs for wall width, height, and number of walls, plus roll dimensions and pattern-repeat; it computes rolls needed (with 10% waste), total coverage, and estimated cost. Clean luxury form UI, live recompute, print-friendly result card. Output ONLY the HTML.
+Agent Abrams — Task Pipeline Kanban|Build a single self-contained HTML file: a self-animating agent task-pipeline kanban board (Queued -> Running -> Done -> Shipped). ~10 task cards auto-advance columns over time with a smooth slide; each card shows an agent avatar dot, title, and elapsed timer. Dark neon UI, counts per column. Pure CSS/JS. Output ONLY the HTML.
+Designer Wallcoverings — Damask Kaleidoscope|Build a single self-contained HTML file: a mesmerizing damask kaleidoscope generator. A symmetric canvas renders an ever-morphing luxury damask motif in gold/charcoal/blush; mouse movement warps the symmetry, click cycles palettes. Elegant, hypnotic, 60fps. Output ONLY the HTML.
+Agent Abrams — Reaction Time Trainer|Build a single self-contained HTML file: a reflex mini-game 'Agent Reflex'. Glowing target nodes pop up at random positions; click them before they fade to score. 30-second round, combo multiplier, hits/misses, best score in localStorage, restart. Neon command-center theme. 60fps. Output ONLY the HTML.
+Designer Wallcoverings — Swatch Memory Match|Build a single self-contained HTML file: a memory card-flip game using pairs of luxury wallcovering swatches (CSS-drawn patterns). 4x4 grid, flip two to match, moves + timer, win screen, restart. Refined muted palette, smooth 3D flip animation. Output ONLY the HTML.
+Agent Abrams — Cost Savings Ticker|Build a single self-contained HTML file: a cinematic 'value delivered' dashboard for an AI agent fleet. A big animated odometer counts up total $ saved and eng-hours saved; below, a scrolling feed of recent 'wins' (title + $ impact) and a bar chart of savings by category. Dark premium UI, count-up easing. Pure CSS/JS. Output ONLY the HTML.
+Designer Wallcoverings — Texture Weave Simulator|Build a single self-contained HTML file: an interactive grasscloth/weave texture simulator on canvas. Sliders control weave density, fiber color (2-tone), and sheen; the canvas renders a realistic woven wallcovering texture that updates live, with a subtle light sweep. Luxury natural palette. 60fps. Output ONLY the HTML.
diff --git a/idea-run/run-next.sh b/idea-run/run-next.sh
new file mode 100755
index 0000000..91e70c9
--- /dev/null
+++ b/idea-run/run-next.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+# run-next.sh — fire the next DW/Agent-Abrams challenge (all $0 local/CLI models),
+# advance a resumable pointer, wrap around the idea file to run all night.
+set -euo pipefail
+ROOT="$HOME/Projects/model-arena"
+IDEAS="$ROOT/idea-run/dw-aa-challenges.txt"
+PTR="$ROOT/idea-run/.dw-aa-pointer"
+N=$(grep -c '' "$IDEAS")
+
+IDX=$(cat "$PTR" 2>/dev/null || echo 2) # line 1 (Fleet Defender) fired manually
+[ "$IDX" -gt "$N" ] && IDX=1 # wrap: loop the pool all night
+
+LINE=$(sed -n "${IDX}p" "$IDEAS")
+TITLE="${LINE%%|*}"; PROMPT="${LINE#*|}"
+
+python3 - "$TITLE" "$PROMPT" > /tmp/dwaa-payload.json <<'PY'
+import json,sys
+json.dump({"title":sys.argv[1],"prompt":sys.argv[2],
+ "models":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code"]},
+ sys.stdout)
+PY
+
+ID=$(curl -s -m 25 -u admin:DW2024! -X POST http://127.0.0.1:9758/api/challenges \
+ -H 'Content-Type: application/json' -d @/tmp/dwaa-payload.json \
+ | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('id') or ('ERR:'+str(d.get('error'))))")
+
+echo "$((IDX+1))" > "$PTR"
+echo "FIRED idx=$IDX/$N id=$ID title=$TITLE"
diff --git a/public/arcade.html b/public/arcade.html
new file mode 100644
index 0000000..7d3d7dc
--- /dev/null
+++ b/public/arcade.html
@@ -0,0 +1,146 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Model Arena — Games Arcade</title>
+<style>
+ :root { --cols: 4; }
+ * { box-sizing: border-box; }
+ body { margin:0; background:#07080f; color:#dfe4ff; font:14px/1.5 ui-monospace,Menlo,Consolas,monospace; }
+ a { color:#00e5ff; text-decoration:none; }
+ header { display:flex; align-items:center; gap:16px; flex-wrap:wrap;
+ padding:18px 22px; border-bottom:1px solid #1a1f3a; position:sticky; top:0; background:#07080fdd; backdrop-filter:blur(6px); z-index:5; }
+ h1 { font-size:18px; letter-spacing:3px; margin:0; }
+ h1 b { color:#00e5ff; }
+ .sub { color:#7d84ad; font-size:12px; }
+ .spacer { flex:1; }
+ .ctrl { display:flex; align-items:center; gap:7px; color:#7d84ad; font-size:12px; }
+ select, input[type=range] { accent-color:#00e5ff; }
+ select { background:#0d0f1c; color:#dfe4ff; border:1px solid #26305c; border-radius:6px; padding:4px 8px; font:inherit; }
+ .grid { display:grid; grid-template-columns:repeat(var(--cols), 1fr); gap:16px; padding:22px; }
+ .tile { border:1px solid #1e2342; background:#0d0f1c; border-radius:10px; overflow:hidden; cursor:pointer;
+ transition:transform .12s ease, border-color .12s ease, box-shadow .12s ease; display:flex; flex-direction:column; }
+ .tile:hover { transform:translateY(-3px); border-color:#00e5ff; box-shadow:0 10px 30px -12px #00e5ff55; }
+ .tile.crowned { border-color:#ffc72e55; }
+ .shot { aspect-ratio:16/10; background:#05060c center/cover no-repeat; position:relative; border-bottom:1px solid #1e2342; }
+ .shot .ph { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; font-size:34px; opacity:.6; }
+ .shot .play { position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
+ font-size:40px; background:#05060caa; opacity:0; transition:opacity .12s; }
+ .tile:hover .shot .play { opacity:1; }
+ .meta { padding:10px 12px; }
+ .meta .t { font-size:13px; color:#eef1ff; margin:0 0 5px; line-height:1.35; }
+ .badges { display:flex; gap:6px; flex-wrap:wrap; font-size:10.5px; }
+ .b { padding:1px 7px; border-radius:20px; border:1px solid #26305c; color:#9aa2cf; }
+ .b.win { border-color:#ffc72e; color:#ffc72e; }
+ .b.ai { border-color:#00e5ff55; color:#00e5ff; }
+ .b.feat { border-color:#b06bff77; color:#c79bff; }
+ .empty { padding:60px 22px; color:#7d84ad; text-align:center; }
+ /* play overlay */
+ #ov { position:fixed; inset:0; background:#05060cf2; z-index:20; display:none; flex-direction:column; }
+ #ov.on { display:flex; }
+ #ovbar { display:flex; align-items:center; gap:14px; padding:12px 18px; border-bottom:1px solid #1a1f3a; }
+ #ovbar .x { cursor:pointer; color:#7d84ad; font-size:20px; border:1px solid #26305c; border-radius:6px; padding:1px 11px; }
+ #ovbar .x:hover { color:#fff; border-color:#00e5ff; }
+ #ovframe { flex:1; width:100%; border:0; background:#000; }
+ footer { color:#4c5378; font-size:11px; padding:16px 22px; border-top:1px solid #1a1f3a; }
+</style>
+</head>
+<body>
+<header>
+ <h1><b>ARENA</b> ARCADE 🎮</h1>
+ <span class="sub" id="count">loading…</span>
+ <span class="spacer"></span>
+ <span class="ctrl">sort
+ <select id="sort">
+ <option value="newest">Newest</option>
+ <option value="title">Title A→Z</option>
+ <option value="model">Model</option>
+ <option value="score">AI Score ↓</option>
+ </select>
+ </span>
+ <span class="ctrl">density
+ <input type="range" id="density" min="2" max="7" step="1" value="4">
+ </span>
+ <a href="/" class="sub">← arena</a>
+</header>
+
+<div class="grid" id="grid"></div>
+<div class="empty" id="empty" style="display:none">No games crowned yet — run a Games challenge in the arena and crown a winner.</div>
+
+<div id="ov">
+ <div id="ovbar">
+ <b id="ovtitle" style="color:#00e5ff"></b>
+ <span class="sub" id="ovmeta"></span>
+ <span class="spacer" style="flex:1"></span>
+ <a id="ovnew" class="sub" target="_blank">open ↗</a>
+ <span class="x" id="ovx">✕ close</span>
+ </div>
+ <iframe id="ovframe" sandbox="allow-scripts allow-pointer-lock"></iframe>
+</div>
+
+<footer>Games are untrusted model output, played in a sandboxed frame (no network). Crowned 👑 = human pick · 🤖 = AI referee pick. Auto-updates as new Games challenges are won.</footer>
+
+<script>
+const $ = s => document.querySelector(s);
+let GAMES = [];
+
+// persisted controls (Steve's hard rule: every grid has sort + density, localStorage-persisted)
+const sortEl = $('#sort'), densEl = $('#density');
+sortEl.value = localStorage.getItem('arcade.sort') || 'newest';
+densEl.value = localStorage.getItem('arcade.density') || '4';
+document.documentElement.style.setProperty('--cols', densEl.value);
+sortEl.onchange = () => { localStorage.setItem('arcade.sort', sortEl.value); render(); };
+densEl.oninput = () => { localStorage.setItem('arcade.density', densEl.value);
+ document.documentElement.style.setProperty('--cols', densEl.value); };
+
+function sortGames(list) {
+ const s = sortEl.value, a = list.slice();
+ if (s === 'title') a.sort((x, y) => x.title.localeCompare(y.title));
+ else if (s === 'model') a.sort((x, y) => (x.label || '').localeCompare(y.label || ''));
+ else if (s === 'score') a.sort((x, y) => (y.aiScore || 0) - (x.aiScore || 0));
+ else a.sort((x, y) => String(y.created_at || '').localeCompare(String(x.created_at || ''))); // newest
+ return a;
+}
+
+function render() {
+ const grid = $('#grid'); grid.innerHTML = '';
+ const list = sortGames(GAMES);
+ $('#empty').style.display = list.length ? 'none' : 'block';
+ for (const g of list) {
+ const el = document.createElement('div');
+ el.className = 'tile' + (g.crowned ? ' crowned' : '');
+ const badges = [
+ g.standalone ? '<span class="b feat">★ FEATURED</span>' : '',
+ g.crowned && !g.standalone ? '<span class="b win">👑 WON</span>' : '',
+ g.aiPick ? '<span class="b ai">🤖 AI PICK</span>' : '',
+ g.label ? `<span class="b">${g.label}</span>` : '',
+ g.aiScore != null ? `<span class="b">🤖 ${g.aiScore}</span>` : '',
+ ].filter(Boolean).join('');
+ el.innerHTML =
+ `<div class="shot" ${g.thumb ? `style="background-image:url('${g.thumb}')"` : ''}>
+ ${g.thumb ? '' : '<div class="ph">🎮</div>'}<div class="play">▶</div></div>
+ <div class="meta"><p class="t">${g.title}</p><div class="badges">${badges}</div></div>`;
+ el.onclick = () => play(g);
+ grid.appendChild(el);
+ }
+}
+
+function play(g) {
+ $('#ovtitle').textContent = g.title;
+ $('#ovmeta').textContent = (g.label ? g.label + (g.crowned && !g.standalone ? ' 👑' : '') : '') + (g.aiScore != null ? ' · 🤖 ' + g.aiScore : '');
+ $('#ovnew').href = g.play;
+ $('#ovframe').src = g.play;
+ $('#ov').classList.add('on');
+}
+$('#ovx').onclick = () => { $('#ov').classList.remove('on'); $('#ovframe').src = 'about:blank'; };
+document.addEventListener('keydown', e => { if (e.key === 'Escape') $('#ovx').click(); });
+
+fetch('/api/arcade').then(r => r.json()).then(d => {
+ GAMES = d.games || [];
+ $('#count').textContent = GAMES.length + ' playable games';
+ render();
+}).catch(() => { $('#count').textContent = 'failed to load'; });
+</script>
+</body>
+</html>
diff --git a/star-clock.html b/public/games/star-clock.html
similarity index 100%
rename from star-clock.html
rename to public/games/star-clock.html
diff --git a/tetris.html b/public/games/tetris.html
similarity index 100%
rename from tetris.html
rename to public/games/tetris.html
diff --git a/public/index.html b/public/index.html
index dac9e3d..9a219b9 100644
--- a/public/index.html
+++ b/public/index.html
@@ -157,6 +157,7 @@ tr.clk{cursor:pointer}tr.clk:hover td{background:rgba(0,229,255,.06)}
<button id="nav-challenges" class="on">Challenges</button>
<button id="nav-new">+ New Battle</button>
<button id="nav-board">Leaderboard</button>
+ <a href="/arcade" style="text-decoration:none"><button type="button">🎮 Arcade</button></a>
</nav>
</header>
<div class="costbar" id="costbar" title="Total metered API spend across the arena (local models + Claude Max CLI are $0)">
diff --git a/server.js b/server.js
index cc09fe0..7899618 100644
--- a/server.js
+++ b/server.js
@@ -860,6 +860,64 @@ iframe{width:100%;height:460px;border:0;background:#000}.c.win iframe{height:560
return res.end(fs.readFileSync(fp));
}
+ // ── Games Arcade ────────────────────────────────────────────────
+ // Auto-discovers every crowned/AI-picked Games-category challenge as a
+ // playable tile, plus the hand-picked standalone champions in public/games.
+ if (p === '/api/arcade') {
+ const GAMEY = /\b(game|tetris|snake|maze|breakout|pong|invader|defender|flappy|asteroid|platformer|puzzle|arcade|shoot|2048|memory|driving|dragon|fireworks|synthwave|starfield|conway|life|neon|visualizer|particle|physics|domino|bounce|rope|pathfind)\b/i;
+ const games = [];
+ for (const c of challenges) {
+ const isGame = (c.category === 'Games') || GAMEY.test((c.title || '') + ' ' + (c.prompt || ''));
+ if (!isGame) continue;
+ // champion = human crown → AI pick → highest AI score among done runs
+ let champ = c.winner || c.aiPick;
+ if (!champ) {
+ const done = (c.runs || []).filter(r => r.status === 'done' && r.thumb);
+ done.sort((a, b) => (b.aiScore || 0) - (a.aiScore || 0));
+ champ = done[0] && done[0].model;
+ }
+ if (!champ) continue;
+ if (!fs.existsSync(path.join(ART, c.id, champ + '.html'))) continue;
+ const run = (c.runs || []).find(r => r.model === champ) || {};
+ games.push({
+ id: c.id, title: c.title, model: champ, label: mLabelServer(champ),
+ created_at: c.created_at, category: c.category || 'Games',
+ crowned: c.winner === champ, aiPick: c.aiPick === champ,
+ aiScore: typeof run.aiScore === 'number' ? run.aiScore : null,
+ play: '/artifact/' + c.id + '/' + champ, thumb: '/thumb/' + c.id + '/' + champ,
+ standalone: false,
+ });
+ }
+ // featured standalone champions
+ let feat = { featured: [] };
+ try { feat = JSON.parse(fs.readFileSync(path.join(DATA, 'arcade-games.json'), 'utf8')); } catch {}
+ for (const g of (feat.featured || [])) {
+ if (!fs.existsSync(path.join(DIR, 'public', 'games', g.slug + '.html'))) continue;
+ games.push({
+ id: 'featured-' + g.slug, title: g.title, model: g.credit || '', label: mLabelServer(g.credit || ''),
+ created_at: null, category: 'Featured', crowned: true, aiPick: false, aiScore: null,
+ play: '/game/' + g.slug, thumb: null, standalone: true, note: g.note || '',
+ });
+ }
+ return send(res, 200, { games, count: games.length });
+ }
+ if (p === '/arcade' || p === '/arcade.html') {
+ const fp = path.join(DIR, 'public', 'arcade.html');
+ if (!fs.existsSync(fp)) { res.writeHead(404); return res.end('no arcade'); }
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
+ return res.end(fs.readFileSync(fp));
+ }
+ if ((m = p.match(/^\/game\/([a-z0-9_-]+)$/i))) {
+ const fp = path.join(DIR, 'public', 'games', path.basename(m[1]) + '.html');
+ if (!fs.existsSync(fp)) { res.writeHead(404); return res.end('no game'); }
+ res.writeHead(200, {
+ 'Content-Type': 'text/html; charset=utf-8',
+ 'Content-Security-Policy': "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; media-src data:; font-src data:",
+ 'X-Frame-Options': 'SAMEORIGIN',
+ });
+ return res.end(fs.readFileSync(fp));
+ }
+
const f = p === '/' ? 'index.html' : p.replace(/^\//, '');
const fp = path.join(DIR, 'public', path.basename(f));
if (fs.existsSync(fp) && fs.statSync(fp).isFile()) {
← 5c09122 auto-save: 2026-07-24T20:59:44 (4 files) — data/artifacts/7b
·
back to Model Arena
·
night-loop: cycle 23:19 — judged=4a6e2cfce746 · (backlog 2 — 5917c15 →