← back to Model Arena
arena: move Build-in-iTerm from whole-challenge to per-MODEL-CHIP — pick which model's attempt to build; brief embeds that model's actual artifact so Claude builds it forward. Per-pane ⚒ in detail view too.
1e4746aa45d838e56b55b62ad226249328e271b1 · 2026-07-23 06:50:56 -0700 · Steve
Files touched
A data/artifacts/ca355985ca95/qwen3-14b.htmlA data/artifacts/ca355985ca95/qwen3-14b.pngM data/challenges.jsonM data/votes.jsonlM public/index.html
Diff
commit 1e4746aa45d838e56b55b62ad226249328e271b1
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 23 06:50:56 2026 -0700
arena: move Build-in-iTerm from whole-challenge to per-MODEL-CHIP — pick which model's attempt to build; brief embeds that model's actual artifact so Claude builds it forward. Per-pane ⚒ in detail view too.
---
data/artifacts/ca355985ca95/qwen3-14b.html | 100 +++++++++++++++++++++++++++++
data/artifacts/ca355985ca95/qwen3-14b.png | Bin 0 -> 2727 bytes
data/challenges.json | 35 +++++-----
data/votes.jsonl | 1 +
public/index.html | 32 ++++-----
5 files changed, 136 insertions(+), 32 deletions(-)
diff --git a/data/artifacts/ca355985ca95/qwen3-14b.html b/data/artifacts/ca355985ca95/qwen3-14b.html
new file mode 100644
index 0000000..90eea87
--- /dev/null
+++ b/data/artifacts/ca355985ca95/qwen3-14b.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ html, body { margin:0; overflow:hidden; }
+ canvas { display: block; }
+ </style>
+</head>
+<body>
+<canvas></canvas>
+<script>
+const canvas = document.querySelector('canvas');
+const ctx = canvas.getContext('2d');
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+
+let particles = [];
+const numParticles = 2000;
+const letters = 'ARENA'.split('');
+const springConstant = 0.005;
+const damping = 0.8;
+const formationDuration = 2000; // ms
+const holdingDuration = 1000; // ms
+const explosionDuration = 1000; // ms
+const reformDuration = 2000; // ms
+
+function createParticles() {
+ const letterCount = letters.length;
+ const particlesPerLetter = Math.floor(numParticles / letterCount);
+ const fontSize = 100;
+ const textWidth = 5 * fontSize * 0.6;
+ const textX = (canvas.width - textWidth) / 2;
+ const textY = canvas.height / 2;
+ const letterWidth = fontSize * 0.6;
+ const letterHeight = fontSize;
+
+ particles = [];
+ for (let i = 0; i < numParticles; i++) {
+ const letterIndex = Math.floor(i / particlesPerLetter) % letterCount;
+ const startX = textX + letterIndex * letterWidth;
+ const startY = textY - letterHeight / 2;
+ const targetX = startX + Math.random() * letterWidth;
+ const targetY = startY + Math.random() * letterHeight;
+ const x = Math.random() * canvas.width;
+ const y = Math.random() * canvas.height;
+ particles.push({
+ x, y,
+ vx: 0,
+ vy: 0,
+ ax: 0,
+ ay: 0,
+ targetX, targetY,
+ group: letterIndex
+ });
+ }
+}
+
+createParticles();
+
+let state = 'forming';
+let stateStartTime = performance.now();
+
+function updateParticles() {
+ const now = performance.now();
+ const deltaTime = (now - stateStartTime) / 1000;
+
+ switch (state) {
+ case 'forming':
+ if (deltaTime >= formationDuration / 1000) {
+ state = 'holding';
+ stateStartTime = now;
+ }
+ break;
+ case 'holding':
+ if (deltaTime >= holdingDuration / 1000) {
+ state = 'exploding';
+ stateStartTime = now;
+ }
+ break;
+ case 'exploding':
+ if (deltaTime >= explosionDuration / 1000) {
+ state = 'reforming';
+ stateStartTime = now;
+ }
+ break;
+ case 'reforming':
+ if (deltaTime >= reformDuration / 1000) {
+ state = 'forming';
+ stateStartTime = now;
+ }
+ break;
+ }
+
+ for (let p of particles) {
+ let ax = 0, ay = 0;
+ if (state === 'forming' || state === 'reforming') {
+ const dx = p.targetX - p.x;
+ const dy = p.targetY - p.y;
+ ax = dx * springConstant;
+ ay = dy *
\ No newline at end of file
diff --git a/data/artifacts/ca355985ca95/qwen3-14b.png b/data/artifacts/ca355985ca95/qwen3-14b.png
new file mode 100644
index 0000000..16a103c
Binary files /dev/null and b/data/artifacts/ca355985ca95/qwen3-14b.png differ
diff --git a/data/challenges.json b/data/challenges.json
index b2734a4..51d33f3 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -1569,31 +1569,33 @@
"prompt": "Build a single self-contained HTML file: a canvas particle system where ~2000 particles assemble into the word \"ARENA\", hold, then explode outward and reform on click. Smooth 60fps, glowing neon particles, physics-based motion. Output ONLY the HTML.",
"category": "Games",
"created_at": "2026-07-23T13:46:18.891Z",
- "winner": null,
+ "winner": "qwen25-7b",
"runs": [
{
"model": "qwen3-14b",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 137,
+ "cost": 0,
"started_at": "2026-07-23T13:47:44.578Z",
- "finished_at": null
+ "finished_at": "2026-07-23T13:50:01.322Z",
+ "bytes": 2597,
+ "thumb": true
},
{
"model": "gemma3-12b",
- "status": "queued",
- "error": null,
- "seconds": null,
+ "status": "error",
+ "error": "server restarted mid-run",
+ "seconds": 0,
"cost": null,
- "started_at": null,
+ "started_at": "2026-07-23T13:50:01.324Z",
"finished_at": null
},
{
"model": "hermes3-8b",
- "status": "queued",
- "error": null,
- "seconds": null,
+ "status": "error",
+ "error": "server restarted mid-run",
+ "seconds": 0,
"cost": null,
"started_at": null,
"finished_at": null
@@ -1611,9 +1613,9 @@
},
{
"model": "kimi",
- "status": "running",
- "error": null,
- "seconds": null,
+ "status": "error",
+ "error": "server restarted mid-run",
+ "seconds": 0,
"cost": null,
"started_at": "2026-07-23T13:48:22.281Z",
"finished_at": null
@@ -1639,6 +1641,7 @@
"bytes": 5051,
"thumb": true
}
- ]
+ ],
+ "voted_at": "2026-07-23T13:50:02.948Z"
}
]
\ No newline at end of file
diff --git a/data/votes.jsonl b/data/votes.jsonl
index c4ee3f1..145a347 100644
--- a/data/votes.jsonl
+++ b/data/votes.jsonl
@@ -5,3 +5,4 @@
{"ts":"2026-07-23T06:13:14.106Z","challenge":"02bc35a3d0b2","title":"Propagation all-7","winner":"grok","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","kimi","gpt","grok"]}
{"ts":"2026-07-23T13:10:47.373Z","challenge":"08d1c975f6db","title":"Daily: Bouncing Balls","winner":"gemma3-12b","field":["gemma3-12b","hermes3-8b","qwen25-7b"]}
{"ts":"2026-07-23T13:35:47.844Z","challenge":"f2af82652fcd","title":"california real estate brokers","winner":"grok","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","kimi","gpt","grok"]}
+{"ts":"2026-07-23T13:50:02.948Z","challenge":"ca355985ca95","title":"Particle Text Reactor","winner":"qwen25-7b","field":["qwen3-14b","qwen25-7b","gpt","grok"]}
diff --git a/public/index.html b/public/index.html
index 193dfae..a239ffc 100644
--- a/public/index.html
+++ b/public/index.html
@@ -47,11 +47,11 @@ input[type=range]{accent-color:var(--neon);width:130px}
.chip.queued{border-color:var(--dim)}
.chip.error{border-color:var(--err);color:var(--err)}
.chip.winner{border-color:var(--gold);color:var(--gold);font-weight:700}
-.mini.build{margin-top:10px;background:none;border:1px solid var(--neon2);color:var(--neon2);padding:5px 12px;font:inherit;font-size:11px;
- letter-spacing:1px;text-transform:uppercase;cursor:pointer;border-radius:3px;width:100%}
-.mini.build:hover{background:rgba(255,46,166,.12);box-shadow:0 0 10px rgba(255,46,166,.25)}
-.mini.build:disabled{opacity:.6;cursor:default}
-#d-build{color:var(--neon2)}
+.chip[data-build-model]{cursor:pointer}
+.chip[data-build-model] .bx{color:var(--neon2);opacity:.55;font-weight:400}
+.chip[data-build-model]:hover{border-color:var(--neon2);box-shadow:0 0 8px rgba(255,46,166,.3)}
+.chip[data-build-model]:hover .bx{opacity:1}
+.chip.launched{border-color:var(--ok);color:var(--ok)}
@keyframes pulse{50%{opacity:.35}}
.empty{color:var(--dim);padding:40px;text-align:center;border:1px dashed var(--line)}
/* new challenge */
@@ -186,7 +186,6 @@ tr.clk{cursor:pointer}tr.clk:hover td{background:rgba(0,229,255,.06)}
<section id="detail" class="view">
<span class="back" id="back">← back to challenges</span>
<a class="back" id="export" style="margin-left:16px" download>⬇ export battle as standalone HTML</a>
- <span class="back" id="d-build" style="margin-left:16px">⚒ Build in iTerm</span>
<label class="blindtog"><input type="checkbox" id="blind"> 🕶 Blind judging — hide model names until a winner is crowned (kills brand bias)</label>
<h2 id="d-title"></h2>
<div class="meta" id="d-meta"></div>
@@ -291,21 +290,21 @@ async function loadList(){
div.innerHTML = `<h3>${esc(c.title)}</h3>
<div class="when" title="${c.created_at}">🕓 ${fmtWhen(c.created_at)}</div>
${shotsHtml}
- <div class="chips">${chips}</div>
- <button class="mini build" data-build="${c.id}" title="Open a real Claude session in iTerm2, pre-loaded with this challenge, to actually build it">⚒ Build in iTerm</button>`;
- div.onclick = (e)=>{ if(e.target.dataset.build) return; openDetail(c.id); };
- div.querySelector('[data-build]').onclick = (e)=>{ e.stopPropagation(); buildInIterm(c.id, e.target); };
+ <div class="chips">${chips}</div>`;
+ div.onclick = (e)=>{ const chip=e.target.closest('[data-build-model]'); if(chip){ e.stopPropagation(); buildInIterm(c.id, chip, chip.dataset.buildModel); return; } openDetail(c.id); };
el.appendChild(div);
}
}
function esc(s){ return String(s).replace(/[&<>"]/g, c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); }
-async function buildInIterm(id, btn){
- const t = btn && btn.textContent; if(btn){ btn.textContent='⚒ launching…'; btn.disabled=true; }
+async function buildInIterm(id, el, model){
+ const t = el && el.innerHTML; if(el){ el.style.opacity=.6; }
try{
- const r = await fetch(API+'/api/challenges/'+id+'/build',{method:'POST'});
+ const url = API+'/api/challenges/'+id+'/build'+(model?'/'+model:'');
+ const r = await fetch(url,{method:'POST'});
if(!r.ok){ alert('Build launch failed: '+(await r.text())); }
- else if(btn){ btn.textContent='⚒ launched ↗'; setTimeout(()=>{btn.textContent=t;btn.disabled=false;},2500); }
- }catch(e){ alert('Build launch failed: '+e.message); if(btn){btn.textContent=t;btn.disabled=false;} }
+ else if(el){ el.classList.add('launched'); setTimeout(()=>{el.style.opacity=1;el.classList.remove('launched');},2000); }
+ }catch(e){ alert('Build launch failed: '+e.message); }
+ finally{ if(el) el.style.opacity=1; }
}
// ---- new battle ----
@@ -372,7 +371,6 @@ async function renderDetail(id, statusOnly){
const remixNote = c.remixOf ? '🧬 remixed from ' + mLabel(c.remixSource) + "'s artifact\n" : '';
$('#d-meta').textContent = '🕓 ' + fmtWhen(c.created_at) + (c.category?' · '+c.category:'') + '\n' + remixNote + c.prompt;
$('#export').href = API+'/export/'+c.id;
- $('#d-build').onclick = (e)=>buildInIterm(c.id, e.target);
// AI referee status bar
const anyArt = c.runs.some(r=>r.status==='done'&&r.thumb);
const ai = $('#d-ai');
@@ -436,6 +434,7 @@ async function renderDetail(id, statusOnly){
${run.status==='done'&&c.winner!==run.model?`<button class="btn" data-win="${run.model}" title="Crown this model the winner of this battle — records a vote into the real-world win-rate ledger. One winner per battle; you can re-crown.">👑 Crown</button>`:''}
${!anon&&c.aiPick===run.model?'<span class="chip aipick">🤖 AI PICK</span>':''}
${c.winner===run.model?'<span class="chip winner">WINNER</span>':''}
+ ${run.status==='done'?`<button class="btn pink" data-build="${run.model}" title="Build ${mLabel(run.model)}'s artifact into a real project in an iTerm Claude session">⚒</button>`:''}
${run.status==='done'?`<button class="btn" data-src="${c.id}/${run.model}" title="View HTML source"></></button>`:''}
${(run.status==='error'||run.status==='done')?`<button class="btn pink" data-retry="${run.model}">↻</button>`:''}
</span>
@@ -454,6 +453,7 @@ async function renderDetail(id, statusOnly){
const [cid,mid]=el.dataset.run.split('/');
el.outerHTML = `<iframe sandbox="allow-scripts" src="${API}/artifact/${cid}/${mid}"></iframe>`;
});
+ arena.querySelectorAll('[data-build]').forEach(b=>b.onclick=e=>buildInIterm(c.id, e.target, e.target.dataset.build));
arena.querySelectorAll('[data-src]').forEach(b=>b.onclick=async e=>{
const [cid,mid]=e.target.dataset.src.split('/');
const body = e.target.closest('.pane').querySelector('iframe,.poster,.msg,.srcview');
← 2897af6 auto-save: 2026-07-23T06:49:07 (7 files) — data/challenges.j
·
back to Model Arena
·
resilience: global unhandledRejection/uncaughtException hand b25a81b →