[object Object]

← back to Model Arena

auto-save: 2026-07-23T11:50:46 (15 files) — data/challenges.json data/costlog.jsonl data/votes.jsonl data/artifacts/04692f72ef1d/ data/artifacts/0cb3fc06b069/

d6dfe90e78fc0744e1834162eecd7448c0f9c224 · 2026-07-23 11:50:54 -0700 · Steve Abrams

Files touched

Diff

commit d6dfe90e78fc0744e1834162eecd7448c0f9c224
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 23 11:50:54 2026 -0700

    auto-save: 2026-07-23T11:50:46 (15 files) — data/challenges.json data/costlog.jsonl data/votes.jsonl data/artifacts/04692f72ef1d/ data/artifacts/0cb3fc06b069/
---
 data/artifacts/04692f72ef1d/claude-code.html       |  372 +++++++
 data/artifacts/04692f72ef1d/claude-code.png        |  Bin 0 -> 124382 bytes
 data/artifacts/04692f72ef1d/gemma3-12b.html        |  260 +++++
 data/artifacts/04692f72ef1d/gemma3-12b.png         |  Bin 0 -> 6026 bytes
 data/artifacts/04692f72ef1d/gpt.html               |  611 ++++++++++
 data/artifacts/04692f72ef1d/gpt.png                |  Bin 0 -> 15556 bytes
 data/artifacts/04692f72ef1d/grok.html              |  351 ++++++
 data/artifacts/04692f72ef1d/grok.png               |  Bin 0 -> 32194 bytes
 data/artifacts/04692f72ef1d/hermes3-8b.html        |  338 ++++++
 data/artifacts/04692f72ef1d/hermes3-8b.png         |  Bin 0 -> 5531 bytes
 data/artifacts/04692f72ef1d/hf-qwen-coder-32b.html |  190 ++++
 data/artifacts/04692f72ef1d/hf-qwen-coder-32b.png  |  Bin 0 -> 8409 bytes
 data/artifacts/04692f72ef1d/kimi.html              |  495 +++++++++
 data/artifacts/04692f72ef1d/kimi.png               |  Bin 0 -> 13994 bytes
 data/artifacts/04692f72ef1d/qwen25-7b.html         |   71 ++
 data/artifacts/04692f72ef1d/qwen25-7b.png          |  Bin 0 -> 2811 bytes
 data/artifacts/04692f72ef1d/qwen3-14b.html         |  267 +++++
 data/artifacts/04692f72ef1d/qwen3-14b.png          |  Bin 0 -> 6170 bytes
 data/artifacts/0cb3fc06b069/claude-code.html       |  265 +++++
 data/artifacts/0cb3fc06b069/claude-code.png        |  Bin 0 -> 240817 bytes
 data/artifacts/0cb3fc06b069/gemma3-12b.html        |  122 ++
 data/artifacts/0cb3fc06b069/gemma3-12b.png         |  Bin 0 -> 7527 bytes
 data/artifacts/0cb3fc06b069/hermes3-8b.html        |  106 ++
 data/artifacts/0cb3fc06b069/hermes3-8b.png         |  Bin 0 -> 2805 bytes
 data/artifacts/0cb3fc06b069/hf-qwen-coder-32b.html |  105 ++
 data/artifacts/0cb3fc06b069/hf-qwen-coder-32b.png  |  Bin 0 -> 7344 bytes
 data/artifacts/0cb3fc06b069/qwen25-7b.html         |   99 ++
 data/artifacts/0cb3fc06b069/qwen25-7b.png          |  Bin 0 -> 2811 bytes
 data/artifacts/0cb3fc06b069/qwen3-14b.html         |  143 +++
 data/artifacts/0cb3fc06b069/qwen3-14b.png          |  Bin 0 -> 103515 bytes
 data/artifacts/1d4944290382/claude-code.html       |  390 +++++++
 data/artifacts/1d4944290382/claude-code.png        |  Bin 0 -> 84632 bytes
 data/artifacts/1d4944290382/gemma3-12b.html        |  139 +++
 data/artifacts/1d4944290382/gemma3-12b.png         |  Bin 0 -> 4456 bytes
 data/artifacts/1d4944290382/hermes3-8b.html        |  105 ++
 data/artifacts/1d4944290382/hermes3-8b.png         |  Bin 0 -> 8277 bytes
 data/artifacts/1d4944290382/hf-qwen-coder-32b.html |  114 ++
 data/artifacts/1d4944290382/hf-qwen-coder-32b.png  |  Bin 0 -> 10442 bytes
 data/artifacts/1d4944290382/qwen25-7b.html         |   77 ++
 data/artifacts/1d4944290382/qwen25-7b.png          |  Bin 0 -> 14513 bytes
 data/artifacts/1d4944290382/qwen3-14b.html         |  177 +++
 data/artifacts/1d4944290382/qwen3-14b.png          |  Bin 0 -> 10160 bytes
 data/artifacts/68b40458a368/claude-code.html       |  350 ++++++
 data/artifacts/68b40458a368/claude-code.png        |  Bin 0 -> 321308 bytes
 data/artifacts/68b40458a368/gemma3-12b.html        |  195 ++++
 data/artifacts/68b40458a368/gemma3-12b.png         |  Bin 0 -> 5697 bytes
 data/artifacts/68b40458a368/qwen25-7b.html         |   95 ++
 data/artifacts/68b40458a368/qwen25-7b.png          |  Bin 0 -> 4471 bytes
 data/artifacts/68b40458a368/qwen3-14b.html         |  210 ++++
 data/artifacts/68b40458a368/qwen3-14b.png          |  Bin 0 -> 11609 bytes
 data/artifacts/7bf57916e775/hf-qwen-coder-32b.html |  224 ++++
 data/artifacts/7bf57916e775/hf-qwen-coder-32b.png  |  Bin 0 -> 8490 bytes
 data/artifacts/7bf57916e775/kimi.html              |  499 +++++++++
 data/artifacts/7bf57916e775/kimi.png               |  Bin 0 -> 52422 bytes
 data/artifacts/9cbc4e3c60a6/gemma3-12b.html        |   69 ++
 data/artifacts/9cbc4e3c60a6/gemma3-12b.png         |  Bin 0 -> 4396 bytes
 data/artifacts/9cbc4e3c60a6/gpt.html               |  400 +++++++
 data/artifacts/9cbc4e3c60a6/gpt.png                |  Bin 0 -> 53344 bytes
 data/artifacts/9cbc4e3c60a6/grok.html              |  447 ++++++++
 data/artifacts/9cbc4e3c60a6/grok.png               |  Bin 0 -> 89741 bytes
 data/artifacts/9cbc4e3c60a6/hermes3-8b.html        |   76 ++
 data/artifacts/9cbc4e3c60a6/hermes3-8b.png         |  Bin 0 -> 4317 bytes
 data/artifacts/9cbc4e3c60a6/hf-qwen-coder-32b.html |   61 +
 data/artifacts/9cbc4e3c60a6/hf-qwen-coder-32b.png  |  Bin 0 -> 43973 bytes
 data/artifacts/9cbc4e3c60a6/kimi.html              |  258 +++++
 data/artifacts/9cbc4e3c60a6/kimi.png               |  Bin 0 -> 85442 bytes
 data/artifacts/9cbc4e3c60a6/qwen25-7b.html         |   54 +
 data/artifacts/9cbc4e3c60a6/qwen25-7b.png          |  Bin 0 -> 7469 bytes
 data/artifacts/9cbc4e3c60a6/qwen3-14b.html         |  100 ++
 data/artifacts/9cbc4e3c60a6/qwen3-14b.png          |  Bin 0 -> 6991 bytes
 data/artifacts/e840f1972643/claude-code.html       |  554 +++++++++
 data/artifacts/e840f1972643/claude-code.png        |  Bin 0 -> 240976 bytes
 data/artifacts/e840f1972643/gemma3-12b.html        |  239 ++++
 data/artifacts/e840f1972643/gemma3-12b.png         |  Bin 0 -> 4977 bytes
 data/artifacts/e840f1972643/gpt.html               |  381 +++++++
 data/artifacts/e840f1972643/gpt.png                |  Bin 0 -> 103677 bytes
 data/artifacts/e840f1972643/grok.html              |  476 ++++++++
 data/artifacts/e840f1972643/grok.png               |  Bin 0 -> 147498 bytes
 data/artifacts/e840f1972643/hermes3-8b.html        |  210 ++++
 data/artifacts/e840f1972643/hermes3-8b.png         |  Bin 0 -> 2727 bytes
 data/artifacts/e840f1972643/hf-qwen-coder-32b.html |  164 +++
 data/artifacts/e840f1972643/hf-qwen-coder-32b.png  |  Bin 0 -> 5450 bytes
 data/artifacts/e840f1972643/kimi.html              |  359 ++++++
 data/artifacts/e840f1972643/kimi.png               |  Bin 0 -> 82713 bytes
 data/artifacts/e840f1972643/qwen25-7b.html         |   78 ++
 data/artifacts/e840f1972643/qwen25-7b.png          |  Bin 0 -> 2803 bytes
 data/artifacts/e840f1972643/qwen3-14b.html         |  270 +++++
 data/artifacts/e840f1972643/qwen3-14b.png          |  Bin 0 -> 5519 bytes
 data/artifacts/ebf4f7984098/claude-code.html       |  475 ++++++++
 data/artifacts/ebf4f7984098/claude-code.png        |  Bin 0 -> 313897 bytes
 data/artifacts/ebf4f7984098/gemma3-12b.html        |  286 +++++
 data/artifacts/ebf4f7984098/gemma3-12b.png         |  Bin 0 -> 12302 bytes
 data/artifacts/ebf4f7984098/gpt.html               |  581 ++++++++++
 data/artifacts/ebf4f7984098/gpt.png                |  Bin 0 -> 245629 bytes
 data/artifacts/ebf4f7984098/grok.html              |  670 +++++++++++
 data/artifacts/ebf4f7984098/grok.png               |  Bin 0 -> 483560 bytes
 data/artifacts/ebf4f7984098/hermes3-8b.html        |  156 +++
 data/artifacts/ebf4f7984098/hermes3-8b.png         |  Bin 0 -> 4039 bytes
 data/artifacts/ebf4f7984098/hf-qwen-coder-32b.html |   19 +
 data/artifacts/ebf4f7984098/hf-qwen-coder-32b.png  |  Bin 0 -> 24335 bytes
 data/artifacts/ebf4f7984098/kimi.html              |  608 ++++++++++
 data/artifacts/ebf4f7984098/kimi.png               |  Bin 0 -> 179024 bytes
 data/artifacts/ebf4f7984098/qwen25-7b.html         |  138 +++
 data/artifacts/ebf4f7984098/qwen25-7b.png          |  Bin 0 -> 117631 bytes
 data/artifacts/ebf4f7984098/qwen3-14b.html         |  309 ++++++
 data/artifacts/ebf4f7984098/qwen3-14b.png          |  Bin 0 -> 71062 bytes
 data/challenges.json                               | 1170 +++++++++++++++++++-
 data/costlog.jsonl                                 |   13 +
 data/votes.jsonl                                   |    8 +
 tetris.html                                        |  372 +++++++
 110 files changed, 15355 insertions(+), 16 deletions(-)

diff --git a/data/artifacts/04692f72ef1d/claude-code.html b/data/artifacts/04692f72ef1d/claude-code.html
new file mode 100644
index 0000000..4e0b4cc
--- /dev/null
+++ b/data/artifacts/04692f72ef1d/claude-code.html
@@ -0,0 +1,372 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Tetris</title>
+<style>
+  :root{
+    --bg:#0d1117; --panel:#161b22; --grid:#1c2330; --line:#2a3345;
+    --text:#e6edf3; --muted:#8b949e; --accent:#58a6ff;
+  }
+  *{box-sizing:border-box; margin:0; padding:0;}
+  html,body{height:100%;}
+  body{
+    background:radial-gradient(circle at 50% -10%, #1a2233, var(--bg));
+    color:var(--text);
+    font-family:"Segoe UI",system-ui,-apple-system,sans-serif;
+    display:flex; align-items:center; justify-content:center;
+    min-height:100vh; padding:16px; user-select:none;
+  }
+  .wrap{
+    display:flex; gap:20px; align-items:flex-start;
+    flex-wrap:wrap; justify-content:center;
+  }
+  .board-shell{position:relative;}
+  canvas{
+    background:var(--grid);
+    border:2px solid var(--line);
+    border-radius:8px;
+    display:block;
+    box-shadow:0 10px 40px rgba(0,0,0,.5);
+  }
+  .side{
+    display:flex; flex-direction:column; gap:16px; width:180px;
+  }
+  .card{
+    background:var(--panel); border:1px solid var(--line);
+    border-radius:10px; padding:14px;
+  }
+  .card h2{
+    font-size:11px; letter-spacing:2px; text-transform:uppercase;
+    color:var(--muted); margin-bottom:8px; font-weight:600;
+  }
+  .stat{font-size:28px; font-weight:700; font-variant-numeric:tabular-nums;}
+  #next{display:block; margin:0 auto; background:var(--grid); border-radius:6px;}
+  .keys{font-size:12px; line-height:1.9; color:var(--muted);}
+  .keys b{color:var(--text); font-weight:600;}
+  kbd{
+    background:#21262d; border:1px solid #30363d; border-bottom-width:2px;
+    border-radius:4px; padding:1px 6px; font-size:11px; color:var(--text);
+  }
+  .title{
+    font-size:26px; font-weight:800; letter-spacing:4px;
+    background:linear-gradient(90deg,#58a6ff,#bc8cff,#ff7b72);
+    -webkit-background-clip:text; background-clip:text; color:transparent;
+    text-align:center; margin-bottom:4px;
+  }
+  .overlay{
+    position:absolute; inset:0; display:flex; flex-direction:column;
+    align-items:center; justify-content:center; gap:14px;
+    background:rgba(6,9,15,.82); border-radius:8px; backdrop-filter:blur(3px);
+    text-align:center; padding:20px;
+  }
+  .overlay.hidden{display:none;}
+  .overlay h3{font-size:30px; font-weight:800; letter-spacing:1px;}
+  .overlay p{color:var(--muted); font-size:14px; max-width:220px;}
+  button{
+    background:var(--accent); color:#04101f; border:none;
+    padding:11px 22px; font-size:15px; font-weight:700; border-radius:8px;
+    cursor:pointer; letter-spacing:.5px; transition:transform .08s, filter .15s;
+  }
+  button:hover{filter:brightness(1.1);}
+  button:active{transform:scale(.96);}
+</style>
+</head>
+<body>
+  <div class="wrap">
+    <div class="board-shell">
+      <canvas id="board" width="300" height="600"></canvas>
+      <div id="overlay" class="overlay">
+        <h3 id="ovTitle">TETRIS</h3>
+        <p id="ovText">Stack the blocks, clear the lines.</p>
+        <button id="startBtn">Start Game</button>
+      </div>
+    </div>
+    <div class="side">
+      <div class="title">TETRIS</div>
+      <div class="card">
+        <h2>Score</h2>
+        <div class="stat" id="score">0</div>
+      </div>
+      <div class="card">
+        <h2>Next</h2>
+        <canvas id="next" width="120" height="120"></canvas>
+      </div>
+      <div class="card" style="display:flex; gap:12px;">
+        <div><h2>Lines</h2><div class="stat" id="lines" style="font-size:20px;">0</div></div>
+        <div><h2>Level</h2><div class="stat" id="level" style="font-size:20px;">1</div></div>
+      </div>
+      <div class="card keys">
+        <h2>Controls</h2>
+        <div><kbd>←</kbd> <kbd>→</kbd> <b>Move</b></div>
+        <div><kbd>↑</kbd> <b>Rotate</b></div>
+        <div><kbd>↓</kbd> <b>Soft drop</b></div>
+        <div><kbd>Space</kbd> <b>Hard drop</b></div>
+        <div><kbd>P</kbd> <b>Pause</b></div>
+      </div>
+    </div>
+  </div>
+
+<script>
+(function(){
+  "use strict";
+  const COLS = 10, ROWS = 20, CELL = 30;
+  const board   = document.getElementById("board");
+  const ctx     = board.getContext("2d");
+  const nextCv  = document.getElementById("next");
+  const nctx    = nextCv.getContext("2d");
+  const scoreEl = document.getElementById("score");
+  const linesEl = document.getElementById("lines");
+  const levelEl = document.getElementById("level");
+  const overlay = document.getElementById("overlay");
+  const ovTitle = document.getElementById("ovTitle");
+  const ovText  = document.getElementById("ovText");
+  const startBtn= document.getElementById("startBtn");
+
+  // Tetromino shapes (rotation states derived by matrix rotation from base)
+  const SHAPES = {
+    I: {color:"#4ad9d9", cells:[[0,1],[1,1],[2,1],[3,1]], size:4},
+    O: {color:"#f2d94e", cells:[[1,0],[2,0],[1,1],[2,1]], size:4},
+    T: {color:"#b06cf0", cells:[[1,0],[0,1],[1,1],[2,1]], size:3},
+    S: {color:"#5ad863", cells:[[1,0],[2,0],[0,1],[1,1]], size:3},
+    Z: {color:"#f2604e", cells:[[0,0],[1,0],[1,1],[2,1]], size:3},
+    J: {color:"#5a86f0", cells:[[0,0],[0,1],[1,1],[2,1]], size:3},
+    L: {color:"#f0a03c", cells:[[2,0],[0,1],[1,1],[2,1]], size:3}
+  };
+  const TYPES = Object.keys(SHAPES);
+
+  let grid, cur, nextType, score, lines, level, dropInterval, dropTimer;
+  let running=false, paused=false, over=false;
+
+  function emptyGrid(){
+    return Array.from({length:ROWS}, ()=>Array(COLS).fill(null));
+  }
+
+  function makePiece(type){
+    const s = SHAPES[type];
+    // build a size x size matrix
+    const m = Array.from({length:s.size},()=>Array(s.size).fill(0));
+    s.cells.forEach(([x,y])=> m[y][x]=1);
+    return {
+      type, color:s.color, matrix:m,
+      x: Math.floor((COLS - s.size)/2), y:-topOffset(m)
+    };
+  }
+  function topOffset(m){
+    for(let y=0;y<m.length;y++) if(m[y].some(v=>v)) return y;
+    return 0;
+  }
+
+  function rotate(matrix){
+    const n = matrix.length;
+    const r = Array.from({length:n},()=>Array(n).fill(0));
+    for(let y=0;y<n;y++) for(let x=0;x<n;x++) r[x][n-1-y]=matrix[y][x];
+    return r;
+  }
+
+  function collide(piece, offX, offY, matrix){
+    const m = matrix || piece.matrix;
+    for(let y=0;y<m.length;y++){
+      for(let x=0;x<m[y].length;x++){
+        if(!m[y][x]) continue;
+        const nx = piece.x + x + offX;
+        const ny = piece.y + y + offY;
+        if(nx<0 || nx>=COLS || ny>=ROWS) return true;
+        if(ny>=0 && grid[ny][nx]) return true;
+      }
+    }
+    return false;
+  }
+
+  function merge(piece){
+    piece.matrix.forEach((row,y)=>row.forEach((v,x)=>{
+      if(v){
+        const ny = piece.y+y, nx = piece.x+x;
+        if(ny>=0) grid[ny][nx]=piece.color;
+      }
+    }));
+  }
+
+  function clearLines(){
+    let cleared=0;
+    for(let y=ROWS-1;y>=0;y--){
+      if(grid[y].every(c=>c)){
+        grid.splice(y,1);
+        grid.unshift(Array(COLS).fill(null));
+        cleared++; y++;
+      }
+    }
+    if(cleared){
+      const pts=[0,100,300,500,800];
+      score += pts[cleared]*level;
+      lines += cleared;
+      const newLevel = Math.floor(lines/10)+1;
+      if(newLevel!==level){ level=newLevel; dropInterval=Math.max(80, 1000-(level-1)*80); }
+      updateStats();
+    }
+  }
+
+  function spawn(){
+    cur = makePiece(nextType);
+    nextType = TYPES[Math.floor(Math.random()*TYPES.length)];
+    drawNext();
+    if(collide(cur,0,0)){ gameOver(); }
+  }
+
+  function drop(){
+    if(!collide(cur,0,1)){
+      cur.y++;
+    } else {
+      merge(cur);
+      clearLines();
+      spawn();
+    }
+    draw();
+  }
+
+  function hardDrop(){
+    let d=0;
+    while(!collide(cur,0,d+1)) d++;
+    cur.y+=d;
+    score += d*2;
+    merge(cur); clearLines(); spawn(); updateStats(); draw();
+  }
+
+  function move(dir){
+    if(!collide(cur,dir,0)) cur.x+=dir, draw();
+  }
+
+  function tryRotate(){
+    const r = rotate(cur.matrix);
+    // wall kicks
+    for(const k of [0,-1,1,-2,2]){
+      if(!collide(cur,k,0,r)){ cur.matrix=r; cur.x+=k; draw(); return; }
+    }
+  }
+
+  // ---------- rendering ----------
+  function cellRect(cx,x,y,color,ctx2){
+    ctx2.fillStyle=color;
+    ctx2.fillRect(x*cx, y*cx, cx, cx);
+    ctx2.fillStyle="rgba(255,255,255,.18)";
+    ctx2.fillRect(x*cx, y*cx, cx, cx*0.18);
+    ctx2.fillStyle="rgba(0,0,0,.22)";
+    ctx2.fillRect(x*cx, y*cx+cx*0.82, cx, cx*0.18);
+    ctx2.strokeStyle="rgba(0,0,0,.35)";
+    ctx2.lineWidth=1;
+    ctx2.strokeRect(x*cx+0.5, y*cx+0.5, cx-1, cx-1);
+  }
+
+  function draw(){
+    ctx.clearRect(0,0,board.width,board.height);
+    // grid lines
+    ctx.strokeStyle="rgba(255,255,255,.04)";
+    ctx.lineWidth=1;
+    for(let x=1;x<COLS;x++){ ctx.beginPath(); ctx.moveTo(x*CELL,0); ctx.lineTo(x*CELL,ROWS*CELL); ctx.stroke(); }
+    for(let y=1;y<ROWS;y++){ ctx.beginPath(); ctx.moveTo(0,y*CELL); ctx.lineTo(COLS*CELL,y*CELL); ctx.stroke(); }
+    // settled
+    for(let y=0;y<ROWS;y++) for(let x=0;x<COLS;x++)
+      if(grid[y][x]) cellRect(CELL,x,y,grid[y][x],ctx);
+    if(!cur) return;
+    // ghost
+    let gd=0; while(!collide(cur,0,gd+1)) gd++;
+    ctx.globalAlpha=0.22;
+    cur.matrix.forEach((row,y)=>row.forEach((v,x)=>{
+      if(v && cur.y+y+gd>=0) cellRect(CELL,cur.x+x,cur.y+y+gd,cur.color,ctx);
+    }));
+    ctx.globalAlpha=1;
+    // current
+    cur.matrix.forEach((row,y)=>row.forEach((v,x)=>{
+      if(v && cur.y+y>=0) cellRect(CELL,cur.x+x,cur.y+y,cur.color,ctx);
+    }));
+  }
+
+  function drawNext(){
+    nctx.clearRect(0,0,nextCv.width,nextCv.height);
+    const s = SHAPES[nextType];
+    const cx = 24;
+    // center it
+    let minx=99,maxx=-9,miny=99,maxy=-9;
+    s.cells.forEach(([x,y])=>{minx=Math.min(minx,x);maxx=Math.max(maxx,x);miny=Math.min(miny,y);maxy=Math.max(maxy,y);});
+    const w=(maxx-minx+1), h=(maxy-miny+1);
+    const ox=(nextCv.width/cx - w)/2 - minx;
+    const oy=(nextCv.height/cx - h)/2 - miny;
+    s.cells.forEach(([x,y])=> cellRect(cx, x+ox, y+oy, s.color, nctx));
+  }
+
+  function updateStats(){
+    scoreEl.textContent=score;
+    linesEl.textContent=lines;
+    levelEl.textContent=level;
+  }
+
+  // ---------- loop ----------
+  function tick(){
+    if(!running||paused||over) return;
+    drop();
+    dropTimer=setTimeout(tick, dropInterval);
+  }
+  function restartTimer(){
+    clearTimeout(dropTimer);
+    if(running&&!paused&&!over) dropTimer=setTimeout(tick, dropInterval);
+  }
+
+  function startGame(){
+    grid=emptyGrid();
+    score=0; lines=0; level=1; dropInterval=1000;
+    running=true; paused=false; over=false;
+    nextType=TYPES[Math.floor(Math.random()*TYPES.length)];
+    updateStats();
+    spawn(); draw();
+    overlay.classList.add("hidden");
+    clearTimeout(dropTimer);
+    dropTimer=setTimeout(tick, dropInterval);
+  }
+
+  function togglePause(){
+    if(!running||over) return;
+    paused=!paused;
+    if(paused){
+      clearTimeout(dropTimer);
+      showOverlay("PAUSED","Press P or the button to resume.","Resume");
+    } else {
+      overlay.classList.add("hidden");
+      restartTimer();
+    }
+  }
+
+  function gameOver(){
+    over=true; running=false;
+    clearTimeout(dropTimer);
+    showOverlay("GAME OVER","Score: "+score+" • Lines: "+lines,"Play Again");
+  }
+
+  function showOverlay(t,txt,btn){
+    ovTitle.textContent=t; ovText.textContent=txt; startBtn.textContent=btn;
+    overlay.classList.remove("hidden");
+  }
+
+  startBtn.addEventListener("click", ()=>{
+    if(paused && running && !over){ togglePause(); }
+    else startGame();
+  });
+
+  document.addEventListener("keydown",(e)=>{
+    if(e.key==="p"||e.key==="P"){ e.preventDefault(); togglePause(); return; }
+    if(!running||paused||over) return;
+    switch(e.key){
+      case "ArrowLeft":  e.preventDefault(); move(-1); break;
+      case "ArrowRight": e.preventDefault(); move(1); break;
+      case "ArrowUp":    e.preventDefault(); tryRotate(); break;
+      case "ArrowDown":  e.preventDefault(); drop(); score+=1; updateStats(); restartTimer(); break;
+      case " ":          e.preventDefault(); hardDrop(); restartTimer(); break;
+    }
+  });
+
+  // initial idle board
+  grid=emptyGrid(); draw(); nextType=TYPES[0]; drawNext();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/04692f72ef1d/claude-code.png b/data/artifacts/04692f72ef1d/claude-code.png
new file mode 100644
index 0000000..9baf089
Binary files /dev/null and b/data/artifacts/04692f72ef1d/claude-code.png differ
diff --git a/data/artifacts/04692f72ef1d/gemma3-12b.html b/data/artifacts/04692f72ef1d/gemma3-12b.html
new file mode 100644
index 0000000..7201ac0
--- /dev/null
+++ b/data/artifacts/04692f72ef1d/gemma3-12b.html
@@ -0,0 +1,260 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Tetris</title>
+<style>
+body { margin: 0; overflow: hidden; background-color: #000; }
+canvas { display: block; }
+#score { position: absolute; top: 10px; left: 10px; color: white; font-family: sans-serif; font-size: 20px; }
+</style>
+</head>
+<body>
+
+<div id="score">Score: 0</div>
+<canvas id="tetrisCanvas" width="240" height="400"></canvas>
+
+<script>
+const canvas = document.getElementById('tetrisCanvas');
+const ctx = canvas.getContext('2d');
+const scoreDisplay = document.getElementById('score');
+
+const gridWidth = 10;
+const gridHeight = 20;
+const blockSize = 24;
+const grid = Array(gridHeight).fill(null).map(() => Array(gridWidth).fill(0));
+let currentPiece = null;
+let nextPiece = null;
+let score = 0;
+let speed = 500; // milliseconds
+let lastTime = 0;
+let gameOver = false;
+let paused = false;
+
+const pieces = [
+  [[1, 1, 1, 1]], // I
+  [[1, 1], [1, 1]], // O
+  [[1, 1, 0], [0, 1, 1]], // S
+  [[0, 1, 1], [1, 1, 0]], // Z
+  [[1, 1, 1], [0, 0, 1]], // J
+  [[1, 1, 1], [1, 0, 0]], // L
+  [[1, 1, 1], [0, 1, 0]]  // T
+];
+
+function getRandomPiece() {
+    return pieces[Math.floor(Math.random() * pieces.length)];
+}
+
+
+function drawGrid() {
+    ctx.fillStyle = '#222';
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+    ctx.strokeStyle = '#555';
+    for (let i = 0; i < gridHeight; i++) {
+        for (let j = 0; j < gridWidth; j++) {
+            if (grid[i][j] !== 0) {
+                ctx.fillStyle = getColor(grid[i][j]);
+                ctx.fillRect(j * blockSize, i * blockSize, blockSize, blockSize);
+            }
+            ctx.strokeRect(j * blockSize, i * blockSize, blockSize, blockSize);
+        }
+    }
+}
+
+function drawPiece(piece, x, y) {
+    for (let row = 0; row < piece.length; row++) {
+        for (let col = 0; col < piece[row].length; col++) {
+            if (piece[row][col]) {
+                ctx.fillStyle = getColor(1); // Color for the current piece.  Could be dynamic later
+                ctx.fillRect((x + col) * blockSize, (y + row) * blockSize, blockSize, blockSize);
+            }
+        }
+    }
+}
+
+function getColor(value) {
+    switch (value) {
+        case 1: return 'blue';
+        case 2: return 'orange';
+        case 3: return 'yellow';
+        case 4: return 'purple';
+        case 5: return 'green';
+        case 6: return 'red';
+        case 7: return 'cyan';
+        default: return 'black';
+    }
+}
+
+function isValidMove(piece, x, y) {
+    for (let row = 0; row < piece.length; row++) {
+        for (let col = 0; col < piece[row].length; col++) {
+            if (piece[row][col]) {
+                const gridX = x + col;
+                const gridY = y + row;
+
+                if (gridX < 0 || gridX >= gridWidth || gridY >= gridHeight) {
+                    return false;
+                }
+                if (gridY >= 0 && grid[gridY][gridX] !== 0) { // Check only if within bounds
+                    return false;
+                }
+            }
+        }
+    }
+    return true;
+}
+
+function rotatePiece(piece) {
+    const rotated = [];
+    for (let col = 0; col < piece[0].length; col++) {
+        rotated.push([]);
+        for (let row = piece.length - 1; row >= 0; row--) {
+            rotated[col].push(piece[row][col]);
+        }
+    }
+    return rotated;
+}
+
+function placePiece(piece, x, y) {
+    for (let row = 0; row < piece.length; row++) {
+        for (let col = 0; col < piece[row].length; col++) {
+            if (piece[row][col]) {
+                grid[y + row][x + col] = 1; // Assign a color ID
+            }
+        }
+    }
+}
+
+function clearLines() {
+    let linesCleared = 0;
+    for (let i = gridHeight - 1; i >= 0; i--) {
+        let full = true;
+        for (let j = 0; j < gridWidth; j++) {
+            if (grid[i][j] === 0) {
+                full = false;
+                break;
+            }
+        }
+
+        if (full) {
+            linesCleared++;
+            // Shift lines down
+            for (let k = i; k < gridHeight - 1; k++) {
+                grid[k] = grid[k + 1].slice();
+            }
+            grid[gridHeight - 1] = Array(gridWidth).fill(0);
+        }
+    }
+
+    score += linesCleared * (Math.pow(2, linesCleared));
+    scoreDisplay.textContent = `Score: ${score}`;
+}
+
+function spawnNextPiece() {
+  nextPiece = getRandomPiece();
+}
+
+
+function newGame() {
+    grid.forEach(row => row.fill(0));
+    score = 0;
+    speed = 500;
+    gameOver = false;
+    paused = false;
+
+    currentPiece = getRandomPiece();
+    nextPiece = getRandomPiece();
+
+    scoreDisplay.textContent = `Score: ${score}`;
+    drawGrid();
+    drawPiece(currentPiece, gridWidth / 2 - 1, 0);
+}
+
+
+function gameLoop(timestamp) {
+    if (!gameOver && !paused) {
+        const deltaTime = timestamp - lastTime;
+        lastTime = timestamp;
+
+        if (deltaTime >= speed) {
+            moveDown();
+        }
+
+        drawGrid();
+        if (currentPiece) drawPiece(currentPiece, currentPiece.x, currentPiece.y);
+
+        requestAnimationFrame(gameLoop);
+    } else if (gameOver) {
+      // Game over screen could be added here.  For now, just show the grid.
+       drawGrid();
+       scoreDisplay.textContent = `Game Over - Score: ${score}`;
+    }
+}
+
+
+function moveDown() {
+    if (!currentPiece || !isValidMove(currentPiece.piece, currentPiece.x, currentPiece.y + 1)) {
+        placePiece(currentPiece.piece, currentPiece.x, currentPiece.y);
+        clearLines();
+        spawnNextPiece();
+
+        currentPiece = { piece: nextPiece, x: gridWidth / 2 - 1, y: 0 };
+        if (!isValidMove(currentPiece.piece, currentPiece.x, currentPiece.y)) {
+            gameOver = true;
+        }
+    } else {
+        currentPiece.y++;
+    }
+}
+
+function moveLeft() {
+    if (currentPiece && isValidMove(currentPiece.piece, currentPiece.x - 1, currentPiece.y)) {
+        currentPiece.x--;
+    }
+}
+
+function moveRight() {
+    if (currentPiece && isValidMove(currentPiece.piece, currentPiece.x + 1, currentPiece.y)) {
+        currentPiece.x++;
+    }
+}
+
+
+function rotate() {
+    const rotatedPiece = rotatePiece(currentPiece.piece);
+    if (isValidMove(rotatedPiece, currentPiece.x, currentPiece.y)) {
+        currentPiece.piece = rotatedPiece;
+    }
+}
+
+document.addEventListener('keydown', function(event) {
+    if (!gameOver && !paused) {
+        switch (event.key) {
+            case 'ArrowLeft': moveLeft(); break;
+            case 'ArrowRight': moveRight(); break;
+            case 'ArrowDown': speed = Math.max(100, speed - 50); break; // Speed up
+            case 'ArrowUp': rotate(); break;
+            case ' ': pauseGame(); break;
+        }
+    } else if (gameOver) {
+      if (event.key === "Enter") {
+          newGame();
+      }
+    }
+});
+
+function pauseGame() {
+  paused = !paused;
+  if(paused){
+     scoreDisplay.textContent = `Paused - Score: ${score}`;
+  } else {
+     scoreDisplay.textContent = `Score: ${score}`;
+  }
+}
+
+
+newGame(); // Start the game
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/04692f72ef1d/gemma3-12b.png b/data/artifacts/04692f72ef1d/gemma3-12b.png
new file mode 100644
index 0000000..b967049
Binary files /dev/null and b/data/artifacts/04692f72ef1d/gemma3-12b.png differ
diff --git a/data/artifacts/04692f72ef1d/gpt.html b/data/artifacts/04692f72ef1d/gpt.html
new file mode 100644
index 0000000..96e6903
--- /dev/null
+++ b/data/artifacts/04692f72ef1d/gpt.html
@@ -0,0 +1,611 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>Tetris Single File</title>
+<style>
+  body {
+    background:#111;
+    color:#eee;
+    font-family:Arial,Helvetica,sans-serif;
+    display:flex;
+    justify-content:center;
+    align-items:flex-start;
+    margin:0;
+    padding:10px;
+  }
+  #game-container {
+    display:flex;
+    gap:15px;
+  }
+  #side-panel {
+    display:flex;
+    flex-direction:column;
+    gap:10px;
+    font-size:14px;
+  }
+  canvas {
+    background:#000;
+    border:2px solid #444;
+    image-rendering:pixelated;
+  }
+  #info {
+    border:1px solid #444;
+    padding:6px 8px;
+    background:#181818;
+  }
+  .label {
+    color:#aaa;
+  }
+  .value {
+    font-weight:bold;
+    color:#fff;
+  }
+  #controls {
+    font-size:12px;
+    color:#ccc;
+  }
+  button {
+    padding:4px 8px;
+    background:#333;
+    border:1px solid #666;
+    color:#eee;
+    cursor:pointer;
+  }
+  button:hover {
+    background:#444;
+  }
+</style>
+</head>
+<body>
+<div id="game-container">
+  <canvas id="game" width="200" height="400"></canvas>
+  <div id="side-panel">
+    <canvas id="next" width="80" height="80"></canvas>
+    <div id="info">
+      <div><span class="label">Score:</span> <span class="value" id="score">0</span></div>
+      <div><span class="label">Lines:</span> <span class="value" id="lines">0</span></div>
+      <div><span class="label">Level:</span> <span class="value" id="level">1</span></div>
+    </div>
+    <div id="controls">
+      <div><b>Controls</b></div>
+      <div>← → : Move</div>
+      <div>↑ : Rotate</div>
+      <div>↓ : Soft drop</div>
+      <div>Space : Hard drop</div>
+      <div>P : Pause/Resume</div>
+      <div>R : Restart</div>
+    </div>
+    <button id="restartBtn">Restart</button>
+  </div>
+</div>
+
+<script>
+(function() {
+  const COLS = 10;
+  const ROWS = 20;
+  const BLOCK = 20;
+  const CANVAS_W = COLS*BLOCK;
+  const CANVAS_H = ROWS*BLOCK;
+
+  const canvas = document.getElementById('game');
+  const ctx = canvas.getContext('2d');
+  const nextCanvas = document.getElementById('next');
+  const nctx = nextCanvas.getContext('2d');
+
+  canvas.width = CANVAS_W;
+  canvas.height = CANVAS_H;
+
+  const scoreEl = document.getElementById('score');
+  const linesEl = document.getElementById('lines');
+  const levelEl = document.getElementById('level');
+  const restartBtn = document.getElementById('restartBtn');
+
+  const COLORS = {
+    0: "#000000",
+    I: "#00f0f0",
+    J: "#0000f0",
+    L: "#f0a000",
+    O: "#f0f000",
+    S: "#00f000",
+    T: "#a000f0",
+    Z: "#f00000"
+  };
+
+  // Tetromino shapes with rotation states (4x4 matrix each)
+  const SHAPES = {
+    I: [
+      [
+        [0,0,0,0],
+        [1,1,1,1],
+        [0,0,0,0],
+        [0,0,0,0]
+      ],
+      [
+        [0,0,1,0],
+        [0,0,1,0],
+        [0,0,1,0],
+        [0,0,1,0]
+      ],
+      [
+        [0,0,0,0],
+        [0,0,0,0],
+        [1,1,1,1],
+        [0,0,0,0]
+      ],
+      [
+        [0,1,0,0],
+        [0,1,0,0],
+        [0,1,0,0],
+        [0,1,0,0]
+      ]
+    ],
+    J: [
+      [
+        [1,0,0],
+        [1,1,1],
+        [0,0,0]
+      ],
+      [
+        [0,1,1],
+        [0,1,0],
+        [0,1,0]
+      ],
+      [
+        [0,0,0],
+        [1,1,1],
+        [0,0,1]
+      ],
+      [
+        [0,1,0],
+        [0,1,0],
+        [1,1,0]
+      ]
+    ],
+    L: [
+      [
+        [0,0,1],
+        [1,1,1],
+        [0,0,0]
+      ],
+      [
+        [0,1,0],
+        [0,1,0],
+        [0,1,1]
+      ],
+      [
+        [0,0,0],
+        [1,1,1],
+        [1,0,0]
+      ],
+      [
+        [1,1,0],
+        [0,1,0],
+        [0,1,0]
+      ]
+    ],
+    O: [
+      [
+        [0,1,1,0],
+        [0,1,1,0],
+        [0,0,0,0],
+        [0,0,0,0]
+      ],
+      [
+        [0,1,1,0],
+        [0,1,1,0],
+        [0,0,0,0],
+        [0,0,0,0]
+      ],
+      [
+        [0,1,1,0],
+        [0,1,1,0],
+        [0,0,0,0],
+        [0,0,0,0]
+      ],
+      [
+        [0,1,1,0],
+        [0,1,1,0],
+        [0,0,0,0],
+        [0,0,0,0]
+      ]
+    ],
+    S: [
+      [
+        [0,1,1],
+        [1,1,0],
+        [0,0,0]
+      ],
+      [
+        [0,1,0],
+        [0,1,1],
+        [0,0,1]
+      ],
+      [
+        [0,0,0],
+        [0,1,1],
+        [1,1,0]
+      ],
+      [
+        [1,0,0],
+        [1,1,0],
+        [0,1,0]
+      ]
+    ],
+    T: [
+      [
+        [0,1,0],
+        [1,1,1],
+        [0,0,0]
+      ],
+      [
+        [0,1,0],
+        [0,1,1],
+        [0,1,0]
+      ],
+      [
+        [0,0,0],
+        [1,1,1],
+        [0,1,0]
+      ],
+      [
+        [0,1,0],
+        [1,1,0],
+        [0,1,0]
+      ]
+    ],
+    Z: [
+      [
+        [1,1,0],
+        [0,1,1],
+        [0,0,0]
+      ],
+      [
+        [0,0,1],
+        [0,1,1],
+        [0,1,0]
+      ],
+      [
+        [0,0,0],
+        [1,1,0],
+        [0,1,1]
+      ],
+      [
+        [0,1,0],
+        [1,1,0],
+        [1,0,0]
+      ]
+    ]
+  };
+
+  const TETROMINOES = Object.keys(SHAPES);
+
+  let grid, current, nextPiece, dropStart, dropInterval;
+  let score, lines, level;
+  let gameOver, paused;
+  let requestId;
+
+  function resetGrid() {
+    grid = [];
+    for (let r = 0; r < ROWS; r++) {
+      const row = [];
+      for (let c = 0; c < COLS; c++) {
+        row.push(0);
+      }
+      grid.push(row);
+    }
+  }
+
+  function randomPiece() {
+    const type = TETROMINOES[Math.floor(Math.random() * TETROMINOES.length)];
+    return {
+      type,
+      rotation: 0,
+      x: 3,
+      y: -2,
+      shape: SHAPES[type][0]
+    };
+  }
+
+  function rotatePiece(p, dir=1) {
+    const len = SHAPES[p.type].length;
+    const newRot = (p.rotation + dir + len) % len;
+    return {
+      ...p,
+      rotation: newRot,
+      shape: SHAPES[p.type][newRot]
+    };
+  }
+
+  function collide(p, offX = 0, offY = 0) {
+    const m = p.shape;
+    for (let r = 0; r < m.length; r++) {
+      for (let c = 0; c < m[r].length; c++) {
+        if (!m[r][c]) continue;
+        const nx = p.x + c + offX;
+        const ny = p.y + r + offY;
+        if (ny >= ROWS || nx < 0 || nx >= COLS) return true;
+        if (ny >= 0 && grid[ny][nx]) return true;
+      }
+    }
+    return false;
+  }
+
+  function merge(p) {
+    const m = p.shape;
+    for (let r = 0; r < m.length; r++) {
+      for (let c = 0; c < m[r].length; c++) {
+        if (!m[r][c]) continue;
+        const nx = p.x + c;
+        const ny = p.y + r;
+        if (ny >= 0 && ny < ROWS && nx >= 0 && nx < COLS) {
+          grid[ny][nx] = p.type;
+        }
+      }
+    }
+  }
+
+  function clearLines() {
+    let cleared = 0;
+    outer: for (let r = ROWS - 1; r >= 0; r--) {
+      for (let c = 0; c < COLS; c++) {
+        if (!grid[r][c]) continue outer;
+      }
+      grid.splice(r, 1);
+      grid.unshift(new Array(COLS).fill(0));
+      cleared++;
+      r++;
+    }
+    if (cleared > 0) {
+      lines += cleared;
+      const lineScores = [0, 40, 100, 300, 1200];
+      score += lineScores[cleared] * level;
+      const newLevel = 1 + Math.floor(lines / 10);
+      if (newLevel !== level) {
+        level = newLevel;
+        updateDropInterval();
+      }
+      updateHUD();
+    }
+  }
+
+  function updateHUD() {
+    scoreEl.textContent = score;
+    linesEl.textContent = lines;
+    levelEl.textContent = level;
+  }
+
+  function drawBlock(x, y, color, context = ctx, size = BLOCK) {
+    context.fillStyle = color;
+    context.fillRect(x * size, y * size, size, size);
+    context.strokeStyle = "#222";
+    context.lineWidth = 1;
+    context.strokeRect(x * size, y * size, size, size);
+  }
+
+  function drawGrid() {
+    ctx.fillStyle = "#000";
+    ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
+    for (let r = 0; r < ROWS; r++) {
+      for (let c = 0; c < COLS; c++) {
+        const cell = grid[r][c];
+        if (cell) {
+          drawBlock(c, r, COLORS[cell]);
+        }
+      }
+    }
+  }
+
+  function drawPiece(p, ghost=false) {
+    const m = p.shape;
+    let gy = p.y;
+    if (ghost) {
+      while (!collide(p, 0, gy - p.y + 1)) gy++;
+    }
+    for (let r = 0; r < m.length; r++) {
+      for (let c = 0; c < m[r].length; c++) {
+        if (!m[r][c]) continue;
+        const nx = p.x + c;
+        const ny = (ghost ? gy : p.y) + r;
+        if (ny >= 0) {
+          const baseColor = COLORS[p.type];
+          let color = baseColor;
+          if (ghost) {
+            // simple ghost effect: darker
+            color = shadeColor(baseColor, -50);
+          }
+          drawBlock(nx, ny, color);
+        }
+      }
+    }
+  }
+
+  function shadeColor(col, amt) {
+    const num = parseInt(col.slice(1), 16);
+    let r = (num >> 16) + amt;
+    let g = ((num >> 8) & 0x00FF) + amt;
+    let b = (num & 0x0000FF) + amt;
+    r = Math.max(Math.min(255, r), 0);
+    g = Math.max(Math.min(255, g), 0);
+    b = Math.max(Math.min(255, b), 0);
+    return "#" + (r<<16 | g<<8 | b).toString(16).padStart(6,'0');
+  }
+
+  function drawNext() {
+    nctx.fillStyle = "#000";
+    nctx.fillRect(0,0,nextCanvas.width,nextCanvas.height);
+    const p = nextPiece;
+    const m = SHAPES[p.type][0];
+    const size = 16;
+    const cols = m[0].length;
+    const rows = m.length;
+    const offsetX = Math.floor((nextCanvas.width/size - cols)/2);
+    const offsetY = Math.floor((nextCanvas.height/size - rows)/2);
+    for (let r = 0; r < m.length; r++) {
+      for (let c = 0; c < m[r].length; c++) {
+        if (!m[r][c]) continue;
+        drawBlock(offsetX + c, offsetY + r, COLORS[p.type], nctx, size);
+      }
+    }
+  }
+
+  function draw() {
+    drawGrid();
+    if (current) {
+      drawPiece(current, true);
+      drawPiece(current, false);
+    }
+    if (gameOver) {
+      ctx.fillStyle = "rgba(0,0,0,0.7)";
+      ctx.fillRect(0, CANVAS_H/2 - 40, CANVAS_W, 80);
+      ctx.fillStyle = "#fff";
+      ctx.font = "bold 20px Arial";
+      ctx.textAlign = "center";
+      ctx.fillText("GAME OVER", CANVAS_W/2, CANVAS_H/2 - 5);
+      ctx.font = "14px Arial";
+      ctx.fillText("Press R to restart", CANVAS_W/2, CANVAS_H/2 + 18);
+    } else if (paused) {
+      ctx.fillStyle = "rgba(0,0,0,0.7)";
+      ctx.fillRect(0, CANVAS_H/2 - 30, CANVAS_W, 60);
+      ctx.fillStyle = "#fff";
+      ctx.font = "bold 18px Arial";
+      ctx.textAlign = "center";
+      ctx.fillText("PAUSED", CANVAS_W/2, CANVAS_H/2);
+    }
+  }
+
+  function updateDropInterval() {
+    // Simple speed curve: min 80ms
+    dropInterval = Math.max(80, 800 - (level-1)*70);
+  }
+
+  function newPiece() {
+    current = nextPiece || randomPiece();
+    nextPiece = randomPiece();
+    drawNext();
+    dropStart = performance.now();
+    if (collide(current, 0, 0)) {
+      gameOver = true;
+    }
+  }
+
+  function hardDrop() {
+    if (gameOver || paused) return;
+    while (!collide(current, 0, 1)) {
+      current.y++;
+      score += 1;
+    }
+    lockPiece();
+  }
+
+  function lockPiece() {
+    merge(current);
+    clearLines();
+    newPiece();
+  }
+
+  function move(dx, dy) {
+    if (gameOver || paused) return;
+    if (!collide(current, dx, dy)) {
+      current.x += dx;
+      current.y += dy;
+    } else if (dy === 1) {
+      lockPiece();
+    }
+  }
+
+  function rotateCurrent() {
+    if (gameOver || paused) return;
+    const rotated = rotatePiece(current, 1);
+    // basic wall kicks: try shifts -1, +1, -2, +2
+    const kicks = [0, -1, 1, -2, 2];
+    for (let k of kicks) {
+      if (!collide(rotated, k, 0)) {
+        rotated.x += k;
+        current = rotated;
+        return;
+      }
+    }
+  }
+
+  function togglePause() {
+    if (gameOver) return;
+    paused = !paused;
+    if (!paused) {
+      dropStart = performance.now();
+      loop();
+    } else {
+      if (requestId) cancelAnimationFrame(requestId);
+      draw();
+    }
+  }
+
+  function restart() {
+    resetGrid();
+    score = 0;
+    lines = 0;
+    level = 1;
+    updateDropInterval();
+    updateHUD();
+    gameOver = false;
+    paused = false;
+    nextPiece = randomPiece();
+    newPiece();
+    dropStart = performance.now();
+    loop();
+  }
+
+  function loop(timestamp) {
+    if (gameOver || paused) {
+      draw();
+      return;
+    }
+    if (!dropStart) dropStart = timestamp;
+    const delta = timestamp - dropStart;
+    if (delta > dropInterval) {
+      move(0,1);
+      dropStart = timestamp;
+    }
+    draw();
+    requestId = requestAnimationFrame(loop);
+  }
+
+  document.addEventListener('keydown', function(e) {
+    switch (e.key) {
+      case "ArrowLeft":
+        e.preventDefault(); move(-1,0); break;
+      case "ArrowRight":
+        e.preventDefault(); move(1,0); break;
+      case "ArrowDown":
+        e.preventDefault(); move(0,1); score += 1; updateHUD(); break;
+      case "ArrowUp":
+        e.preventDefault(); rotateCurrent(); break;
+      case " ":
+        e.preventDefault(); hardDrop(); updateHUD(); break;
+      case "p":
+      case "P":
+        e.preventDefault(); togglePause(); break;
+      case "r":
+      case "R":
+        e.preventDefault(); restart(); break;
+    }
+  });
+
+  restartBtn.addEventListener('click', function() {
+    restart();
+  });
+
+  // Init
+  resetGrid();
+  score = 0;
+  lines = 0;
+  level = 1;
+  updateDropInterval();
+  updateHUD();
+  nextPiece = randomPiece();
+  newPiece();
+  requestAnimationFrame(loop);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/04692f72ef1d/gpt.png b/data/artifacts/04692f72ef1d/gpt.png
new file mode 100644
index 0000000..04a7775
Binary files /dev/null and b/data/artifacts/04692f72ef1d/gpt.png differ
diff --git a/data/artifacts/04692f72ef1d/grok.html b/data/artifacts/04692f72ef1d/grok.html
new file mode 100644
index 0000000..b87f9f2
--- /dev/null
+++ b/data/artifacts/04692f72ef1d/grok.html
@@ -0,0 +1,351 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Tetris</title>
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+body{background:#0f0f1a;color:#e0e0e0;font-family:'Segoe UI',system-ui,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;overflow:hidden}
+.wrap{display:flex;gap:24px;align-items:flex-start;padding:20px}
+canvas#game{background:#1a1a2e;border:3px solid #4a4a6a;border-radius:4px;box-shadow:0 0 30px rgba(80,80,180,.3)}
+.side{display:flex;flex-direction:column;gap:16px;min-width:140px}
+.panel{background:#1a1a2e;border:2px solid #4a4a6a;border-radius:8px;padding:14px;text-align:center}
+.panel h2{font-size:11px;text-transform:uppercase;letter-spacing:2px;color:#8888aa;margin-bottom:8px}
+.panel .val{font-size:28px;font-weight:700;color:#fff;font-variant-numeric:tabular-nums}
+canvas#next{display:block;margin:0 auto;background:#12122a;border-radius:4px}
+.controls{font-size:12px;line-height:1.8;color:#8888aa;text-align:left}
+.controls kbd{display:inline-block;background:#2a2a4a;border:1px solid #5a5a7a;border-radius:3px;padding:1px 6px;font-size:11px;color:#ccc;font-family:inherit;min-width:22px;text-align:center}
+.overlay{position:fixed;inset:0;background:rgba(0,0,0,.75);display:flex;justify-content:center;align-items:center;z-index:10;opacity:0;pointer-events:none;transition:opacity .25s}
+.overlay.show{opacity:1;pointer-events:auto}
+.modal{background:#1a1a2e;border:3px solid #6a6a9a;border-radius:12px;padding:36px 48px;text-align:center;box-shadow:0 0 60px rgba(100,100,200,.4)}
+.modal h1{font-size:36px;margin-bottom:8px;color:#fff}
+.modal p{color:#aaa;margin-bottom:20px;font-size:15px}
+.modal .score-line{font-size:18px;color:#ddd;margin-bottom:24px}
+.modal button{background:linear-gradient(135deg,#5a5aee,#8a4aee);color:#fff;border:none;padding:12px 36px;font-size:16px;border-radius:8px;cursor:pointer;font-weight:600;letter-spacing:1px;transition:transform .1s,box-shadow .1s}
+.modal button:hover{transform:scale(1.05);box-shadow:0 4px 20px rgba(100,80,220,.5)}
+.modal button:active{transform:scale(.98)}
+#pause-ov .modal h1{color:#ffcc44}
+#go-ov .modal h1{color:#ff4466}
+</style>
+</head>
+<body>
+<div class="wrap">
+<canvas id="game" width="300" height="600"></canvas>
+<div class="side">
+<div class="panel"><h2>Score</h2><div class="val" id="score">0</div></div>
+<div class="panel"><h2>Lines</h2><div class="val" id="lines">0</div></div>
+<div class="panel"><h2>Level</h2><div class="val" id="level">1</div></div>
+<div class="panel"><h2>Next</h2><canvas id="next" width="120" height="120"></canvas></div>
+<div class="panel controls">
+<kbd>←</kbd><kbd>→</kbd> Move<br>
+<kbd>↓</kbd> Soft drop<br>
+<kbd>↑</kbd> Rotate<br>
+<kbd>Space</kbd> Hard drop<br>
+<kbd>P</kbd> Pause
+</div>
+</div>
+</div>
+<div class="overlay show" id="start-ov">
+<div class="modal">
+<h1>TETRIS</h1>
+<p>Clear lines, don't stack out!</p>
+<button id="start-btn">Play</button>
+</div>
+</div>
+<div class="overlay" id="pause-ov">
+<div class="modal">
+<h1>PAUSED</h1>
+<p>Press P or click to resume</p>
+<button id="resume-btn">Resume</button>
+</div>
+</div>
+<div class="overlay" id="go-ov">
+<div class="modal">
+<h1>GAME OVER</h1>
+<div class="score-line">Score: <strong id="final-score">0</strong></div>
+<button id="restart-btn">Play Again</button>
+</div>
+</div>
+<script>
+(function(){
+const COLS=10,ROWS=20,BLOCK=30;
+const canvas=document.getElementById('game');
+const ctx=canvas.getContext('2d');
+const nextCanvas=document.getElementById('next');
+const nctx=nextCanvas.getContext('2d');
+
+const COLORS={
+I:'#00f0f0',O:'#f0f000',T:'#a000f0',S:'#00f000',
+Z:'#f00000',J:'#0000f0',L:'#f0a000'
+};
+const SHAPES={
+I:[[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],
+O:[[1,1],[1,1]],
+T:[[0,1,0],[1,1,1],[0,0,0]],
+S:[[0,1,1],[1,1,0],[0,0,0]],
+Z:[[1,1,0],[0,1,1],[0,0,0]],
+J:[[1,0,0],[1,1,1],[0,0,0]],
+L:[[0,0,1],[1,1,1],[0,0,0]]
+};
+const PIECE_TYPES=['I','O','T','S','Z','J','L'];
+
+let grid,piece,nextPiece,score,lines,level,dropInterval,dropTimer,lastTime,paused,gameOver,started,animId;
+
+function createGrid(){
+const g=[];
+for(let r=0;r<ROWS;r++)g.push(new Array(COLS).fill(null));
+return g;
+}
+
+function randomType(){return PIECE_TYPES[Math.floor(Math.random()*7)];}
+
+function spawnPiece(type){
+const shape=SHAPES[type].map(r=>r.slice());
+const w=shape[0].length;
+return{type,shape,x:Math.floor((COLS-w)/2),y:0,color:COLORS[type]};
+}
+
+function rotate(shape){
+const N=shape.length;
+const res=Array.from({length:N},()=>new Array(N).fill(0));
+for(let r=0;r<N;r++)for(let c=0;c<N;c++)res[c][N-1-r]=shape[r][c];
+return res;
+}
+
+function collides(p,ox,oy,shape){
+const s=shape||p.shape;
+for(let r=0;r<s.length;r++){
+for(let c=0;c<s[r].length;c++){
+if(!s[r][c])continue;
+const nx=p.x+c+ox,ny=p.y+r+oy;
+if(nx<0||nx>=COLS||ny>=ROWS)return true;
+if(ny>=0&&grid[ny][nx])return true;
+}
+}
+return false;
+}
+
+function merge(p){
+p.shape.forEach((row,r)=>{
+row.forEach((v,c)=>{
+if(v){
+const y=p.y+r,x=p.x+c;
+if(y>=0&&y<ROWS&&x>=0&&x<COLS)grid[y][x]=p.color;
+}
+});
+});
+}
+
+function clearLines(){
+let cleared=0;
+for(let r=ROWS-1;r>=0;r--){
+if(grid[r].every(c=>c!==null)){
+grid.splice(r,1);
+grid.unshift(new Array(COLS).fill(null));
+cleared++;
+r++;
+}
+}
+if(cleared>0){
+const pts=[0,100,300,500,800];
+score+=pts[cleared]*level;
+lines+=cleared;
+level=Math.floor(lines/10)+1;
+dropInterval=Math.max(80,1000-(level-1)*80);
+updateHUD();
+}
+}
+
+function hardDrop(){
+let dist=0;
+while(!collides(piece,0,1)){piece.y++;dist++;}
+score+=dist*2;
+lockPiece();
+}
+
+function lockPiece(){
+merge(piece);
+clearLines();
+piece=nextPiece;
+nextPiece=spawnPiece(randomType());
+drawNext();
+if(collides(piece,0,0)){
+gameOver=true;
+cancelAnimationFrame(animId);
+document.getElementById('final-score').textContent=score;
+document.getElementById('go-ov').classList.add('show');
+}
+}
+
+function drawBlock(c,x,y,color,size){
+const s=size||BLOCK;
+const g=c.createLinearGradient(x,y,x+s,y+s);
+g.addColorStop(0,lighten(color,40));
+g.addColorStop(0.4,color);
+g.addColorStop(1,darken(color,30));
+c.fillStyle=g;
+c.fillRect(x+1,y+1,s-2,s-2);
+c.fillStyle='rgba(255,255,255,0.25)';
+c.fillRect(x+2,y+2,s-4,Math.floor(s/4));
+c.strokeStyle='rgba(0,0,0,0.3)';
+c.lineWidth=1;
+c.strokeRect(x+1,y+1,s-2,s-2);
+}
+
+function lighten(hex,amt){
+const n=parseInt(hex.slice(1),16);
+let r=Math.min(255,(n>>16)+amt),g=Math.min(255,((n>>8)&0xff)+amt),b=Math.min(255,(n&0xff)+amt);
+return`#${((r<<16)|(g<<8)|b).toString(16).padStart(6,'0')}`;
+}
+function darken(hex,amt){
+const n=parseInt(hex.slice(1),16);
+let r=Math.max(0,(n>>16)-amt),g=Math.max(0,((n>>8)&0xff)-amt),b=Math.max(0,(n&0xff)-amt);
+return`#${((r<<16)|(g<<8)|b).toString(16).padStart(6,'0')}`;
+}
+
+function drawGhost(){
+let gy=0;
+while(!collides(piece,0,gy+1))gy++;
+if(gy===0)return;
+piece.shape.forEach((row,r)=>{
+row.forEach((v,c)=>{
+if(v){
+const x=(piece.x+c)*BLOCK,y=(piece.y+r+gy)*BLOCK;
+ctx.fillStyle='rgba(255,255,255,0.08)';
+ctx.fillRect(x+1,y+1,BLOCK-2,BLOCK-2);
+ctx.strokeStyle='rgba(255,255,255,0.2)';
+ctx.lineWidth=1;
+ctx.strokeRect(x+2,y+2,BLOCK-4,BLOCK-4);
+}
+});
+});
+}
+
+function draw(){
+ctx.fillStyle='#1a1a2e';
+ctx.fillRect(0,0,canvas.width,canvas.height);
+ctx.strokeStyle='rgba(255,255,255,0.03)';
+ctx.lineWidth=1;
+for(let r=0;r<=ROWS;r++){ctx.beginPath();ctx.moveTo(0,r*BLOCK);ctx.lineTo(COLS*BLOCK,r*BLOCK);ctx.stroke();}
+for(let c=0;c<=COLS;c++){ctx.beginPath();ctx.moveTo(c*BLOCK,0);ctx.lineTo(c*BLOCK,ROWS*BLOCK);ctx.stroke();}
+for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++)if(grid[r][c])drawBlock(ctx,c*BLOCK,r*BLOCK,grid[r][c]);
+if(piece&&!gameOver){
+drawGhost();
+piece.shape.forEach((row,r)=>{
+row.forEach((v,c)=>{
+if(v){
+const y=piece.y+r;
+if(y>=0)drawBlock(ctx,(piece.x+c)*BLOCK,y*BLOCK,piece.color);
+}
+});
+});
+}
+}
+
+function drawNext(){
+nctx.fillStyle='#12122a';
+nctx.fillRect(0,0,nextCanvas.width,nextCanvas.height);
+if(!nextPiece)return;
+const s=nextPiece.shape;
+const bs=24;
+const w=s[0].length*bs,h=s.length*bs;
+const ox=(nextCanvas.width-w)/2,oy=(nextCanvas.height-h)/2;
+s.forEach((row,r)=>{
+row.forEach((v,c)=>{
+if(v)drawBlock(nctx,ox+c*bs,oy+r*bs,nextPiece.color,bs);
+});
+});
+}
+
+function updateHUD(){
+document.getElementById('score').textContent=score;
+document.getElementById('lines').textContent=lines;
+document.getElementById('level').textContent=level;
+}
+
+function gameLoop(time){
+if(gameOver||paused)return;
+animId=requestAnimationFrame(gameLoop);
+const delta=time-lastTime;
+if(delta>=dropInterval){
+lastTime=time;
+if(!collides(piece,0,1)){piece.y++;}
+else{lockPiece();}
+}
+draw();
+}
+
+function startGame(){
+grid=createGrid();
+score=0;lines=0;level=1;
+dropInterval=1000;
+paused=false;gameOver=false;started=true;
+piece=spawnPiece(randomType());
+nextPiece=spawnPiece(randomType());
+updateHUD();
+drawNext();
+lastTime=performance.now();
+document.getElementById('start-ov').classList.remove('show');
+document.getElementById('go-ov').classList.remove('show');
+document.getElementById('pause-ov').classList.remove('show');
+cancelAnimationFrame(animId);
+animId=requestAnimationFrame(gameLoop);
+}
+
+function togglePause(){
+if(!started||gameOver)return;
+paused=!paused;
+if(paused){
+cancelAnimationFrame(animId);
+document.getElementById('pause-ov').classList.add('show');
+}else{
+document.getElementById('pause-ov').classList.remove('show');
+lastTime=performance.now();
+animId=requestAnimationFrame(gameLoop);
+}
+}
+
+document.addEventListener('keydown',e=>{
+if(!started&&e.key!=='Enter'&&e.key!==' ')return;
+if(gameOver&&(e.key==='Enter'||e.key===' ')){startGame();return;}
+if(e.key==='p'||e.key==='P'){togglePause();return;}
+if(paused||gameOver||!started)return;
+switch(e.key){
+case'ArrowLeft':case'a':case'A':
+if(!collides(piece,-1,0))piece.x--;
+break;
+case'ArrowRight':case'd':case'D':
+if(!collides(piece,1,0))piece.x++;
+break;
+case'ArrowDown':case's':case'S':
+if(!collides(piece,0,1)){piece.y++;score+=1;updateHUD();}
+else lockPiece();
+break;
+case'ArrowUp':case'w':case'W':{
+const rotated=rotate(piece.shape);
+let kicks=[0,-1,1,-2,2];
+if(piece.type==='I')kicks=[0,-1,1,-2,2];
+for(const k of kicks){
+if(!collides(piece,k,0,rotated)){piece.shape=rotated;piece.x+=k;break;}
+}
+break;
+}
+case' ':
+e.preventDefault();
+hardDrop();
+break;
+}
+draw();
+});
+
+document.getElementById('start-btn').addEventListener('click',startGame);
+document.getElementById('restart-btn').addEventListener('click',startGame);
+document.getElementById('resume-btn').addEventListener('click',togglePause);
+document.getElementById('pause-ov').addEventListener('click',e=>{if(e.target.id==='pause-ov')togglePause();});
+
+draw();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/04692f72ef1d/grok.png b/data/artifacts/04692f72ef1d/grok.png
new file mode 100644
index 0000000..9d9cf2c
Binary files /dev/null and b/data/artifacts/04692f72ef1d/grok.png differ
diff --git a/data/artifacts/04692f72ef1d/hermes3-8b.html b/data/artifacts/04692f72ef1d/hermes3-8b.html
new file mode 100644
index 0000000..e8a0ad0
--- /dev/null
+++ b/data/artifacts/04692f72ef1d/hermes3-8b.html
@@ -0,0 +1,338 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Playable Tetris</title>
+  <style>
+    body {
+      font-family: monospace;
+      background-color: #000;
+      color: #0f0;
+      padding: 10px;
+    }
+    
+    canvas {
+      background-color: #333;
+    }
+    
+    .score {
+      margin-top: 5px;
+    }
+    
+    .controls {
+      margin-top: 10px;
+    }
+    
+    button {
+      margin-right: 5px;
+    }
+  </style>
+</head>
+<body>
+  <canvas id="game"></canvas>
+  <div class="score">Score: <span id="score">0</span></div>
+  <div class="controls">
+    <button id="pause" disabled>Pause</button>
+    <button id="restart">Restart</button>
+  </div>
+
+  <script>
+    const canvas = document.getElementById('game');
+    const ctx = canvas.getContext('2d');
+
+    const tetrominoes = [
+      [[1]], [[1], [1], [1]], [[1, 1], [1, 1]], [[1, 1, 1], [0, 1, 0]],
+    ];
+
+    let grid = Array(20).fill().map(() => Array(10).fill(0));
+    let currentPiece = null;
+    let currentX = 0;
+    let currentY = 0;
+
+    function getRandomInt(max) {
+      return Math.floor(Math.random() * max);
+    }
+
+    function rotatePiece() {
+      if (currentPiece === tetrominoes[3]) {
+        return;
+      }
+      
+      for (let i = 0; i < currentPiece.length; i++) {
+        for (let j = 0; j < currentPiece[i].length; j++) {
+          const temp = currentPiece[i][j];
+          currentPiece[i][j] = currentPiece[j][i];
+          currentPiece[j][i] = temp;
+        }
+      }
+
+      if (!isCollision()) {
+        return;
+      }
+
+      for (let i = 0; i < currentPiece.length; i++) {
+        for (let j = 0; j < currentPiece[i].length; j++) {
+          currentPiece[i][j] = 0;
+        }
+      }
+
+      if (!isCollision()) {
+        return;
+      }
+
+      for (let i = 0; i < currentPiece.length; i++) {
+        for (let j = 0; j < currentPiece[i].length; j++) {
+          currentPiece[i][j] = temp;
+        }
+      }
+    }
+
+    function movePieceDown() {
+      currentY++;
+
+      if (isCollision()) {
+        setCurrentPiece(null);
+        merge();
+        return;
+      }
+      
+      clearLines();
+    }
+
+    function movePieceLeft() {
+      currentX--;
+
+      if (isCollision()) {
+        currentX++;
+      }
+    }
+
+    function movePieceRight() {
+      currentX++;
+
+      if (isCollision()) {
+        currentX--;
+      }
+    }
+
+    function isCollision() {
+      for (let i = 0; i < currentPiece.length; i++) {
+        for (let j = 0; j < currentPiece[i].length; j++) {
+          const value = currentPiece[i][j];
+
+          if (value !== 0) {
+            if (currentX + i >= 0 && currentX + i < grid.length &&
+                currentY + j >= 0 && currentY + j < grid[0].length) {
+
+              if (grid[currentX + i][currentY + j] !== 0) {
+                return true;
+              }
+            } else {
+              return true;
+            }
+          }
+        }
+      }
+
+      return false;
+    }
+
+    function clearLines() {
+      for (let i = grid.length - 1; i >= 0; i--) {
+        let lineFull = true;
+
+        for (let j = 0; j < grid[i].length; j++) {
+          if (grid[i][j] === 0) {
+            lineFull = false;
+            break;
+          }
+        }
+
+        if (lineFull) {
+          grid.splice(i, 1);
+          grid.unshift(Array(grid[0].length).fill(0));
+
+          for (let k = i; k < grid.length; k++) {
+            for (let j = 0; j < grid[k].length; j++) {
+              grid[k][j]++;
+            }
+          }
+        }
+      }
+
+      if (grid.length === 0) {
+        grid.unshift(Array(grid[0].length).fill(0));
+      }
+    }
+
+    function merge() {
+      let score = 0;
+
+      for (let i = 0; i < grid.length; i++) {
+        for (let j = 0; j < grid[i].length; j++) {
+          if (grid[i][j] > 0) {
+            const count = Math.floor(grid[i][j] / tetrominoes[3][0].length);
+
+            for (let k = 0; k < count; k++) {
+              score += count;
+              grid[i][j] -= tetrominoes[3][0].length;
+            }
+          }
+        }
+      }
+
+      document.getElementById('score').textContent = score;
+    }
+
+    function setCurrentPiece(piece) {
+      currentX = Math.floor(grid.length / 2) - Math.floor(tetrominoes[piece][0].length / 2);
+      currentY = Math.floor(grid[0].length / 2) - Math.floor(tetrominoes[piece][0][0] / 2);
+
+      if (isCollision()) {
+        setCurrentPiece(0);
+      }
+
+      currentPiece = tetrominoes[piece];
+    }
+
+    function getRandomTetromino() {
+      const types = [1, 2, 3, 4];
+
+      for (let i = 0; i < types.length; i++) {
+        if (types[i] !== null) {
+          return types.splice(getRandomInt(types.length), 1)[0];
+        }
+      }
+
+      return 0;
+    }
+
+    function drawPiece(x, y) {
+      for (let i = 0; i < currentPiece.length; i++) {
+        for (let j = 0; j < currentPiece[i].length; j++) {
+          if (currentPiece[i][j] > 0) {
+            ctx.fillStyle = 'rgba(255, 255, 0, 1)';
+            ctx.fillRect((x + j) * 20, (y + i) * 20, 20, 20);
+            ctx.strokeStyle = '#000';
+            ctx.strokeRect((x + j) * 20, (y + i) * 20, 20, 20);
+          }
+        }
+      }
+    }
+
+    function drawGrid() {
+      for (let i = 0; i < grid.length; i++) {
+        for (let j = 0; j < grid[i].length; j++) {
+          if (grid[i][j] > 0) {
+            ctx.fillStyle = 'rgba(255, 255, 0, 1)';
+            ctx.fillRect(j * 20, i * 20, 20, 20);
+            
+            if (grid[i][j] > 9) {
+              ctx.font = "12px monospace";
+              ctx.fillStyle = "#fff";
+              ctx.textAlign = "center";
+              ctx.fillText(grid[i][j], j * 20 + 10, i * 20 + 20);
+            }
+          } else {
+            ctx.strokeStyle = '#fff';
+            ctx.strokeRect(j * 20, i * 20, 20, 20);
+          }
+        }
+      }
+    }
+
+    function drawNextPiece() {
+      const nextX = Math.floor(grid.length / 2) - Math.floor(tetrominoes[getRandomTetromino()][0].length / 2);
+
+      for (let i = 0; i < tetrominoes[getRandomTetromino()].length; i++) {
+        for (let j = 0; j < tetrominoes[getRandomTetromino()][i].length; j++) {
+          if (tetrominoes[getRandomTetromino()][i][j] > 0) {
+            ctx.fillStyle = 'rgba(255, 255, 255, 1)';
+            ctx.fillRect((nextX + j) * 20, (i) * 20, 20, 20);
+          }
+        }
+      }
+    }
+
+    function draw() {
+      ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+      drawGrid();
+      drawPiece(currentX, currentY);
+
+      if (!isGameOver()) {
+        setTimeout(() => {
+          movePieceDown();
+          draw();
+        }, 500 - document.getElementById('score').textContent * 5);
+      } else {
+        ctx.fillStyle = 'rgba(255, 0, 0, 1)';
+        ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+        ctx.font = "24px monospace";
+        ctx.fillStyle = "#fff";
+        ctx.textAlign = "center";
+        ctx.fillText('Game Over!', canvas.width / 2, canvas.height / 2);
+      }
+
+      drawNextPiece();
+    }
+
+    function isGameOver() {
+      return currentPiece === null && !isCollision();
+    }
+
+    document.getElementById('pause').addEventListener('click', () => {
+      const pause = document.getElementById('pause');
+      
+      if (pause.innerText === 'Pause') {
+        pause.innerText = 'Resume';
+        pause.disabled = true;
+      } else {
+        pause.innerText = 'Pause';
+        pause.disabled = false;
+
+        draw();
+      }
+    });
+
+    document.getElementById('restart').addEventListener('click', () => {
+      grid = Array(20).fill().map(() => Array(10).fill(0));
+      currentPiece = null;
+      currentX = 0;
+      currentY = 0;
+
+      document.getElementById('score').textContent = 0;
+      document.getElementById('pause').disabled = true;
+      document.getElementById('pause').innerText = 'Pause';
+
+      draw();
+    });
+
+    setCurrentPiece(getRandomTetromino());
+    draw();
+
+    canvas.addEventListener('keydown', (e) => {
+      switch (e.key) {
+        case 'a':
+          movePieceLeft();
+          break;
+        case 'd':
+          movePieceRight();
+          break;
+        case 'w':
+          rotatePiece();
+          break;
+        case 's':
+          movePieceDown();
+          break;
+        case 'f':
+          document.getElementById('pause').click();
+          break;
+      }
+    });
+
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/04692f72ef1d/hermes3-8b.png b/data/artifacts/04692f72ef1d/hermes3-8b.png
new file mode 100644
index 0000000..d56ddbd
Binary files /dev/null and b/data/artifacts/04692f72ef1d/hermes3-8b.png differ
diff --git a/data/artifacts/04692f72ef1d/hf-qwen-coder-32b.html b/data/artifacts/04692f72ef1d/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..526aecc
--- /dev/null
+++ b/data/artifacts/04692f72ef1d/hf-qwen-coder-32b.html
@@ -0,0 +1,190 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>Tetris</title>
+<style>
+body{display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#000;}
+canvas{border:1px solid #fff;}
+#info{text-align:center;color:#fff;font-family:Arial,sans-serif;}
+#score,#lines{font-size:2em;margin-bottom:10px;}
+</style>
+</head>
+<body>
+<canvas id="game" width="300" height="600"></canvas>
+<div id="info">
+  <div id="score">Score: 0</div>
+  <div id="lines">Lines: 0</div>
+</div>
+<script>
+const canvas = document.getElementById('game');
+const ctx = canvas.getContext('2d');
+const gridSize = 30;
+const gridWidth = canvas.width / gridSize;
+const gridHeight = canvas.height / gridSize;
+let gameInterval, score = 0, linesCleared = 0, level = 1, paused = false;
+const tetrominoes = [
+  [[1,1,1,1]], // I
+  [[1,1],[1,1]], // O
+  [[0,1,0],[1,1,1]], // T
+  [[1,0,0],[1,1,1]], // L
+  [[0,0,1],[1,1,1]], // J
+  [[1,1,0],[0,1,1]], // S
+  [[0,1,1],[1,1,0]]  // Z
+];
+const colors = ['#FFA500', '#FFFF00', '#8A2BE2', '#00BFFF', '#00FF7F', '#FF4500', '#FF69B4'];
+let grid = Array.from({length: gridHeight}, () => Array(gridWidth).fill(0));
+let currentTetromino, nextTetromino, currentPosition = {x: Math.floor(gridWidth/2) - 1, y: 0};
+
+function drawGrid() {
+  ctx.clearRect(0, 0, canvas.width, canvas.height);
+  for (let row = 0; row < gridHeight; row++) {
+    for (let col = 0; col < gridWidth; col++) {
+      if (grid[row][col]) {
+        ctx.fillStyle = colors[grid[row][col] - 1];
+        ctx.fillRect(col * gridSize, row * gridSize, gridSize, gridSize);
+        ctx.strokeStyle = '#000';
+        ctx.strokeRect(col * gridSize, row * gridSize, gridSize, gridSize);
+      }
+    }
+  }
+}
+
+function drawTetromino(tetromino, position) {
+  tetromino.forEach((row, y) => {
+    row.forEach((value, x) => {
+      if (value) {
+        ctx.fillStyle = colors[value - 1];
+        ctx.fillRect((position.x + x) * gridSize, (position.y + y) * gridSize, gridSize, gridSize);
+        ctx.strokeStyle = '#000';
+        ctx.strokeRect((position.x + x) * gridSize, (position.y + y) * gridSize, gridSize, gridSize);
+      }
+    });
+  });
+}
+
+function checkCollision(tetromino, position) {
+  for (let row = 0; row < tetromino.length; row++) {
+    for (let col = 0; col < tetromino[row].length; col++) {
+      if (tetromino[row][col]) {
+        const newX = position.x + col;
+        const newY = position.y + row;
+        if (newX < 0 || newX >= gridWidth || newY >= gridHeight || (newY >= 0 && grid[newY][newX])) {
+          return true;
+        }
+      }
+    }
+  }
+  return false;
+}
+
+function mergeTetromino(tetromino, position) {
+  tetromino.forEach((row, y) => {
+    row.forEach((value, x) => {
+      if (value) {
+        grid[position.y + y][position.x + x] = value;
+      }
+    });
+  });
+}
+
+function clearLines() {
+  let lines = 0;
+  for (let row = gridHeight - 1; row >= 0; row--) {
+    if (grid[row].every(cell => cell)) {
+      grid.splice(row, 1);
+      grid.unshift(Array(gridWidth).fill(0));
+      lines++;
+      row++; // Re-check the same row after clearing
+    }
+  }
+  return lines;
+}
+
+function rotate(tetromino) {
+  const newTetromino = [];
+  for (let x = 0; x < tetromino[0].length; x++) {
+    const newRow = [];
+    for (let y = tetromino.length - 1; y >= 0; y--) {
+      newRow.push(tetromino[y][x]);
+    }
+    newTetromino.push(newRow);
+  }
+  return newTetromino;
+}
+
+function spawnTetromino() {
+  currentTetromino = nextTetromino || tetrominoes[Math.floor(Math.random() * tetrominoes.length)];
+  currentPosition = {x: Math.floor(gridWidth/2) - Math.ceil(currentTetromino[0].length/2), y: 0};
+  nextTetromino = tetrominoes[Math.floor(Math.random() * tetrominoes.length)];
+}
+
+function gameOver() {
+  clearInterval(gameInterval);
+  alert('Game Over! Score: ' + score);
+  location.reload();
+}
+
+function updateScore(lines) {
+  const points = [0, 40, 100, 300, 1200];
+  linesCleared += lines;
+  score += points[lines] * level;
+  document.getElementById('score').innerText = 'Score: ' + score;
+  document.getElementById('lines').innerText = 'Lines: ' + linesCleared;
+}
+
+function tick() {
+  if (checkCollision(currentTetromino, {x: currentPosition.x, y: currentPosition.y + 1})) {
+    mergeTetromino(currentTetromino, currentPosition);
+    const lines = clearLines();
+    updateScore(lines);
+    spawnTetromino();
+    if (checkCollision(currentTetromino, currentPosition)) {
+      gameOver();
+    }
+  } else {
+    currentPosition.y++;
+  }
+  drawGrid();
+  drawTetromino(currentTetromino, currentPosition);
+}
+
+function startGame() {
+  spawnTetromino();
+  gameInterval = setInterval(tick, 1000 / level);
+}
+
+document.addEventListener('keydown', function(e) {
+  if (paused) return;
+  switch (e.key) {
+    case 'ArrowLeft':
+      if (!checkCollision(currentTetromino, {x: currentPosition.x - 1, y: currentPosition.y})) {
+        currentPosition.x--;
+      }
+      break;
+    case 'ArrowRight':
+      if (!checkCollision(currentTetromino, {x: currentPosition.x + 1, y: currentPosition.y})) {
+        currentPosition.x++;
+      }
+      break;
+    case 'ArrowDown':
+      tick();
+      break;
+    case 'ArrowUp':
+      const rotated = rotate(currentTetromino);
+      if (!checkCollision(rotated, currentPosition)) {
+        currentTetromino = rotated;
+      }
+      break;
+    case 'p':
+      paused = !paused;
+      clearInterval(gameInterval);
+      if (!paused) gameInterval = setInterval(tick, 1000 / level);
+      break;
+  }
+});
+
+startGame();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/04692f72ef1d/hf-qwen-coder-32b.png b/data/artifacts/04692f72ef1d/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..41cbea8
Binary files /dev/null and b/data/artifacts/04692f72ef1d/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/04692f72ef1d/kimi.html b/data/artifacts/04692f72ef1d/kimi.html
new file mode 100644
index 0000000..b090478
--- /dev/null
+++ b/data/artifacts/04692f72ef1d/kimi.html
@@ -0,0 +1,495 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Tetris</title>
+    <style>
+        body {
+            margin: 0;
+            padding: 0;
+            background: #111;
+            color: #fff;
+            font-family: 'Courier New', Courier, monospace;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            min-height: 100vh;
+            overflow: hidden;
+        }
+        #gameContainer {
+            display: flex;
+            gap: 20px;
+            background: #222;
+            padding: 20px;
+            border-radius: 8px;
+            box-shadow: 0 0 20px rgba(0,0,0,0.8);
+        }
+        #board {
+            border: 3px solid #444;
+            background: #000;
+        }
+        #sidePanel {
+            display: flex;
+            flex-direction: column;
+            gap: 20px;
+            width: 120px;
+        }
+        #nextCanvas {
+            border: 2px solid #444;
+            background: #000;
+        }
+        .infoBox {
+            background: #333;
+            padding: 10px;
+            border-radius: 4px;
+            text-align: center;
+            border: 1px solid #555;
+        }
+        .label {
+            font-size: 12px;
+            color: #888;
+            text-transform: uppercase;
+            margin-bottom: 5px;
+        }
+        .value {
+            font-size: 20px;
+            font-weight: bold;
+            color: #fff;
+        }
+        #controls {
+            font-size: 11px;
+            color: #666;
+            line-height: 1.5;
+            margin-top: auto;
+        }
+        #gameOver, #paused {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            background: rgba(0,0,0,0.9);
+            padding: 30px;
+            border-radius: 8px;
+            text-align: center;
+            border: 2px solid #fff;
+            display: none;
+        }
+        button {
+            background: #4a4;
+            color: white;
+            border: none;
+            padding: 10px 20px;
+            font-family: inherit;
+            font-size: 16px;
+            cursor: pointer;
+            margin-top: 10px;
+            border-radius: 4px;
+        }
+        button:hover {
+            background: #5c5;
+        }
+        h1 {
+            margin: 0 0 10px 0;
+            font-size: 24px;
+            text-shadow: 2px 2px 0 #f00;
+        }
+    </style>
+</head>
+<body>
+    <div id="gameContainer">
+        <canvas id="board" width="300" height="600"></canvas>
+        <div id="sidePanel">
+            <div class="infoBox">
+                <div class="label">Next</div>
+                <canvas id="nextCanvas" width="120" height="120"></canvas>
+            </div>
+            <div class="infoBox">
+                <div class="label">Score</div>
+                <div class="value" id="score">0</div>
+            </div>
+            <div class="infoBox">
+                <div class="label">Lines</div>
+                <div class="value" id="lines">0</div>
+            </div>
+            <div class="infoBox">
+                <div class="label">Level</div>
+                <div class="value" id="level">1</div>
+            </div>
+            <div id="controls">
+                <strong>Controls:</strong><br>
+                ← → : Move<br>
+                ↑ / X : Rotate<br>
+                Z : Rotate CCW<br>
+                ↓ : Soft Drop<br>
+                Space : Hard Drop<br>
+                P : Pause<br>
+                R : Restart
+            </div>
+        </div>
+    </div>
+    
+    <div id="gameOver">
+        <h1>GAME OVER</h1>
+        <div>Score: <span id="finalScore">0</span></div>
+        <button onclick="resetGame()">Restart</button>
+    </div>
+    
+    <div id="paused">
+        <h1>PAUSED</h1>
+        <div>Press P to resume</div>
+    </div>
+
+    <script>
+        const canvas = document.getElementById('board');
+        const ctx = canvas.getContext('2d');
+        const nextCanvas = document.getElementById('nextCanvas');
+        const nextCtx = nextCanvas.getContext('2d');
+        
+        const COLS = 10;
+        const ROWS = 20;
+        const BLOCK_SIZE = 30;
+        const PREVIEW_BLOCK_SIZE = 25;
+        
+        let board = [];
+        let score = 0;
+        let lines = 0;
+        let level = 1;
+        let gameOver = false;
+        let paused = false;
+        let dropCounter = 0;
+        let lastTime = 0;
+        let dropInterval = 1000;
+        
+        const PIECES = [
+            { shape: [[1,1,1,1]], color: '#00f0f0' }, // I
+            { shape: [[1,1],[1,1]], color: '#f0f000' }, // O
+            { shape: [[0,1,0],[1,1,1]], color: '#a000f0' }, // T
+            { shape: [[0,1,1],[1,1,0]], color: '#00f000' }, // S
+            { shape: [[1,1,0],[0,1,1]], color: '#f00000' }, // Z
+            { shape: [[1,0,0],[1,1,1]], color: '#0000f0' }, // J
+            { shape: [[0,0,1],[1,1,1]], color: '#f0a000' }  // L
+        ];
+        
+        let currentPiece = null;
+        let nextPiece = null;
+        
+        function createBoard() {
+            board = Array(ROWS).fill().map(() => Array(COLS).fill(0));
+        }
+        
+        function rotateMatrix(matrix, times) {
+            times = ((times % 4) + 4) % 4;
+            let result = matrix;
+            for (let i = 0; i < times; i++) {
+                result = rotate90(result);
+            }
+            return result;
+        }
+        
+        function rotate90(matrix) {
+            const rows = matrix.length;
+            const cols = matrix[0].length;
+            const rotated = [];
+            for (let i = 0; i < cols; i++) {
+                rotated[i] = [];
+                for (let j = 0; j < rows; j++) {
+                    rotated[i][j] = matrix[rows - 1 - j][i];
+                }
+            }
+            return rotated;
+        }
+        
+        function getRandomPiece() {
+            const piece = PIECES[Math.floor(Math.random() * PIECES.length)];
+            return {
+                shape: piece.shape,
+                color: piece.color,
+                x: 0,
+                y: 0,
+                rotation: 0
+            };
+        }
+        
+        function spawnPiece() {
+            currentPiece = nextPiece || getRandomPiece();
+            nextPiece = getRandomPiece();
+            
+            const shape = rotateMatrix(currentPiece.shape, currentPiece.rotation);
+            currentPiece.x = Math.floor(COLS / 2) - Math.floor(shape[0].length / 2);
+            currentPiece.y = 0;
+            
+            if (!isValidPosition(currentPiece, currentPiece.x, currentPiece.y, currentPiece.rotation)) {
+                endGame();
+            }
+        }
+        
+        function isValidPosition(piece, x, y, rotation) {
+            const shape = rotateMatrix(piece.shape, rotation);
+            for (let r = 0; r < shape.length; r++) {
+                for (let c = 0; c < shape[r].length; c++) {
+                    if (shape[r][c]) {
+                        const newX = x + c;
+                        const newY = y + r;
+                        if (newX < 0 || newX >= COLS || newY >= ROWS) return false;
+                        if (newY >= 0 && board[newY][newX]) return false;
+                    }
+                }
+            }
+            return true;
+        }
+        
+        function rotatePiece(clockwise = true) {
+            const newRotation = clockwise ? (currentPiece.rotation + 1) % 4 : (currentPiece.rotation + 3) % 4;
+            const kicks = [0, -1, 1, -2, 2];
+            
+            for (let kick of kicks) {
+                if (isValidPosition(currentPiece, currentPiece.x + kick, currentPiece.y, newRotation)) {
+                    currentPiece.x += kick;
+                    currentPiece.rotation = newRotation;
+                    break;
+                }
+            }
+        }
+        
+        function movePiece(dx, dy) {
+            if (isValidPosition(currentPiece, currentPiece.x + dx, currentPiece.y + dy, currentPiece.rotation)) {
+                currentPiece.x += dx;
+                currentPiece.y += dy;
+                return true;
+            }
+            return false;
+        }
+        
+        function dropPiece() {
+            if (!movePiece(0, 1)) {
+                lockPiece();
+            }
+            dropCounter = 0;
+        }
+        
+        function hardDrop() {
+            while (movePiece(0, 1)) {
+                score += 2;
+            }
+            lockPiece();
+            dropCounter = 0;
+        }
+        
+        function lockPiece() {
+            const shape = rotateMatrix(currentPiece.shape, currentPiece.rotation);
+            for (let r = 0; r < shape.length; r++) {
+                for (let c = 0; c < shape[r].length; c++) {
+                    if (shape[r][c]) {
+                        const y = currentPiece.y + r;
+                        const x = currentPiece.x + c;
+                        if (y >= 0) {
+                            board[y][x] = currentPiece.color;
+                        }
+                    }
+                }
+            }
+            clearLines();
+            spawnPiece();
+            updateStats();
+        }
+        
+        function clearLines() {
+            let linesCleared = 0;
+            for (let r = ROWS - 1; r >= 0; r--) {
+                if (board[r].every(cell => cell !== 0)) {
+                    board.splice(r, 1);
+                    board.unshift(Array(COLS).fill(0));
+                    linesCleared++;
+                    r++;
+                }
+            }
+            
+            if (linesCleared > 0) {
+                lines += linesCleared;
+                const points = [0, 100, 300, 500, 800];
+                score += points[linesCleared] * level;
+                level = Math.floor(lines / 10) + 1;
+                dropInterval = Math.max(100, 1000 - (level - 1) * 80);
+            }
+        }
+        
+        function drawBlock(context, x, y, color, size = BLOCK_SIZE) {
+            context.fillStyle = color;
+            context.fillRect(x * size, y * size, size, size);
+            
+            context.strokeStyle = 'rgba(255,255,255,0.4)';
+            context.lineWidth = 2;
+            context.strokeRect(x * size, y * size, size, size);
+            
+            context.fillStyle = 'rgba(255,255,255,0.3)';
+            context.fillRect(x * size + 2, y * size + 2, size - 4, 3);
+            context.fillStyle = 'rgba(0,0,0,0.2)';
+            context.fillRect(x * size + 2, y * size + size - 5, size - 4, 3);
+        }
+        
+        function draw() {
+            ctx.fillStyle = '#000';
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+            
+            ctx.strokeStyle = '#222';
+            ctx.lineWidth = 1;
+            for (let i = 0; i <= COLS; i++) {
+                ctx.beginPath();
+                ctx.moveTo(i * BLOCK_SIZE, 0);
+                ctx.lineTo(i * BLOCK_SIZE, canvas.height);
+                ctx.stroke();
+            }
+            for (let i = 0; i <= ROWS; i++) {
+                ctx.beginPath();
+                ctx.moveTo(0, i * BLOCK_SIZE);
+                ctx.lineTo(canvas.width, i * BLOCK_SIZE);
+                ctx.stroke();
+            }
+            
+            for (let r = 0; r < ROWS; r++) {
+                for (let c = 0; c < COLS; c++) {
+                    if (board[r][c]) {
+                        drawBlock(ctx, c, r, board[r][c]);
+                    }
+                }
+            }
+            
+            if (currentPiece) {
+                const shape = rotateMatrix(currentPiece.shape, currentPiece.rotation);
+                for (let r = 0; r < shape.length; r++) {
+                    for (let c = 0; c < shape[r].length; c++) {
+                        if (shape[r][c]) {
+                            drawBlock(ctx, currentPiece.x + c, currentPiece.y + r, currentPiece.color);
+                        }
+                    }
+                }
+            }
+            
+            if (nextPiece) {
+                nextCtx.fillStyle = '#000';
+                nextCtx.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
+                const shape = nextPiece.shape;
+                const offsetX = Math.floor((4 - shape[0].length) / 2);
+                const offsetY = Math.floor((4 - shape.length) / 2);
+                for (let r = 0; r < shape.length; r++) {
+                    for (let c = 0; c < shape[r].length; c++) {
+                        if (shape[r][c]) {
+                            drawBlock(nextCtx, offsetX + c, offsetY + r, nextPiece.color, PREVIEW_BLOCK_SIZE);
+                        }
+                    }
+                }
+            }
+        }
+        
+        function update(time = 0) {
+            if (gameOver || paused) return;
+            
+            const deltaTime = time - lastTime;
+            lastTime = time;
+            dropCounter += deltaTime;
+            
+            if (dropCounter > dropInterval) {
+                dropPiece();
+            }
+            
+            draw();
+            requestAnimationFrame(update);
+        }
+        
+        function updateStats() {
+            document.getElementById('score').textContent = score;
+            document.getElementById('lines').textContent = lines;
+            document.getElementById('level').textContent = level;
+        }
+        
+        function endGame() {
+            gameOver = true;
+            document.getElementById('finalScore').textContent = score;
+            document.getElementById('gameOver').style.display = 'block';
+        }
+        
+        function resetGame() {
+            createBoard();
+            score = 0;
+            lines = 0;
+            level = 1;
+            dropInterval = 1000;
+            gameOver = false;
+            paused = false;
+            currentPiece = null;
+            nextPiece = null;
+            document.getElementById('gameOver').style.display = 'none';
+            document.getElementById('paused').style.display = 'none';
+            updateStats();
+            spawnPiece();
+            lastTime = 0;
+            dropCounter = 0;
+            update();
+        }
+        
+        function togglePause() {
+            if (gameOver) return;
+            paused = !paused;
+            document.getElementById('paused').style.display = paused ? 'block' : 'none';
+            if (!paused) {
+                lastTime = performance.now();
+                update();
+            }
+        }
+        
+        document.addEventListener('keydown', (e) => {
+            if (e.key === 'r' || e.key === 'R') {
+                if (gameOver) resetGame();
+                return;
+            }
+            
+            if (gameOver) return;
+            
+            if (e.key === 'p' || e.key === 'P') {
+                togglePause();
+                return;
+            }
+            
+            if (paused) return;
+            
+            switch(e.key) {
+                case 'ArrowLeft':
+                    if (movePiece(-1, 0)) draw();
+                    break;
+                case 'ArrowRight':
+                    if (movePiece(1, 0)) draw();
+                    break;
+                case 'ArrowDown':
+                    if (movePiece(0, 1)) {
+                        score += 1;
+                        updateStats();
+                        draw();
+                    }
+                    break;
+                case 'ArrowUp':
+                case 'x':
+                case 'X':
+                    rotatePiece(true);
+                    draw();
+                    break;
+                case 'z':
+                case 'Z':
+                    rotatePiece(false);
+                    draw();
+                    break;
+                case ' ':
+                    hardDrop();
+                    updateStats();
+                    draw();
+                    break;
+            }
+        });
+        
+        createBoard();
+        spawnPiece();
+        updateStats();
+        update();
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/04692f72ef1d/kimi.png b/data/artifacts/04692f72ef1d/kimi.png
new file mode 100644
index 0000000..1eff346
Binary files /dev/null and b/data/artifacts/04692f72ef1d/kimi.png differ
diff --git a/data/artifacts/04692f72ef1d/qwen25-7b.html b/data/artifacts/04692f72ef1d/qwen25-7b.html
new file mode 100644
index 0000000..9943a13
--- /dev/null
+++ b/data/artifacts/04692f72ef1d/qwen25-7b.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Tetris</title>
+    <style>
+        body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
+        #game { width: 320px; height: 640px; background: #000; border: 1px solid #fff; overflow: hidden; position: relative; }
+        .row { display: flex; justify-content: space-between; align-items: center; width: 100%; margin-top: -20px; }
+        .cell { width: 20px; height: 20px; background: #ccc; border: 1px solid #fff; float: left; }
+        .piece { position: absolute; z-index: 2; top: 0; left: 0; }
+    </style>
+</head>
+<body>
+<div id="game"></div>
+<script>
+const game = document.getElementById('game')
+const cells = Array.from({length:32},(_,i)=>(i%16<8?'#fff':'#000').slice(-1))
+let grid = Array.from({length:20},()=>cells.slice())
+let piece, pieceX, pieceY, score = 0
+let nextPieceIndex = Math.floor(Math.random() * 7)
+const pieces = [{x:[1,3,4,6],[0,1,2,3]},{x:[1,3,5,7],[0,1,2,3]},{x:[0,1,3,5],[1,2,4,5]},{x:[0,1,2,3],[1,2,3,6]},{x:[0,1,3,4],[1,2,4,5]},{x:[0,1,2,5],[1,3,4,5]},{x:[0,1,2,3],[1,2,3,5]}]
+const pieceSpawns = [6,7,8,9,10,11,12]
+let t = 0
+let intervalId
+
+function render() {
+    grid.forEach((row,i) => 
+        row.map(cell => game.innerHTML += `<div class="cell ${cell}"></div>`).splice(16)
+    ).splice(4)
+    
+    if(piece) pieceSpawns[piece.index] ? (piece = pieces[pieceSpawns[piece.index++]]) : clearInterval(intervalId)
+    else {
+        piece = {x: [0,2,3,5], y: 0, index: nextPieceIndex}
+        nextPieceIndex = Math.floor(Math.random() * 7)
+        if(!pieces.some(p => p.x.every(x => (pieceX + x) < 16 && grid[pieceY + 1][pieceX + x] == '#fff'))) return clearInterval(intervalId)
+    }
+    
+    const {x, y} = piece
+    game.innerHTML += `<div class="piece" style="left:${(pieceX * 20)}px;top:${(pieceY * 20)}px">${x.map(x => `<div class="cell"></div>`).join('')}</div>`
+
+    if(pieceY == -1) {
+        for(let i = 4; i < 20; i++) 
+            grid[i] = cells.slice()
+        
+        const cleared = [...grid].reverse().some(row => row.every(cell => cell == '#fff'))
+        if(cleared) score += 10 * (cleared.length + 1)
+    }
+}
+
+function key(e) {
+    switch(e.keyCode) {
+        case 37: pieceX = Math.max(0, pieceX - 1); break
+        case 39: pieceX = Math.min(pieceX + 1, 15 - x.length); break
+        case 40: 
+            if(grid[pieceY + 1].every((cell, i) => cell == '#fff' || (x[i] && cells.includes(cell)))) {
+                pieceY++
+                render()
+            }
+            break
+        case 38: 
+            const {x, y} = piece
+            x.map(x => grid[pieceY][pieceX + x] != '#fff').every(free => free) ? (y++, render()) : null
+    }
+}
+
+document.addEventListener('keydown', key)
+setInterval(render, t = 500 - Math.floor(score / 3))
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/04692f72ef1d/qwen25-7b.png b/data/artifacts/04692f72ef1d/qwen25-7b.png
new file mode 100644
index 0000000..cc10538
Binary files /dev/null and b/data/artifacts/04692f72ef1d/qwen25-7b.png differ
diff --git a/data/artifacts/04692f72ef1d/qwen3-14b.html b/data/artifacts/04692f72ef1d/qwen3-14b.html
new file mode 100644
index 0000000..16039f5
--- /dev/null
+++ b/data/artifacts/04692f72ef1d/qwen3-14b.html
@@ -0,0 +1,267 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Tetris</title>
+  <style>
+    body {
+      background: #000;
+      color: #fff;
+      font-family: sans-serif;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      height: 100vh;
+      margin: 0;
+    }
+    canvas {
+      border: 2px solid #fff;
+      background: #111;
+    }
+    #info {
+      margin-top: 10px;
+      font-size: 16px;
+    }
+    #next {
+      margin-top: 10px;
+      font-size: 14px;
+      white-space: pre;
+    }
+  </style>
+</head>
+<body>
+  <canvas id="tetris" width="240" height="400"></canvas>
+  <div id="info">Score: 0 | Level: 1</div>
+  <div id="next">Next:</div>
+  <script>
+    const canvas = document.getElementById("tetris");
+    const context = canvas.getContext("2d");
+    const scale = 20;
+    const rows = canvas.height / scale;
+    const columns = canvas.width / scale;
+    const scoreDisplay = document.getElementById("info");
+    const nextDisplay = document.getElementById("next");
+
+    const colors = [
+      null,
+      "#FF0D72",
+      "#0DC2FF",
+      "#0DFF72",
+      "#F538FF",
+      "#FF8E0D",
+      "#FFE138",
+      "#3877FF",
+    ];
+
+    const tetrominoes = [
+      [ [1,1,1,1] ], // I
+      [ [1,1,1], [0,1,0] ], // T
+      [ [1,1,0], [0,1,1] ], // S
+      [ [0,1,1], [1,1,0] ], // Z
+      [ [1,1,1], [1,0,0] ], // J
+      [ [1,1,1], [0,0,1] ], // L
+      [ [1,0,0], [1,1,1] ], // O
+    ];
+
+    let score = 0;
+    let level = 1;
+    let dropCounter = 0;
+    let dropInterval = 1000;
+    let lastTime = 0;
+    let gameOver = false;
+    let pause = false;
+
+    let playingField = createMatrix(columns, rows);
+    let currentPiece = createPiece(randomPiece());
+    let nextPiece = createPiece(randomPiece());
+    let piecePosition = { x: 0, y: 0 };
+
+    function createMatrix(w, h) {
+      const matrix = [];
+      while (h--) {
+        matrix.push(new Array(w).fill(0));
+      }
+      return matrix;
+    }
+
+    function createPiece(type) {
+      if (!tetrominoes[type]) return;
+      return tetrominoes[type].map(row => row.slice().map(col => col ? colors[type] : 0));
+    }
+
+    function randomPiece() {
+      return Math.floor(Math.random() * tetrominoes.length);
+    }
+
+    function drawMatrix(matrix, offset) {
+      matrix.forEach((row, y) => {
+        row.forEach((value, x) => {
+          if (value !== 0) {
+            context.fillStyle = value;
+            context.fillRect((x + offset.x) * scale, (y + offset.y) * scale, scale, scale);
+          }
+        });
+      });
+    }
+
+    function draw() {
+      context.fillStyle = "#000";
+      context.fillRect(0, 0, canvas.width, canvas.height);
+      drawMatrix(playingField, { x: 0, y: 0 });
+      drawMatrix(currentPiece, piecePosition);
+      drawNextPiece();
+    }
+
+    function merge(playingField, piece, offset) {
+      piece.forEach((row, y) => {
+        row.forEach((value, x) => {
+          if (value !== 0) {
+            playingField[y + offset.y][x + offset.x] = value;
+          }
+        });
+      });
+    }
+
+    function collide(playingField, piece, offset) {
+      for (let y = 0; y < piece.length; y++) {
+        for (let x = 0; x < piece[y].length; x++) {
+          if (piece[y][x] !== 0 &&
+              (playingField[y + offset.y] &&
+               playingField[y + offset.y][x + offset.x]) !== 0) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    function rotate(matrix, dir) {
+      for (let y = 0; y < matrix.length; y++) {
+        for (let x = 0; x < y; x++) {
+          [matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]];
+        }
+      }
+      if (dir > 0) {
+        matrix.forEach(row => row.reverse());
+      } else {
+        matrix.reverse();
+      }
+    }
+
+    function playerDrop() {
+      piecePosition.y++;
+      if (collide(playingField, currentPiece, piecePosition)) {
+        piecePosition.y--;
+        merge(playingField, currentPiece, piecePosition);
+        currentPiece = nextPiece;
+        nextPiece = createPiece(randomPiece());
+        piecePosition = { x: 0, y: 0 };
+        if (collide(playingField, currentPiece, piecePosition)) {
+          gameOver = true;
+        }
+        clearRows();
+      }
+    }
+
+    function clearRows() {
+      let rowsCleared = 1;
+      outer: for (let y = playingField.length - 1; y > 0; y--) {
+        if (playingField[y].every(cell => cell !== 0)) {
+          rowsCleared++;
+          for (let yy = y; yy > 0; yy--) {
+            playingField[yy] = playingField[yy - 1].slice();
+          }
+          y++;
+        } else {
+          break outer;
+        }
+      }
+      if (rowsCleared > 1) {
+        score += rowsCleared * 100 * level;
+        level = Math.floor(score / 1000) + 1;
+        dropInterval = 1000 - (level * 50);
+      }
+      updateScore();
+    }
+
+    function updateScore() {
+      scoreDisplay.textContent = `Score: ${score} | Level: ${level}`;
+    }
+
+    function drawNextPiece() {
+      nextDisplay.textContent = "Next:\n";
+      nextPiece.forEach(row => {
+        nextDisplay.textContent += row.map(cell => cell ? "■" : " ").join(" ") + "\n";
+      });
+    }
+
+    function playerMove(dir) {
+      piecePosition.x += dir;
+      if (collide(playingField, currentPiece, piecePosition)) {
+        piecePosition.x -= dir;
+      }
+    }
+
+    function playerRotate(dir) {
+      const prevRotation = currentPiece;
+      rotate(currentPiece, dir);
+      if (collide(playingField, currentPiece, piecePosition)) {
+        currentPiece = prevRotation;
+      }
+    }
+
+    function update(time = 0) {
+      const deltaTime = time - lastTime;
+      lastTime = time;
+      dropCounter += deltaTime;
+      if (dropCounter > dropInterval && !gameOver && !pause) {
+        playerDrop();
+        dropCounter = 0;
+      }
+      draw();
+      if (!gameOver) {
+        requestAnimationFrame(update);
+      }
+    }
+
+    function resetGame() {
+      playingField = createMatrix(columns, rows);
+      score = 0;
+      level = 1;
+      dropInterval = 1000;
+      gameOver = false;
+      pause = false;
+      currentPiece = createPiece(randomPiece());
+      nextPiece = createPiece(randomPiece());
+      piecePosition = { x: 0, y: 0 };
+      updateScore();
+      update();
+    }
+
+    document.addEventListener("keydown", event => {
+      if (gameOver) {
+        if (event.key === "Enter") {
+          resetGame();
+        }
+        return;
+      }
+      if (event.key === "ArrowLeft") {
+        playerMove(-1);
+      } else if (event.key === "ArrowRight") {
+        playerMove(1);
+      } else if (event.key === "ArrowDown") {
+        playerDrop();
+      } else if (event.key === "ArrowUp") {
+        playerRotate(1);
+      } else if (event.key === " ") {
+        playerDrop();
+      } else if (event.key === "Escape") {
+        pause = !pause;
+      }
+    });
+
+    update();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/04692f72ef1d/qwen3-14b.png b/data/artifacts/04692f72ef1d/qwen3-14b.png
new file mode 100644
index 0000000..280cb9a
Binary files /dev/null and b/data/artifacts/04692f72ef1d/qwen3-14b.png differ
diff --git a/data/artifacts/0cb3fc06b069/claude-code.html b/data/artifacts/0cb3fc06b069/claude-code.html
new file mode 100644
index 0000000..ded0fe1
--- /dev/null
+++ b/data/artifacts/0cb3fc06b069/claude-code.html
@@ -0,0 +1,265 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Particle Text</title>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  html, body { height: 100%; overflow: hidden; background: #05060a; }
+  body {
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+    display: flex; flex-direction: column;
+    color: #e8ecf4;
+  }
+  #stage { position: fixed; inset: 0; }
+  canvas { display: block; width: 100%; height: 100%; }
+
+  .ui {
+    position: fixed; left: 50%; bottom: 34px; transform: translateX(-50%);
+    display: flex; gap: 10px; align-items: center;
+    padding: 10px 12px;
+    background: rgba(18, 22, 34, 0.55);
+    border: 1px solid rgba(255,255,255,0.10);
+    border-radius: 16px;
+    backdrop-filter: blur(14px);
+    -webkit-backdrop-filter: blur(14px);
+    box-shadow: 0 12px 40px rgba(0,0,0,0.45);
+    z-index: 10;
+    max-width: calc(100vw - 28px);
+  }
+  .ui input[type="text"] {
+    width: min(46vw, 320px);
+    padding: 11px 14px;
+    font-size: 15px;
+    color: #fff;
+    background: rgba(255,255,255,0.06);
+    border: 1px solid rgba(255,255,255,0.12);
+    border-radius: 11px;
+    outline: none;
+    transition: border-color .2s, background .2s;
+  }
+  .ui input[type="text"]:focus {
+    border-color: rgba(120,180,255,0.6);
+    background: rgba(255,255,255,0.10);
+  }
+  .ui input::placeholder { color: rgba(255,255,255,0.35); }
+  .ui button {
+    padding: 11px 18px;
+    font-size: 14px; font-weight: 600;
+    color: #071018;
+    background: linear-gradient(135deg, #8fd3ff, #b7a6ff);
+    border: none; border-radius: 11px;
+    cursor: pointer;
+    transition: transform .12s, filter .2s;
+    white-space: nowrap;
+  }
+  .ui button:hover { filter: brightness(1.08); }
+  .ui button:active { transform: scale(0.96); }
+
+  .hint {
+    position: fixed; top: 22px; left: 50%; transform: translateX(-50%);
+    font-size: 12.5px; letter-spacing: .3px;
+    color: rgba(255,255,255,0.42);
+    z-index: 10; text-align: center; pointer-events: none;
+    width: 100%;
+  }
+</style>
+</head>
+<body>
+  <div id="stage"><canvas id="c"></canvas></div>
+  <div class="hint">Type a word &middot; move your mouse through the letters to scatter them</div>
+  <div class="ui">
+    <input id="word" type="text" value="HELLO" maxlength="18" spellcheck="false" autocomplete="off" placeholder="type a word…">
+    <button id="go">Form</button>
+  </div>
+
+<script>
+(function () {
+  "use strict";
+
+  var canvas = document.getElementById("c");
+  var ctx = canvas.getContext("2d", { alpha: false });
+  var input = document.getElementById("word");
+  var button = document.getElementById("go");
+
+  var DPR = Math.min(window.devicePixelRatio || 1, 2);
+  var W = 0, H = 0;
+  var particles = [];
+  var mouse = { x: -9999, y: -9999, active: false, r: 90 };
+
+  // Offscreen canvas for sampling text pixels
+  var sampler = document.createElement("canvas");
+  var sctx = sampler.getContext("2d", { willReadFrequently: true });
+
+  function resize() {
+    W = window.innerWidth;
+    H = window.innerHeight;
+    canvas.width = W * DPR;
+    canvas.height = H * DPR;
+    canvas.style.width = W + "px";
+    canvas.style.height = H + "px";
+    ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
+    mouse.r = Math.max(70, Math.min(W, H) * 0.11);
+  }
+
+  // Sample target points from rendered text
+  function targetsForText(text) {
+    text = (text || "").trim();
+    if (!text) text = " ";
+
+    sampler.width = W;
+    sampler.height = H;
+    sctx.clearRect(0, 0, W, H);
+    sctx.fillStyle = "#fff";
+    sctx.textAlign = "center";
+    sctx.textBaseline = "middle";
+
+    // Fit font size to width
+    var maxW = W * 0.82;
+    var maxH = H * 0.5;
+    var size = Math.min(maxH, 340);
+    sctx.font = "800 " + size + "px -apple-system, 'Segoe UI', Roboto, Arial, sans-serif";
+    var measured = sctx.measureText(text).width;
+    if (measured > maxW) {
+      size = Math.max(28, size * (maxW / measured));
+      sctx.font = "800 " + size + "px -apple-system, 'Segoe UI', Roboto, Arial, sans-serif";
+    }
+    sctx.fillText(text, W / 2, H / 2);
+
+    var img = sctx.getImageData(0, 0, W, H).data;
+    // gap scales so particle count stays reasonable
+    var gap = W * H > 1500000 ? 5 : 4;
+    var pts = [];
+    for (var y = 0; y < H; y += gap) {
+      for (var x = 0; x < W; x += gap) {
+        var alpha = img[(y * W + x) * 4 + 3];
+        if (alpha > 128) {
+          pts.push({ x: x, y: y });
+        }
+      }
+    }
+    return pts;
+  }
+
+  function hueColor(t) {
+    // pleasant cyan->violet gradient based on x position
+    var h = 190 + t * 90;
+    return "hsl(" + h + ", 90%, 66%)";
+  }
+
+  function build(text) {
+    var pts = targetsForText(text);
+
+    // shuffle for organic fly-in
+    for (var i = pts.length - 1; i > 0; i--) {
+      var j = (Math.random() * (i + 1)) | 0;
+      var tmp = pts[i]; pts[i] = pts[j]; pts[j] = tmp;
+    }
+
+    var need = pts.length;
+    // Grow or shrink particle pool
+    while (particles.length < need) {
+      particles.push({
+        x: Math.random() * W,
+        y: Math.random() * H,
+        vx: 0, vy: 0,
+        tx: 0, ty: 0,
+        size: 1.6 + Math.random() * 1.4,
+        color: "#8fd3ff"
+      });
+    }
+    if (particles.length > need) particles.length = need;
+
+    for (var k = 0; k < need; k++) {
+      var p = particles[k];
+      p.tx = pts[k].x;
+      p.ty = pts[k].y;
+      p.color = hueColor(pts[k].x / W);
+      p.size = 1.5 + Math.random() * 1.6;
+    }
+  }
+
+  function tick() {
+    // fade trail
+    ctx.fillStyle = "rgba(5, 6, 10, 0.28)";
+    ctx.fillRect(0, 0, W, H);
+
+    var mr = mouse.r, mr2 = mr * mr;
+    for (var i = 0; i < particles.length; i++) {
+      var p = particles[i];
+
+      // spring toward target
+      var dx = p.tx - p.x;
+      var dy = p.ty - p.y;
+      p.vx += dx * 0.014;
+      p.vy += dy * 0.014;
+
+      // mouse repulsion
+      if (mouse.active) {
+        var mx = p.x - mouse.x;
+        var my = p.y - mouse.y;
+        var d2 = mx * mx + my * my;
+        if (d2 < mr2 && d2 > 0.0001) {
+          var d = Math.sqrt(d2);
+          var force = (mr - d) / mr;
+          var f = force * 5.2;
+          p.vx += (mx / d) * f;
+          p.vy += (my / d) * f;
+        }
+      }
+
+      // friction
+      p.vx *= 0.86;
+      p.vy *= 0.86;
+      p.x += p.vx;
+      p.y += p.vy;
+
+      ctx.fillStyle = p.color;
+      ctx.beginPath();
+      ctx.arc(p.x, p.y, p.size, 0, 6.2832);
+      ctx.fill();
+    }
+
+    requestAnimationFrame(tick);
+  }
+
+  // ---- events ----
+  function moveHandler(x, y) {
+    mouse.x = x; mouse.y = y; mouse.active = true;
+  }
+  window.addEventListener("mousemove", function (e) {
+    moveHandler(e.clientX, e.clientY);
+  });
+  window.addEventListener("mouseout", function () { mouse.active = false; });
+  window.addEventListener("touchmove", function (e) {
+    if (e.touches[0]) {
+      moveHandler(e.touches[0].clientX, e.touches[0].clientY);
+    }
+  }, { passive: true });
+  window.addEventListener("touchend", function () { mouse.active = false; });
+
+  window.addEventListener("resize", function () {
+    resize();
+    build(input.value);
+  });
+
+  function submit() {
+    build(input.value);
+    input.blur();
+  }
+  button.addEventListener("click", submit);
+  input.addEventListener("keydown", function (e) {
+    if (e.key === "Enter") submit();
+  });
+
+  // ---- init ----
+  resize();
+  ctx.fillStyle = "#05060a";
+  ctx.fillRect(0, 0, W, H);
+  build(input.value);
+  requestAnimationFrame(tick);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0cb3fc06b069/claude-code.png b/data/artifacts/0cb3fc06b069/claude-code.png
new file mode 100644
index 0000000..d531cda
Binary files /dev/null and b/data/artifacts/0cb3fc06b069/claude-code.png differ
diff --git a/data/artifacts/0cb3fc06b069/gemma3-12b.html b/data/artifacts/0cb3fc06b069/gemma3-12b.html
new file mode 100644
index 0000000..8717207
--- /dev/null
+++ b/data/artifacts/0cb3fc06b069/gemma3-12b.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Particle Word</title>
+<style>
+body { margin: 0; overflow: hidden; background-color: #111; }
+canvas { display: block; }
+</style>
+</head>
+<body>
+
+<canvas id="myCanvas"></canvas>
+
+<script>
+  const canvas = document.getElementById("myCanvas");
+  const ctx = canvas.getContext("2d");
+
+  let particles = [];
+  let word = "HELLO"; // Change this to the desired word
+  let fontSize = 50;
+  let particleSize = 3;
+  let particleColor = "#fff";
+  let speedX = 1;
+  let speedY = 1;
+
+  canvas.width = window.innerWidth;
+  canvas.height = window.innerHeight;
+
+  function initParticles() {
+    particles = [];
+    const wordWidth = ctx.measureText(word).width;
+
+    for (let i = 0; i < word.length * 5 ; i++) { // More particles for density
+      particles.push({
+        x: canvas.width / 2 - wordWidth / 2 + Math.random() * wordWidth,
+        y: canvas.height / 2 + Math.random() * 50,
+        dx: (Math.random() - 0.5) * speedX,
+        dy: (Math.random() - 0.5) * speedY,
+        charIndex: Math.floor(Math.random() * word.length),
+      });
+    }
+  }
+
+  function drawParticle(particle) {
+    ctx.fillStyle = particleColor;
+    ctx.font = `${fontSize}px sans-serif`;
+    ctx.textAlign = "center";
+    ctx.fillText(word[particle.charIndex], particle.x, particle.y);
+  }
+
+  function updateParticles() {
+    particles.forEach(particle => {
+      particle.x += particle.dx;
+      particle.y += particle.dy;
+
+      if (particle.x < 0 || particle.x > canvas.width) {
+        particle.dx = -particle.dx;
+      }
+      if (particle.y < 0 || particle.y > canvas.height) {
+        particle.dy = -particle.dy;
+      }
+    });
+  }
+
+  function draw() {
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+    particles.forEach(particle => {
+      drawParticle(particle);
+    });
+  }
+
+
+  let mouseX = 0;
+  let mouseY = 0;
+  canvas.addEventListener("mousemove", (e) => {
+    mouseX = e.clientX;
+    mouseY = e.clientY;
+  });
+
+  let scatterEnabled = true;
+
+  function scatterParticles() {
+      if(scatterEnabled){
+        particles.forEach(particle =>{
+            const distance = Math.sqrt((mouseX - particle.x)**2 + (mouseY - particle.y)**2);
+            if(distance < 100){
+                particle.dx = (Math.random() - 0.5) * speedX * 3;
+                particle.dy = (Math.random() - 0.5) * speedY * 3;
+            }
+        });
+      }
+  }
+
+    let animationFrameId;
+
+
+  function animate() {
+      updateParticles();
+      draw();
+      scatterParticles();
+      animationFrameId = requestAnimationFrame(animate);
+  }
+
+
+
+  initParticles();
+  canvas.width = window.innerWidth;
+  canvas.height = window.innerHeight;
+
+  window.addEventListener("resize", () => {
+    canvas.width = window.innerWidth;
+    canvas.height = window.innerHeight;
+    initParticles();
+  });
+
+  animate();
+
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0cb3fc06b069/gemma3-12b.png b/data/artifacts/0cb3fc06b069/gemma3-12b.png
new file mode 100644
index 0000000..f696d38
Binary files /dev/null and b/data/artifacts/0cb3fc06b069/gemma3-12b.png differ
diff --git a/data/artifacts/0cb3fc06b069/hermes3-8b.html b/data/artifacts/0cb3fc06b069/hermes3-8b.html
new file mode 100644
index 0000000..5782177
--- /dev/null
+++ b/data/artifacts/0cb3fc06b069/hermes3-8b.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Particle Text</title>
+    <style>
+        body {
+            background: #000;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            height: 100vh;
+            margin: 0;
+            overflow: hidden;
+        }
+
+        canvas {
+            border: 1px solid #ccc;
+        }
+    </style>
+</head>
+<body>
+    <canvas id="canvas" width="800" height="400"></canvas>
+
+    <script>
+        const canvas = document.getElementById('canvas');
+        const ctx = canvas.getContext('2d');
+
+        let particles = [];
+        const numParticles = 1000;
+        const word = 'HELLO WORLD';
+
+        class Particle {
+            constructor(x, y) {
+                this.x = x;
+                this.y = y;
+                this.size = 1;
+                this.speedX = Math.random() * 3 - 1.5;
+                this.speedY = Math.random() * 3 - 1.5;
+            }
+
+            update() {
+                this.x += this.speedX;
+                this.y += this.speedY;
+
+                if (this.size > 0) {
+                    this.size -= 0.03;
+                }
+            }
+
+            draw() {
+                ctx.beginPath();
+                ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
+                ctx.fillStyle = `rgba(255, 255, 255, ${this.size})`;
+                ctx.fill();
+            }
+        }
+
+        function createParticles(word) {
+            const wordsWidth = canvas.measureText(word).width;
+
+            for (let i = 0; i < numParticles; i++) {
+                const x = Math.random() * (canvas.width - wordsWidth);
+                const y = Math.random() * canvas.height;
+                particles.push(new Particle(x, y));
+            }
+        }
+
+        function draw() {
+            ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+            for (let i = 0; i < particles.length; i++) {
+                particles[i].draw();
+                particles[i].update();
+
+                if (particles[i].size <= 0) {
+                    particles.splice(i, 1);
+                    i--;
+                }
+            }
+
+            if (particles.length < numParticles) {
+                createParticles(word);
+            }
+        }
+
+        function writeWord() {
+            ctx.font = "60px Arial";
+            ctx.fillStyle = "#fff";
+            ctx.textAlign = "center";
+            ctx.fillText(word, canvas.width / 2, canvas.height / 2);
+
+            particles = [];
+        }
+
+        document.onmousemove = (e) => {
+            if (!particles.length && !canvas.children.length) {
+                writeWord();
+            }
+        };
+
+        setInterval(draw, 10);
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0cb3fc06b069/hermes3-8b.png b/data/artifacts/0cb3fc06b069/hermes3-8b.png
new file mode 100644
index 0000000..a4ceeff
Binary files /dev/null and b/data/artifacts/0cb3fc06b069/hermes3-8b.png differ
diff --git a/data/artifacts/0cb3fc06b069/hf-qwen-coder-32b.html b/data/artifacts/0cb3fc06b069/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..0d668e0
--- /dev/null
+++ b/data/artifacts/0cb3fc06b069/hf-qwen-coder-32b.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Particle Text</title>
+<style>
+body { margin: 0; overflow: hidden; background-color: #000; display: flex; justify-content: center; align-items: center; height: 100vh; }
+canvas { display: block; }
+</style>
+</head>
+<body>
+<canvas id="c"></canvas>
+<script>
+const canvas = document.getElementById('c');
+const ctx = canvas.getContext('2d');
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+const text = "HELLO";
+const fontSize = 50;
+ctx.font = `${fontSize}px Arial`;
+const textWidth = ctx.measureText(text).width;
+const particles = [];
+class Particle {
+    constructor(x, y) {
+        this.x = x;
+        this.y = y;
+        this.size = Math.random() * 2 + 1;
+        this.baseX = x;
+        this.baseY = y;
+        this.density = (Math.random() * 40) + 5;
+    }
+    draw() {
+        ctx.fillStyle = 'white';
+        ctx.beginPath();
+        ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
+        ctx.closePath();
+        ctx.fill();
+    }
+    update(mouse) {
+        const dx = mouse.x - this.x;
+        const dy = mouse.y - this.y;
+        const distance = Math.sqrt(dx * dx + dy * dy);
+        let forceDirectionX = dx / distance;
+        let forceDirectionY = dy / distance;
+        const maxDistance = 100;
+        const force = (maxDistance - distance) / maxDistance;
+        const directionX = forceDirectionX * force * this.density;
+        const directionY = forceDirectionY * force * this.density;
+
+        if (distance < maxDistance) {
+            this.x -= directionX;
+            this.y -= directionY;
+        } else {
+            if (this.x !== this.baseX) {
+                let dx = this.x - this.baseX;
+                this.x -= dx / 10;
+            }
+            if (this.y !== this.baseY) {
+                let dy = this.y - this.baseY;
+                this.y -= dy / 10;
+            }
+        }
+    }
+}
+function init() {
+    particles.length = 0;
+    ctx.font = `${fontSize}px Arial`;
+    const textWidth = ctx.measureText(text).width;
+    ctx.fillText(text, canvas.width/2 - textWidth/2, canvas.height/2);
+    const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+    for (let y = 0; y < canvas.height; y++) {
+        for (let x = 0; x < canvas.width; x++) {
+            if (data[(y * 4 * canvas.width) + (x * 4) + 3] > 128) {
+                particles.push(new Particle(x, y));
+            }
+        }
+    }
+}
+function animate() {
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+    for (let i = 0; i < particles.length; i++) {
+        particles[i].draw();
+        particles[i].update(mouse);
+    }
+    requestAnimationFrame(animate);
+}
+
+const mouse = { x: undefined, y: undefined };
+window.addEventListener('mousemove', function(e) {
+    mouse.x = e.x;
+    mouse.y = e.y;
+});
+
+init();
+animate();
+
+window.onresize = () => {
+    canvas.width = window.innerWidth;
+    canvas.height = window.innerHeight;
+    init();
+};
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0cb3fc06b069/hf-qwen-coder-32b.png b/data/artifacts/0cb3fc06b069/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..6682b1c
Binary files /dev/null and b/data/artifacts/0cb3fc06b069/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/0cb3fc06b069/qwen25-7b.html b/data/artifacts/0cb3fc06b069/qwen25-7b.html
new file mode 100644
index 0000000..4c4bef2
--- /dev/null
+++ b/data/artifacts/0cb3fc06b069/qwen25-7b.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Particle Text</title>
+    <style>
+        body {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            height: 100vh;
+            background-color: #222;
+            margin: 0;
+            overflow: hidden;
+        }
+        canvas {
+            border: 1px solid #fff;
+        }
+    </style>
+</head>
+<body>
+    <canvas id="particleCanvas" width="800" height="400"></canvas>
+    <script>
+        const canvas = document.getElementById('particleCanvas');
+        const ctx = canvas.getContext('2d');
+        let particles = [];
+
+        function Particle(x, y, dx, dy, size) {
+            this.x = x;
+            this.y = y;
+            this.dx = dx;
+            this.dy = dy;
+            this.size = size;
+        }
+
+        Particle.prototype.update = function() {
+            this.x += this.dx;
+            this.y += this.dy;
+
+            if (this.x < 0 || this.x > canvas.width) {
+                this.dx *= -1;
+            }
+            if (this.y < 0 || this.y > canvas.height) {
+                this.dy *= -1;
+            }
+
+            this.size -= 0.1;
+        };
+
+        Particle.prototype.draw = function() {
+            ctx.beginPath();
+            ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
+            ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
+            ctx.fill();
+        };
+
+        function initParticles(word) {
+            for (let i = 0; i < word.length * 100; i++) {
+                let x = Math.random() * canvas.width;
+                let y = -canvas.height;
+                let dx = (Math.random() - 0.5) / 20;
+                let dy = (Math.random() + 1) / 2;
+                let size = 3 + Math.random() * 4;
+                particles.push(new Particle(x, y, dx, dy, size));
+            }
+        }
+
+        function animate(word) {
+            ctx.clearRect(0, 0, canvas.width, canvas.height);
+            for (let i = 0; i < particles.length; i++) {
+                let particle = particles[i];
+                if (particle.size > 1.5 && word.includes(particle.x * 32 + Math.floor(particle.y / 32))) {
+                    particle.draw();
+                    particle.update();
+                }
+            }
+
+            requestAnimationFrame(() => animate(word));
+        }
+
+        function onInput(event) {
+            const inputWord = event.target.value.toUpperCase();
+            initParticles(inputWord);
+            ctx.clearRect(0, 0, canvas.width, canvas.height);
+            animate(inputWord);
+        }
+
+        document.body.addEventListener('mousemove', () => {
+            particles.forEach(particle => particle.dy *= -1);
+        });
+
+        document.querySelector('input').addEventListener('input', onInput);
+
+        initParticles('HELLO');
+        animate('HELLO');
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0cb3fc06b069/qwen25-7b.png b/data/artifacts/0cb3fc06b069/qwen25-7b.png
new file mode 100644
index 0000000..a9d08c9
Binary files /dev/null and b/data/artifacts/0cb3fc06b069/qwen25-7b.png differ
diff --git a/data/artifacts/0cb3fc06b069/qwen3-14b.html b/data/artifacts/0cb3fc06b069/qwen3-14b.html
new file mode 100644
index 0000000..36408d4
--- /dev/null
+++ b/data/artifacts/0cb3fc06b069/qwen3-14b.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Particle Word</title>
+  <style>
+    html, body {
+      margin: 0;
+      padding: 0;
+      overflow: hidden;
+      background: #000;
+      height: 100%;
+      width: 100%;
+      font-family: 'Arial', sans-serif;
+    }
+    #word {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      color: #fff;
+      font-size: 48px;
+      pointer-events: none;
+      text-transform: uppercase;
+      text-align: center;
+    }
+    #controls {
+      position: absolute;
+      top: 20px;
+      left: 50%;
+      transform: translateX(-50%);
+      color: #fff;
+      font-size: 18px;
+    }
+  </style>
+</head>
+<body>
+  <div id="word">TYPE HERE</div>
+  <div id="controls">Type a word to see particles form it</div>
+  <canvas id="canvas"></canvas>
+  <script>
+    const canvas = document.getElementById('canvas');
+    const ctx = canvas.getContext('2d');
+    let width, height;
+    let particles = [];
+    let targetWord = 'TYPE HERE';
+    let isHovering = false;
+
+    function resize() {
+      width = canvas.width = window.innerWidth;
+      height = canvas.height = window.innerHeight;
+    }
+    window.addEventListener('resize', resize);
+    resize();
+
+    function createParticles(word, count = 2000) {
+      particles = [];
+      const chars = word.split('');
+      const totalChars = chars.length;
+      for (let i = 0; i < count; i++) {
+        const charIndex = Math.floor(i / (count / totalChars));
+        const char = chars[charIndex % totalChars];
+        const angle = (Math.random() * Math.PI * 2);
+        const radius = Math.random() * 150 + 100;
+        const x = width / 2 + Math.cos(angle) * radius;
+        const y = height / 2 + Math.sin(angle) * radius;
+        const size = Math.random() * 2 + 1;
+        const speed = Math.random() * 0.5 + 0.2;
+        particles.push({
+          x,
+          y,
+          targetX: width / 2 + (char.charCodeAt(0) - 65) * 30,
+          targetY: height / 2 + (Math.floor(i / totalChars)) * 30,
+          size,
+          speed,
+          vx: (Math.random() - 0.5) * 2,
+          vy: (Math.random() - 0.5) * 2,
+          char: char
+        });
+      }
+    }
+
+    function updateParticles() {
+      for (let p of particles) {
+        const dx = p.targetX - p.x;
+        const dy = p.targetY - p.y;
+        const dist = Math.sqrt(dx * dx + dy * dy);
+        if (dist < 2) {
+          p.x = p.targetX;
+          p.y = p.targetY;
+        } else {
+          const angle = Math.atan2(dy, dx);
+          p.x += Math.cos(angle) * p.speed;
+          p.y += Math.sin(angle) * p.speed;
+        }
+        if (isHovering) {
+          const nx = (Math.random() - 0.5) * 10;
+          const ny = (Math.random() - 0.5) * 10;
+          p.x += nx;
+          p.y += ny;
+        }
+      }
+    }
+
+    function drawParticles() {
+      ctx.clearRect(0, 0, width, height);
+      ctx.fillStyle = '#00ffcc';
+      for (let p of particles) {
+        ctx.beginPath();
+        ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
+        ctx.fill();
+      }
+    }
+
+    function animate() {
+      updateParticles();
+      drawParticles();
+      requestAnimationFrame(animate);
+    }
+
+    function resetParticles() {
+      createParticles(targetWord);
+    }
+
+    window.addEventListener('mousemove', () => {
+      isHovering = true;
+      setTimeout(() => {
+        isHovering = false;
+      }, 500);
+    });
+
+    window.addEventListener('keydown', (e) => {
+      if (e.key.length === 1 && e.key.match(/[a-zA-Z]/)) {
+        targetWord = e.key.toUpperCase();
+        resetParticles();
+      }
+    });
+
+    createParticles(targetWord);
+    animate();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0cb3fc06b069/qwen3-14b.png b/data/artifacts/0cb3fc06b069/qwen3-14b.png
new file mode 100644
index 0000000..0f57df8
Binary files /dev/null and b/data/artifacts/0cb3fc06b069/qwen3-14b.png differ
diff --git a/data/artifacts/1d4944290382/claude-code.html b/data/artifacts/1d4944290382/claude-code.html
new file mode 100644
index 0000000..1deff3d
--- /dev/null
+++ b/data/artifacts/1d4944290382/claude-code.html
@@ -0,0 +1,390 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Tiny Isometric City Builder</title>
+<style>
+  :root{
+    --bg1:#1b2440; --bg2:#0e1424; --panel:#141d33; --ink:#e8eefc;
+    --muted:#8ea1c9; --accent:#66d0a5;
+  }
+  *{box-sizing:border-box;}
+  html,body{height:100%;margin:0;}
+  body{
+    font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
+    background:radial-gradient(1200px 700px at 50% -10%,var(--bg1),var(--bg2));
+    color:var(--ink);
+    display:flex;flex-direction:column;align-items:center;
+    min-height:100%;padding:16px;
+  }
+  h1{
+    font-size:20px;font-weight:700;letter-spacing:.3px;margin:2px 0 2px;
+    display:flex;align-items:center;gap:8px;
+  }
+  h1 .dot{width:10px;height:10px;border-radius:50%;background:var(--accent);box-shadow:0 0 12px var(--accent);}
+  .hint{color:var(--muted);font-size:13px;margin:0 0 12px;text-align:center;max-width:640px;line-height:1.4;}
+  .stage{
+    background:linear-gradient(180deg,#0f1830,#0b1122);
+    border:1px solid rgba(255,255,255,.06);
+    border-radius:16px;
+    box-shadow:0 20px 60px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.04);
+    padding:10px;
+  }
+  canvas{display:block;border-radius:10px;cursor:pointer;touch-action:none;}
+  .controls{
+    display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:center;
+    margin-top:14px;
+  }
+  button{
+    font:inherit;font-weight:600;color:var(--ink);
+    background:linear-gradient(180deg,#26365e,#1b284a);
+    border:1px solid rgba(255,255,255,.12);
+    padding:9px 16px;border-radius:10px;cursor:pointer;
+    transition:transform .06s ease, filter .12s ease;
+  }
+  button:hover{filter:brightness(1.15);}
+  button:active{transform:translateY(1px);}
+  .counts{
+    display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-top:14px;
+  }
+  .chip{
+    display:flex;align-items:center;gap:8px;
+    background:var(--panel);border:1px solid rgba(255,255,255,.08);
+    padding:7px 12px;border-radius:999px;font-size:14px;
+  }
+  .sw{width:14px;height:14px;border-radius:4px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.35);}
+  .chip b{font-variant-numeric:tabular-nums;}
+  .foot{color:var(--muted);font-size:12px;margin-top:14px;}
+</style>
+</head>
+<body>
+  <h1><span class="dot"></span> Tiny Isometric City Builder</h1>
+  <p class="hint">Click any tile to cycle it: <b>grass → house → tree → road → shop → grass</b>. Use <b>Rotate&nbsp;View</b> to spin the camera. Build your little town!</p>
+
+  <div class="stage">
+    <canvas id="city" width="720" height="540"></canvas>
+  </div>
+
+  <div class="controls">
+    <button id="rotate">⟳ Rotate View</button>
+    <button id="clear">🗑 Clear</button>
+    <button id="fill">🎲 Randomize</button>
+  </div>
+
+  <div class="counts" id="counts"></div>
+  <div class="foot">Canvas · no libraries · self-contained</div>
+
+<script>
+(function(){
+  "use strict";
+  const canvas = document.getElementById('city');
+  const ctx = canvas.getContext('2d');
+  const countsEl = document.getElementById('counts');
+
+  const N = 10;                 // grid size
+  const TW = 60, TH = 30;       // tile width/height (iso diamond)
+  const originX = canvas.width/2;
+  const originY = 70;
+
+  // Tile types
+  const EMPTY=0, HOUSE=1, TREE=2, ROAD=3, SHOP=4;
+  const CYCLE = [EMPTY,HOUSE,TREE,ROAD,SHOP];
+  const TYPES = {
+    [EMPTY]:{name:'Grass', color:'#5fa855'},
+    [HOUSE]:{name:'House', color:'#d9a05b'},
+    [TREE] :{name:'Tree',  color:'#3f9d5c'},
+    [ROAD] :{name:'Road',  color:'#6a6f7a'},
+    [SHOP] :{name:'Shop',  color:'#4bb3c4'}
+  };
+
+  // grid[col][row]
+  const grid = [];
+  for(let c=0;c<N;c++){grid[c]=[];for(let r=0;r<N;r++)grid[c][r]=EMPTY;}
+
+  let rotation = 0;   // 0..3
+  let hover = null;   // {c,r}
+
+  // ---- rotation transforms (logical -> display) ----
+  function toDisplay(c,r){
+    switch(rotation){
+      case 0: return [c, r];
+      case 1: return [N-1-r, c];
+      case 2: return [N-1-c, N-1-r];
+      case 3: return [r, N-1-c];
+    }
+  }
+  // display -> logical (inverse)
+  function toLogical(dc,dr){
+    switch(rotation){
+      case 0: return [dc, dr];
+      case 1: return [dr, N-1-dc];
+      case 2: return [N-1-dc, N-1-dr];
+      case 3: return [N-1-dr, dc];
+    }
+  }
+
+  // display coords -> screen (top vertex of ground diamond)
+  function project(dc,dr){
+    return [
+      originX + (dc - dr) * (TW/2),
+      originY + (dc + dr) * (TH/2)
+    ];
+  }
+
+  // ---- color helpers ----
+  function shade(hex, amt){
+    const n = parseInt(hex.slice(1),16);
+    let R=(n>>16)&255, G=(n>>8)&255, B=n&255;
+    R=Math.max(0,Math.min(255,R+amt));
+    G=Math.max(0,Math.min(255,G+amt));
+    B=Math.max(0,Math.min(255,B+amt));
+    return 'rgb('+R+','+G+','+B+')';
+  }
+
+  function diamond(sx,sy){
+    return [
+      [sx, sy],                 // top
+      [sx+TW/2, sy+TH/2],       // right
+      [sx, sy+TH],              // bottom
+      [sx-TW/2, sy+TH/2]        // left
+    ];
+  }
+  function poly(pts){
+    ctx.beginPath();
+    ctx.moveTo(pts[0][0],pts[0][1]);
+    for(let i=1;i<pts.length;i++)ctx.lineTo(pts[i][0],pts[i][1]);
+    ctx.closePath();
+  }
+
+  // draw the flat ground tile
+  function drawGround(sx,sy,base,highlight){
+    const d = diamond(sx,sy);
+    poly(d);
+    ctx.fillStyle = highlight ? shade(base,35) : base;
+    ctx.fill();
+    ctx.lineWidth=1;
+    ctx.strokeStyle='rgba(0,0,0,.18)';
+    ctx.stroke();
+  }
+
+  // draw a raised iso cube (top at height h above ground), returns top diamond
+  function drawCube(sx,sy,h,color){
+    const tw=TW/2, th=TH/2;
+    // top diamond corners (raised)
+    const t_top=[sx, sy-h];
+    const t_right=[sx+tw, sy-h+th];
+    const t_bottom=[sx, sy-h+TH];
+    const t_left=[sx-tw, sy-h+th];
+    // bottom (ground) corners we need: bottom, right, left
+    const g_right=[sx+tw, sy+th];
+    const g_bottom=[sx, sy+TH];
+    const g_left=[sx-tw, sy+th];
+
+    // left face (front-left)
+    poly([t_left,t_bottom,g_bottom,g_left]);
+    ctx.fillStyle=shade(color,-40); ctx.fill();
+    ctx.strokeStyle='rgba(0,0,0,.25)'; ctx.stroke();
+    // right face (front-right)
+    poly([t_bottom,t_right,g_right,g_bottom]);
+    ctx.fillStyle=shade(color,-70); ctx.fill();
+    ctx.stroke();
+    // top face
+    poly([t_top,t_right,t_bottom,t_left]);
+    ctx.fillStyle=shade(color,20); ctx.fill();
+    ctx.stroke();
+
+    return {top:t_top,right:t_right,bottom:t_bottom,left:t_left};
+  }
+
+  // pitched roof on top of a cube's top diamond
+  function drawRoof(topD, roofH, color){
+    const apex=[topD.top[0], topD.top[1]-roofH+ (topD.bottom[1]-topD.top[1])/2 ];
+    // apex centered above the top diamond center
+    const cx=(topD.left[0]+topD.right[0])/2;
+    const cy=(topD.top[1]+topD.bottom[1])/2;
+    const peak=[cx, cy-roofH];
+    // front-left roof face
+    poly([peak, topD.left, topD.bottom]);
+    ctx.fillStyle=shade(color,10); ctx.fill();
+    ctx.strokeStyle='rgba(0,0,0,.28)'; ctx.stroke();
+    // front-right roof face
+    poly([peak, topD.bottom, topD.right]);
+    ctx.fillStyle=shade(color,-35); ctx.fill();
+    ctx.stroke();
+  }
+
+  function drawRoadTop(sx,sy){
+    const d=diamond(sx,sy);
+    poly(d);
+    ctx.fillStyle='#5a5f69'; ctx.fill();
+    ctx.strokeStyle='rgba(0,0,0,.25)'; ctx.stroke();
+    // dashed center line along the diamond's long axis
+    ctx.save();
+    ctx.setLineDash([5,5]);
+    ctx.strokeStyle='rgba(255,225,120,.85)';
+    ctx.lineWidth=2;
+    ctx.beginPath();
+    ctx.moveTo(sx-TW/2*0.55, sy+TH/2-TH*0.05);
+    ctx.lineTo(sx+TW/2*0.55, sy+TH/2-TH*0.05+0);
+    // horizontal-ish line across tile
+    ctx.moveTo(d[3][0]+8, d[3][1]);
+    ctx.lineTo(d[1][0]-8, d[1][1]);
+    ctx.stroke();
+    ctx.restore();
+  }
+
+  // draw a single logical tile at screen pos
+  function drawTile(c,r,sx,sy,isHover){
+    const t = grid[c][r];
+    if(t===EMPTY){
+      drawGround(sx,sy,TYPES[EMPTY].color,isHover);
+      return;
+    }
+    // grass base under everything except road
+    if(t!==ROAD) drawGround(sx,sy,shade(TYPES[EMPTY].color,-8),isHover);
+
+    if(t===ROAD){
+      drawRoadTop(sx,sy);
+      if(isHover){poly(diamond(sx,sy));ctx.fillStyle='rgba(255,255,255,.10)';ctx.fill();}
+    }
+    else if(t===HOUSE){
+      const top=drawCube(sx,sy,26,TYPES[HOUSE].color);
+      drawRoof(top,18,'#c0413a');
+    }
+    else if(t===SHOP){
+      const top=drawCube(sx,sy,40,TYPES[SHOP].color);
+      // flat parapet + little sign
+      drawRoof(top,10,'#e8613f');
+      ctx.fillStyle='rgba(255,255,255,.9)';
+      ctx.beginPath();
+      ctx.arc(sx, sy-40+ (top.bottom[1]-top.top[1])/2, 3, 0, Math.PI*2);
+      ctx.fill();
+    }
+    else if(t===TREE){
+      // trunk
+      drawCube(sx,sy,10,'#7a5230');
+      // foliage: rounded green blob using stacked cube + circle top
+      const top=drawCube(sx,sy-10,24,'#3f9d5c');
+      const cx=(top.left[0]+top.right[0])/2;
+      const cy=(top.top[1]+top.bottom[1])/2;
+      ctx.beginPath();
+      ctx.fillStyle=shade('#3f9d5c',22);
+      ctx.arc(cx, cy-4, TW*0.28, 0, Math.PI*2);
+      ctx.fill();
+      ctx.strokeStyle='rgba(0,0,0,.15)'; ctx.stroke();
+    }
+
+    if(isHover){
+      poly(diamond(sx,sy));
+      ctx.lineWidth=2;
+      ctx.strokeStyle='rgba(255,255,255,.55)';
+      ctx.stroke();
+      ctx.lineWidth=1;
+    }
+  }
+
+  function render(){
+    ctx.clearRect(0,0,canvas.width,canvas.height);
+
+    // build a draw list of logical tiles with their display + screen coords
+    const list=[];
+    for(let c=0;c<N;c++){
+      for(let r=0;r<N;r++){
+        const [dc,dr]=toDisplay(c,r);
+        const [sx,sy]=project(dc,dr);
+        list.push({c,r,dc,dr,sx,sy,depth:dc+dr});
+      }
+    }
+    // painter's algorithm: back (small depth) first
+    list.sort((a,b)=> a.depth-b.depth || a.dc-b.dc);
+
+    for(const it of list){
+      const isHover = hover && hover.c===it.c && hover.r===it.r;
+      drawTile(it.c,it.r,it.sx,it.sy,isHover);
+    }
+    updateCounts();
+  }
+
+  function updateCounts(){
+    const counts={};
+    for(const k of CYCLE) counts[k]=0;
+    for(let c=0;c<N;c++)for(let r=0;r<N;r++)counts[grid[c][r]]++;
+    countsEl.innerHTML='';
+    for(const k of CYCLE){
+      const el=document.createElement('div');
+      el.className='chip';
+      el.innerHTML='<span class="sw" style="background:'+TYPES[k].color+'"></span>'+
+                   TYPES[k].name+' <b>'+counts[k]+'</b>';
+      countsEl.appendChild(el);
+    }
+  }
+
+  // ---- picking: screen -> logical tile (ground plane) ----
+  function pick(mx,my){
+    const a=(mx-originX)/(TW/2);
+    const b=(my-originY)/(TH/2);
+    let dc=Math.floor((a+b)/2);
+    let dr=Math.floor((b-a)/2);
+    if(dc<0||dc>=N||dr<0||dr>=N) return null;
+    const [c,r]=toLogical(dc,dr);
+    if(c<0||c>=N||r<0||r>=N) return null;
+    return {c,r};
+  }
+
+  function relPos(e){
+    const rect=canvas.getBoundingClientRect();
+    const sx=canvas.width/rect.width, sy=canvas.height/rect.height;
+    const p=(e.touches&&e.touches[0])||e;
+    return [ (p.clientX-rect.left)*sx, (p.clientY-rect.top)*sy ];
+  }
+
+  canvas.addEventListener('mousemove',e=>{
+    const [mx,my]=relPos(e);
+    const t=pick(mx,my);
+    const changed = (!!t!==!!hover) || (t&&hover&&(t.c!==hover.c||t.r!==hover.r));
+    hover=t;
+    if(changed) render();
+  });
+  canvas.addEventListener('mouseleave',()=>{ if(hover){hover=null;render();} });
+
+  function place(e){
+    e.preventDefault();
+    const [mx,my]=relPos(e);
+    const t=pick(mx,my);
+    if(!t) return;
+    const cur=grid[t.c][t.r];
+    const idx=CYCLE.indexOf(cur);
+    grid[t.c][t.r]=CYCLE[(idx+1)%CYCLE.length];
+    hover=t;
+    render();
+  }
+  canvas.addEventListener('click',place);
+  canvas.addEventListener('touchstart',place,{passive:false});
+
+  document.getElementById('rotate').addEventListener('click',()=>{
+    rotation=(rotation+1)%4;
+    render();
+  });
+  document.getElementById('clear').addEventListener('click',()=>{
+    for(let c=0;c<N;c++)for(let r=0;r<N;r++)grid[c][r]=EMPTY;
+    render();
+  });
+  document.getElementById('fill').addEventListener('click',()=>{
+    for(let c=0;c<N;c++)for(let r=0;r<N;r++){
+      const roll=Math.random();
+      grid[c][r]= roll<0.45?EMPTY : roll<0.6?HOUSE : roll<0.75?TREE : roll<0.9?ROAD : SHOP;
+    }
+    render();
+  });
+
+  // seed a tiny starter town
+  grid[4][4]=HOUSE; grid[5][4]=HOUSE; grid[4][5]=SHOP;
+  grid[5][5]=ROAD;  grid[6][5]=ROAD;  grid[5][6]=ROAD;
+  grid[3][4]=TREE;  grid[6][6]=TREE;  grid[3][6]=TREE;
+
+  render();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1d4944290382/claude-code.png b/data/artifacts/1d4944290382/claude-code.png
new file mode 100644
index 0000000..96e4730
Binary files /dev/null and b/data/artifacts/1d4944290382/claude-code.png differ
diff --git a/data/artifacts/1d4944290382/gemma3-12b.html b/data/artifacts/1d4944290382/gemma3-12b.html
new file mode 100644
index 0000000..ffd6f48
--- /dev/null
+++ b/data/artifacts/1d4944290382/gemma3-12b.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Tiny Isometric City</title>
+    <style>
+        body { margin: 0; overflow: hidden; background-color: #eee; }
+        #canvasContainer { width: 800px; height: 600px; margin: 50px auto; border: 1px solid black; position: relative;}
+        #rotateButton { position: absolute; top: 10px; right: 10px; padding: 5px 10px; background-color: #333; color: white; cursor: pointer; }
+        #counter { position: absolute; bottom: 10px; left: 10px; color: black; }
+
+    </style>
+</head>
+<body>
+    <div id="canvasContainer">
+        <button id="rotateButton">Rotate View</button>
+        <div id="counter"></div>
+        <canvas id="cityCanvas" width="800" height="600"></canvas>
+    </div>
+
+    <script>
+        const canvas = document.getElementById('cityCanvas');
+        const ctx = canvas.getContext('2d');
+        const rotateButton = document.getElementById('rotateButton');
+        const counterDiv = document.getElementById('counter');
+
+        let gridSize = 30;
+        let data = [];
+        let buildingTypes = ['house', 'tree', 'road', 'shop'];
+        let currentBuildingTypeIndex = 0;
+        let rotateAngle = 0;
+
+
+        function init() {
+            for (let x = 0; x < canvas.width / gridSize; x++) {
+                data[x] = [];
+                for (let y = 0; y < canvas.height / gridSize; y++) {
+                    data[x][y] = null; // Initialize all tiles as empty
+                }
+            }
+
+            updateCounter();
+        }
+
+        function drawIsometric(x, y, type) {
+            const tileWidth = gridSize;
+            const tileHeight = gridSize * 0.866;
+            const zOffset = 5;  // Depth offset for isometric effect. Adjust as needed.
+
+            let color;
+            switch (type) {
+                case 'house': color = '#a0785a'; break;
+                case 'tree': color = '#228b22'; break;
+                case 'road': color = '#8b4513'; break;
+                case 'shop': color = '#d2691e'; break;
+                default: color = '#777';
+            }
+
+
+            let isoX = (x - y) * tileWidth / 2;
+            let isoY = (x + y) * tileHeight / 2;
+
+            ctx.fillStyle = color;
+            //Draw isometric rectangle
+            if(type === 'house'){
+                ctx.fillRect(isoX, isoY - tileHeight/3, tileWidth/1.5, tileHeight*0.7); // Draw the house shape
+            } else if (type === 'tree'){
+                ctx.fillRect(isoX, isoY - tileHeight / 2 , tileWidth * 0.8, tileHeight * 0.4 );
+            } else if (type === 'road') {
+                 ctx.fillRect(isoX, isoY - tileHeight/2, tileWidth*1.3, tileHeight * 0.2);
+
+            }else if (type === 'shop'){
+                ctx.fillRect(isoX, isoY - tileHeight /3 , tileWidth/1.5, tileHeight*0.7 ); // Draw the shop shape
+            }
+
+
+        }
+
+
+
+        function draw() {
+            ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+            for (let x = 0; x < data.length; x++) {
+                for (let y = 0; y < data[x].length; y++) {
+                    if (data[x][y] !== null) {
+                        drawIsometric(x, y, data[x][y]);
+                    }
+                }
+            }
+        }
+
+       function updateCounter(){
+           let houseCount = 0;
+           let treeCount = 0;
+           let roadCount = 0;
+           let shopCount = 0;
+           for (let x = 0; x < data.length; x++) {
+               for (let y = 0; y < data[x].length; y++) {
+                   if (data[x][y] === 'house') houseCount++;
+                   if (data[x][y] === 'tree') treeCount++;
+                   if (data[x][y] === 'road') roadCount++;
+                   if (data[x][y] === 'shop') shopCount++;
+               }
+           }
+           counterDiv.textContent = `House: ${houseCount}, Tree: ${treeCount}, Road: ${roadCount}, Shop: ${shopCount}`;
+       }
+
+
+        function handleClick(event) {
+            const x = Math.floor(event.offsetX / gridSize);
+            const y = Math.floor(event.offsetY / gridSize);
+
+            if (x >= 0 && x < data.length && y >= 0 && y < data[x].length) {
+                data[x][y] = buildingTypes[currentBuildingTypeIndex];
+                draw();
+                updateCounter();
+            }
+        }
+
+
+
+        rotateButton.addEventListener('click', () => {
+          rotateAngle += 45;
+          if(rotateAngle > 360) rotateAngle -= 360;
+
+           ctx.setTransform(Math.cos(rotateAngle * Math.PI / 180), Math.sin(rotateAngle * Math.PI / 180), -Math.sin(rotateAngle * Math.PI / 180), Math.cos(rotateAngle * Math.PI / 180), 0, 0);
+        });
+
+
+
+        canvas.addEventListener('click', handleClick);
+
+        init();
+        draw();
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1d4944290382/gemma3-12b.png b/data/artifacts/1d4944290382/gemma3-12b.png
new file mode 100644
index 0000000..538b0fb
Binary files /dev/null and b/data/artifacts/1d4944290382/gemma3-12b.png differ
diff --git a/data/artifacts/1d4944290382/hermes3-8b.html b/data/artifacts/1d4944290382/hermes3-8b.html
new file mode 100644
index 0000000..0889641
--- /dev/null
+++ b/data/artifacts/1d4944290382/hermes3-8b.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Isometric City Builder</title>
+    <style>
+        body {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            height: 100vh;
+            margin: 0;
+            background-color: #f0f0f0;
+        }
+
+        canvas {
+            border: 1px solid #ccc;
+        }
+        
+        .controls {
+            margin-top: 10px;
+        }
+        
+        button {
+            margin-right: 5px;
+        }
+    </style>
+</head>
+<body>
+    <canvas id="cityCanvas" width="400" height="400"></canvas>
+    <div class="controls">
+        <button id="rotateView">Rotate View</button>
+        <button id="addHouse">Add House</button>
+        <button id="addTree">Add Tree</button>
+        <button id="addShop">Add Shop</button>
+        <button id="addRoad">Add Road</button>
+    </div>
+
+    <script>
+        const canvas = document.getElementById('cityCanvas');
+        const ctx = canvas.getContext('2d');
+
+        let buildings = {
+            house: 0,
+            tree: 0,
+            shop: 0,
+            road: 0
+        };
+
+        function drawBuilding(x, y, type) {
+            switch (type) {
+                case 'house':
+                    ctx.fillStyle = '#FFD700';
+                    break;
+                case 'tree':
+                    ctx.fillStyle = '#228B22';
+                    break;
+                case 'shop':
+                    ctx.fillStyle = '#1E90FF';
+                    break;
+                case 'road':
+                    ctx.fillStyle = '#A52A2A';
+                    break;
+            }
+            
+            ctx.beginPath();
+            ctx.fillRect(x, y, 50, 50);
+            ctx.closePath();
+        }
+
+        function toggleBuildingType(type) {
+            return (buildings[type] % 2 === 0) ? 'house' : type;
+        }
+
+        canvas.addEventListener('click', (event) => {
+            const x = Math.floor(event.clientX / 50) * 50 - 100;
+            const y = Math.floor(event.clientY / 50) * 50 - 200;
+
+            ctx.clearRect(x, y, 50, 50);
+
+            Object.keys(buildings).forEach((type) => {
+                drawBuilding(x, y, toggleBuildingType(type));
+            });
+
+            buildings[toggleBuildingType(Object.keys(buildings)[0]]]++;
+        });
+
+        document.getElementById('rotateView').addEventListener('click', () => {
+            ctx.translate(canvas.width, 0);
+            ctx.scale(-1, 1);
+        });
+
+        Object.keys(buildings).forEach((type) => {
+            const button = document.getElementById(type);
+            
+            if (button) {
+                button.addEventListener('click', () => {
+                    buildings[type]++;
+                });
+            }
+        });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1d4944290382/hermes3-8b.png b/data/artifacts/1d4944290382/hermes3-8b.png
new file mode 100644
index 0000000..71e036b
Binary files /dev/null and b/data/artifacts/1d4944290382/hermes3-8b.png differ
diff --git a/data/artifacts/1d4944290382/hf-qwen-coder-32b.html b/data/artifacts/1d4944290382/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..f892b33
--- /dev/null
+++ b/data/artifacts/1d4944290382/hf-qwen-coder-32b.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Isometric City Builder</title>
+<style>
+  body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background: #e0e0e0; }
+  canvas { border: 1px solid #000; }
+  button { position: absolute; top: 10px; left: 10px; z-index: 10; }
+  .counts { position: absolute; bottom: 10px; right: 10px; background: rgba(255,255,255,0.8); padding: 10px; border-radius: 5px; }
+</style>
+</head>
+<body>
+<canvas id="city"></canvas>
+<button onclick="rotateView()">Rotate View</button>
+<div class="counts" id="counts"></div>
+<script>
+const canvas = document.getElementById('city');
+const ctx = canvas.getContext('2d');
+const gridSize = 5;
+const cellSize = 40;
+let angle = 0;
+const types = ['house', 'tree', 'road', 'shop'];
+let grid = Array.from({length: gridSize}, () => Array(gridSize).fill(0));
+
+function drawIsometric(x, y, type) {
+  const dx = (x - y) * cellSize / 2;
+  const dy = (x + y) * cellSize / 4;
+  ctx.save();
+  ctx.translate(canvas.width/2 + dx, canvas.height/3 + dy);
+  ctx.rotate(angle);
+  switch(type) {
+    case 'house': 
+      ctx.fillStyle = '#8B4513';
+      ctx.fillRect(-cellSize/2, -cellSize/2, cellSize, cellSize);
+      ctx.beginPath();
+      ctx.moveTo(-cellSize/2, -cellSize/2);
+      ctx.lineTo(0, -cellSize*0.75);
+      ctx.lineTo(cellSize/2, -cellSize/2);
+      ctx.fillStyle = '#A0522D';
+      ctx.fill();
+      break;
+    case 'tree': 
+      ctx.fillStyle = '#228B22';
+      ctx.fillRect(-cellSize/4, cellSize/6, cellSize/2, cellSize*0.75);
+      ctx.beginPath();
+      ctx.arc(0, -cellSize/3, cellSize/2, 0, Math.PI * 2);
+      ctx.fillStyle = '#3CB371';
+      ctx.fill();
+      break;
+    case 'road': 
+      ctx.fillStyle = '#808080';
+      ctx.fillRect(-cellSize/2, -cellSize/4, cellSize, cellSize/2);
+      break;
+    case 'shop': 
+      ctx.fillStyle = '#FFD700';
+      ctx.fillRect(-cellSize/2, -cellSize/2, cellSize, cellSize);
+      ctx.beginPath();
+      ctx.moveTo(-cellSize/2, -cellSize/2);
+      ctx.lineTo(0, -cellSize*1.5);
+      ctx.lineTo(cellSize/2, -cellSize/2);
+      ctx.fillStyle = '#FFA500';
+      ctx.fill();
+      break;
+  }
+  ctx.restore();
+}
+
+function drawGrid() {
+  ctx.clearRect(0, 0, canvas.width, canvas.height);
+  for (let y = 0; y < gridSize; y++) {
+    for (let x = 0; x < gridSize; x++) {
+      drawIsometric(x, y, types[grid[y][x]]);
+    }
+  }
+  updateCounts();
+}
+
+function updateCounts() {
+  const counts = types.map(() => 0);
+  grid.forEach(row => row.forEach(cell => counts[cell]++));
+  document.getElementById('counts').innerHTML = types.map((type, i) => `${type}: ${counts[i]}`).join('<br>');
+}
+
+canvas.width = cellSize * gridSize;
+canvas.height = cellSize * gridSize;
+
+canvas.addEventListener('click', (e) => {
+  const rect = canvas.getBoundingClientRect();
+  const x = e.clientX - rect.left;
+  const y = e.clientY - rect.top;
+  for (let cy = 0; cy < gridSize; cy++) {
+    for (let cx = 0; cx < gridSize; cx++) {
+      const dx = (cx - cy) * cellSize / 2;
+      const dy = (cx + cy) * cellSize / 4;
+      if (Math.abs(x - (canvas.width/2 + dx)) < cellSize/2 && Math.abs(y - (canvas.height/3 + dy)) < cellSize/2) {
+        grid[cy][cx] = (grid[cy][cx] + 1) % types.length;
+        drawGrid();
+        return;
+      }
+    }
+  }
+});
+
+function rotateView() {
+  angle += Math.PI / 4;
+  drawGrid();
+}
+
+drawGrid();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1d4944290382/hf-qwen-coder-32b.png b/data/artifacts/1d4944290382/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..36245f0
Binary files /dev/null and b/data/artifacts/1d4944290382/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/1d4944290382/qwen25-7b.html b/data/artifacts/1d4944290382/qwen25-7b.html
new file mode 100644
index 0000000..c973976
--- /dev/null
+++ b/data/artifacts/1d4944290382/qwen25-7b.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Isometric City Builder</title>
+    <style>
+        body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
+        canvas { border: 1px solid #000; }
+    </style>
+</head>
+<body>
+    <canvas id="city" width="400" height="300"></canvas>
+    <div style="margin-top: 20px;">
+        <button onclick="rotateView()">Rotate View</button>
+        House: <span id="houseCount">0</span>, Tree: <span id="treeCount">0</span>, Road: <span id="roadCount">0</span>, Shop: <span id="shopCount">0</span>
+    </div>
+    <script>
+        const canvas = document.getElementById('city');
+        const ctx = canvas.getContext('2d');
+        const tileWidth = 50;
+        const tileHeight = 28.864; // 90°/360 * tileWidth
+        let viewAngle = Math.PI / 4;
+        let buildingCount = { house: 0, tree: 0, road: 0, shop: 0 };
+        
+        function rotateView() {
+            viewAngle += Math.PI / 8;
+            if (viewAngle > Math.PI * 2) viewAngle -= Math.PI * 2;
+        }
+
+        function drawTile(x, y, type) {
+            ctx.save();
+            ctx.translate(canvas.width / 2 + x * tileWidth * Math.cos(viewAngle) - y * tileHeight * Math.sin(viewAngle), canvas.height / 2 - x * tileWidth * Math.sin(viewAngle) - y * tileHeight * Math.cos(viewAngle));
+            ctx.rotate(-viewAngle);
+            ctx.beginPath();
+            switch (type) {
+                case 'house':
+                    ctx.fillStyle = '#6f42c1';
+                    break;
+                case 'tree':
+                    ctx.fillStyle = '#2e8b57';
+                    break;
+                case 'road':
+                    ctx.fillStyle = '#9a9a9a';
+                    break;
+                case 'shop':
+                    ctx.fillStyle = '#ff6347';
+                    break;
+            }
+            ctx.rect(-tileWidth / 2, -tileHeight / 2, tileWidth, tileHeight);
+            ctx.fill();
+            ctx.restore();
+        }
+
+        function draw() {
+            ctx.clearRect(0, 0, canvas.width, canvas.height);
+            for (let x = 0; x < 10; x++) {
+                for (let y = 0; y < 10; y++) {
+                    let type = Math.floor(Math.random() * 4);
+                    buildingCount[Object.keys(buildingCount)[type]]++;
+                    drawTile(x, y, Object.keys(buildingCount)[type]);
+                }
+            }
+        }
+
+        function updateCounts() {
+            document.getElementById('houseCount').textContent = buildingCount.house;
+            document.getElementById('treeCount').textContent = buildingCount.tree;
+            document.getElementById('roadCount').textContent = buildingCount.road;
+            document.getElementById('shopCount').textContent = buildingCount.shop;
+        }
+
+        setInterval(draw, 100);
+        updateCounts();
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1d4944290382/qwen25-7b.png b/data/artifacts/1d4944290382/qwen25-7b.png
new file mode 100644
index 0000000..1e2eaa0
Binary files /dev/null and b/data/artifacts/1d4944290382/qwen25-7b.png differ
diff --git a/data/artifacts/1d4944290382/qwen3-14b.html b/data/artifacts/1d4944290382/qwen3-14b.html
new file mode 100644
index 0000000..25b8c1e
--- /dev/null
+++ b/data/artifacts/1d4944290382/qwen3-14b.html
@@ -0,0 +1,177 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Tiny Isometric City Builder</title>
+  <style>
+    html, body {
+      margin: 0;
+      padding: 0;
+      overflow: hidden;
+      background: #222;
+      color: #fff;
+      font-family: sans-serif;
+    }
+    canvas {
+      display: block;
+    }
+    #toolbar {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      background: rgba(0,0,0,0.5);
+      padding: 10px;
+      border-radius: 5px;
+    }
+    #toolbar button {
+      margin: 5px;
+      padding: 5px 10px;
+      cursor: pointer;
+      background: #444;
+      border: none;
+      color: #fff;
+      border-radius: 3px;
+    }
+    #toolbar button:hover {
+      background: #666;
+    }
+    #counts {
+      position: absolute;
+      bottom: 10px;
+      left: 10px;
+      font-size: 14px;
+    }
+  </style>
+</head>
+<body>
+  <div id="toolbar">
+    <button id="rotateView">Rotate View</button>
+    <button id="house">House</button>
+    <button id="tree">Tree</button>
+    <button id="road">Road</button>
+    <button id="shop">Shop</button>
+  </div>
+  <div id="counts">
+    Houses: <span id="countHouse">0</span> |
+    Trees: <span id="countTree">0</span> |
+    Roads: <span id="countRoad">0</span> |
+    Shops: <span id="countShop">0</span>
+  </div>
+  <canvas id="gameCanvas"></canvas>
+  <script>
+    const canvas = document.getElementById('gameCanvas');
+    const ctx = canvas.getContext('2d');
+    const toolbar = document.getElementById('toolbar');
+    const countHouse = document.getElementById('countHouse');
+    const countTree = document.getElementById('countTree');
+    const countRoad = document.getElementById('countRoad');
+    const countShop = document.getElementById('countShop');
+
+    const tileSize = 40;
+    const mapSize = 16;
+    const isometricTileSize = 40;
+    const isometricMapWidth = mapSize * isometricTileSize;
+    const isometricMapHeight = mapSize * isometricTileSize;
+    let currentBuilding = 'house';
+    let rotation = 0;
+
+    canvas.width = isometricMapWidth;
+    canvas.height = isometricMapHeight;
+
+    const map = Array.from({ length: mapSize }, () => Array(mapSize).fill(null));
+    const buildingColors = {
+      house: '#6b46c1',
+      tree: '#228b22',
+      road: '#808080',
+      shop: '#ff6347'
+    };
+
+    function drawIsometricTile(x, y, color) {
+      const px = (x - y) * isometricTileSize / 2;
+      const py = (x + y) * isometricTileSize / 4;
+      ctx.fillStyle = color;
+      ctx.beginPath();
+      ctx.moveTo(px, py);
+      ctx.lineTo(px + isometricTileSize / 2, py + isometricTileSize / 2);
+      ctx.lineTo(px + isometricTileSize / 2, py + isometricTileSize);
+      ctx.lineTo(px - isometricTileSize / 2, py + isometricTileSize);
+      ctx.lineTo(px - isometricTileSize / 2, py + isometricTileSize / 2);
+      ctx.closePath();
+      ctx.fill();
+    }
+
+    function drawMap() {
+      ctx.clearRect(0, 0, canvas.width, canvas.height);
+      for (let y = 0; y < mapSize; y++) {
+        for (let x = 0; x < mapSize; x++) {
+          const tile = map[y][x];
+          if (tile) {
+            drawIsometricTile(x, y, buildingColors[tile]);
+          }
+        }
+      }
+    }
+
+    function getTileFromMouse(mx, my) {
+      for (let y = 0; y < mapSize; y++) {
+        for (let x = 0; x < mapSize; x++) {
+          const px = (x - y) * isometricTileSize / 2;
+          const py = (x + y) * isometricTileSize / 4;
+          const tileX = px + isometricTileSize / 2;
+          const tileY = py + isometricTileSize / 2;
+          if (mx >= tileX - isometricTileSize / 2 && mx <= tileX + isometricTileSize / 2 &&
+              my >= tileY - isometricTileSize / 2 && my <= tileY + isometricTileSize / 2) {
+            return { x, y };
+          }
+        }
+      }
+      return null;
+    }
+
+    function rotateMap() {
+      rotation = (rotation + 1) % 4;
+      canvas.width = isometricMapWidth;
+      canvas.height = isometricMapHeight;
+      drawMap();
+    }
+
+    function updateCounts() {
+      let houses = 0, trees = 0, roads = 0, shops = 0;
+      for (let y = 0; y < mapSize; y++) {
+        for (let x = 0; x < mapSize; x++) {
+          const tile = map[y][x];
+          if (tile === 'house') houses++;
+          else if (tile === 'tree') trees++;
+          else if (tile === 'road') roads++;
+          else if (tile === 'shop') shops++;
+        }
+      }
+      countHouse.textContent = houses;
+      countTree.textContent = trees;
+      countRoad.textContent = roads;
+      countShop.textContent = shops;
+    }
+
+    canvas.addEventListener('click', function (e) {
+      const rect = canvas.getBoundingClientRect();
+      const mx = e.clientX - rect.left;
+      const my = e.clientY - rect.top;
+      const tile = getTileFromMouse(mx, my);
+      if (tile && tile.x >= 0 && tile.x < mapSize && tile.y >= 0 && tile.y < mapSize) {
+        map[tile.y][tile.x] = currentBuilding;
+        drawMap();
+        updateCounts();
+      }
+    });
+
+    document.getElementById('house').addEventListener('click', () => currentBuilding = 'house');
+    document.getElementById('tree').addEventListener('click', () => currentBuilding = 'tree');
+    document.getElementById('road').addEventListener('click', () => currentBuilding = 'road');
+    document.getElementById('shop').addEventListener('click', () => currentBuilding = 'shop');
+    document.getElementById('rotateView').addEventListener('click', rotateMap);
+
+    drawMap();
+    updateCounts();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1d4944290382/qwen3-14b.png b/data/artifacts/1d4944290382/qwen3-14b.png
new file mode 100644
index 0000000..a885c4a
Binary files /dev/null and b/data/artifacts/1d4944290382/qwen3-14b.png differ
diff --git a/data/artifacts/68b40458a368/claude-code.html b/data/artifacts/68b40458a368/claude-code.html
new file mode 100644
index 0000000..265a9c3
--- /dev/null
+++ b/data/artifacts/68b40458a368/claude-code.html
@@ -0,0 +1,350 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Verlet Rope · Physics Toy</title>
+<style>
+  :root{
+    --bg0:#0b0f1a; --bg1:#151b2e; --ink:#e8ecf7; --muted:#8a93ad;
+    --accent:#5ad1ff; --accent2:#ff5a8a; --line:rgba(255,255,255,.08);
+  }
+  *{box-sizing:border-box}
+  html,body{margin:0;height:100%;overflow:hidden;background:var(--bg0);color:var(--ink);
+    font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
+    -webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}
+  #stage{position:fixed;inset:0}
+  canvas{display:block;width:100%;height:100%;touch-action:none;cursor:grab}
+  canvas.cut{cursor:crosshair}
+  canvas:active{cursor:grabbing}
+
+  .hud{position:fixed;top:16px;left:16px;right:16px;display:flex;gap:14px;
+    align-items:flex-start;justify-content:space-between;pointer-events:none;flex-wrap:wrap}
+  .panel{pointer-events:auto;background:linear-gradient(180deg,rgba(21,27,46,.86),rgba(11,15,26,.86));
+    backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
+    border:1px solid var(--line);border-radius:14px;padding:12px 14px;
+    box-shadow:0 10px 40px rgba(0,0,0,.45)}
+  .title{font-weight:700;letter-spacing:.3px;font-size:15px;display:flex;align-items:center;gap:9px}
+  .dot{width:9px;height:9px;border-radius:50%;background:var(--accent);
+    box-shadow:0 0 12px var(--accent)}
+  .hint{color:var(--muted);font-size:12px;margin-top:5px;line-height:1.5;max-width:280px}
+  .controls{display:flex;gap:9px;flex-wrap:wrap;align-items:center}
+  button{font:inherit;font-size:13px;font-weight:600;color:var(--ink);cursor:pointer;
+    border:1px solid var(--line);background:rgba(255,255,255,.05);
+    padding:9px 14px;border-radius:10px;transition:.15s;display:inline-flex;align-items:center;gap:7px}
+  button:hover{background:rgba(255,255,255,.11);transform:translateY(-1px)}
+  button:active{transform:translateY(0)}
+  button.on{background:var(--accent2);border-color:transparent;color:#fff;
+    box-shadow:0 4px 18px rgba(255,90,138,.5)}
+  button.on .ic{filter:none}
+  .ic{font-size:15px;line-height:1}
+  .seg{display:flex;gap:6px;align-items:center;color:var(--muted);font-size:12px}
+  input[type=range]{-webkit-appearance:none;appearance:none;width:96px;height:4px;border-radius:4px;
+    background:rgba(255,255,255,.18);outline:none}
+  input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:15px;height:15px;border-radius:50%;
+    background:var(--accent);cursor:pointer;box-shadow:0 0 8px var(--accent)}
+  input[type=range]::-moz-range-thumb{width:15px;height:15px;border:none;border-radius:50%;
+    background:var(--accent);cursor:pointer}
+  .stats{position:fixed;bottom:14px;left:16px;color:var(--muted);font-size:11px;
+    font-variant-numeric:tabular-nums;letter-spacing:.4px;pointer-events:none}
+  kbd{background:rgba(255,255,255,.09);border:1px solid var(--line);border-bottom-width:2px;
+    border-radius:5px;padding:1px 6px;font-family:inherit;font-size:11px}
+</style>
+</head>
+<body>
+<div id="stage">
+  <canvas id="c"></canvas>
+</div>
+
+<div class="hud">
+  <div class="panel">
+    <div class="title"><span class="dot"></span>Verlet Rope Toy</div>
+    <div class="hint">Drag any point to swing the rope. Toggle <b>Cut</b> and slice across
+      strands to sever them. Everything is verlet-integrated cloth physics — no libraries.</div>
+  </div>
+  <div class="panel controls">
+    <button id="cutBtn" title="Toggle cut tool"><span class="ic">✂️</span><span>Cut</span></button>
+    <button id="resetBtn" title="Rebuild the rope"><span class="ic">↺</span><span>Reset</span></button>
+    <div class="seg"><span>Gravity</span><input id="grav" type="range" min="0" max="200" value="70"></div>
+    <div class="seg"><span>Wind</span><input id="wind" type="range" min="0" max="100" value="0"></div>
+  </div>
+</div>
+
+<div class="stats" id="stats"></div>
+
+<script>
+(() => {
+  "use strict";
+  const canvas = document.getElementById('c');
+  const ctx = canvas.getContext('2d');
+  const statsEl = document.getElementById('stats');
+  const cutBtn = document.getElementById('cutBtn');
+  const resetBtn = document.getElementById('resetBtn');
+  const gravSlider = document.getElementById('grav');
+  const windSlider = document.getElementById('wind');
+
+  let W = 0, H = 0, DPR = 1;
+  function resize(){
+    DPR = Math.min(window.devicePixelRatio || 1, 2);
+    W = window.innerWidth; H = window.innerHeight;
+    canvas.width = W * DPR; canvas.height = H * DPR;
+    ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
+  }
+  window.addEventListener('resize', () => { resize(); rebuild(); });
+
+  // ---------- Physics model ----------
+  // Points: {x,y,px,py,pinned}   Sticks: {a,b,len,live}
+  let points = [], sticks = [];
+  const FRICTION = 0.995;          // velocity damping
+  const BOUNCE = 0.9;
+  let gravity = 70, wind = 0;
+
+  function makePoint(x, y, pinned=false){
+    return { x, y, px:x, py:y, pinned, ox:x, oy:y };
+  }
+  function makeStick(a, b){
+    const p = points[a], q = points[b];
+    const len = Math.hypot(p.x - q.x, p.y - q.y);
+    return { a, b, len, live:true };
+  }
+
+  // Build a hanging rope grid: several ropes pinned at the top,
+  // cross-linked into a light cloth-like lattice.
+  function rebuild(){
+    points = []; sticks = [];
+    const cols = Math.max(5, Math.min(11, Math.round(W / 90)));
+    const rows = Math.max(9, Math.min(20, Math.round(H / 55)));
+    const spanW = Math.min(W * 0.72, cols * 70);
+    const spacing = spanW / (cols - 1);
+    const startX = (W - spanW) / 2;
+    const startY = H * 0.14;
+
+    const idx = (r, c) => r * cols + c;
+    for (let r = 0; r < rows; r++){
+      for (let c = 0; c < cols; c++){
+        const pinned = (r === 0) && (c % 2 === 0);
+        points.push(makePoint(startX + c * spacing, startY + r * spacing * 0.8, pinned));
+      }
+    }
+    for (let r = 0; r < rows; r++){
+      for (let c = 0; c < cols; c++){
+        if (c < cols - 1) sticks.push(makeStick(idx(r,c), idx(r,c+1)));   // horizontal
+        if (r < rows - 1) sticks.push(makeStick(idx(r,c), idx(r+1,c)));   // vertical
+      }
+    }
+  }
+
+  // ---------- Interaction ----------
+  let cutMode = false;
+  let dragPoint = null;
+  const mouse = { x:0, y:0, px:0, py:0, down:false };
+
+  function pointerPos(e){
+    const t = e.touches ? e.touches[0] : e;
+    return { x: t.clientX, y: t.clientY };
+  }
+  function nearestPoint(x, y, maxD){
+    let best = null, bd = maxD * maxD;
+    for (const p of points){
+      const dx = p.x - x, dy = p.y - y, d = dx*dx + dy*dy;
+      if (d < bd){ bd = d; best = p; }
+    }
+    return best;
+  }
+
+  function down(e){
+    e.preventDefault();
+    const {x,y} = pointerPos(e);
+    mouse.x = mouse.px = x; mouse.y = mouse.py = y; mouse.down = true;
+    if (!cutMode){
+      dragPoint = nearestPoint(x, y, 40);
+    }
+  }
+  function move(e){
+    const {x,y} = pointerPos(e);
+    mouse.px = mouse.x; mouse.py = mouse.y;
+    mouse.x = x; mouse.y = y;
+    if (cutMode && mouse.down) sliceAt(mouse.px, mouse.py, x, y);
+  }
+  function up(){ mouse.down = false; dragPoint = null; }
+
+  // Segment intersection for the cutting blade
+  function segHit(x1,y1,x2,y2, x3,y3,x4,y4){
+    const d = (x2-x1)*(y4-y3) - (y2-y1)*(x4-x3);
+    if (d === 0) return false;
+    const t = ((x3-x1)*(y4-y3) - (y3-y1)*(x4-x3)) / d;
+    const u = ((x3-x1)*(y2-y1) - (y3-y1)*(x2-x1)) / d;
+    return t >= 0 && t <= 1 && u >= 0 && u <= 1;
+  }
+  let cutFlashes = [];
+  function sliceAt(x1,y1,x2,y2){
+    for (const s of sticks){
+      if (!s.live) continue;
+      const p = points[s.a], q = points[s.b];
+      if (segHit(x1,y1,x2,y2, p.x,p.y, q.x,q.y)){
+        s.live = false;
+        cutFlashes.push({ x:(p.x+q.x)/2, y:(p.y+q.y)/2, life:1 });
+      }
+    }
+  }
+
+  canvas.addEventListener('mousedown', down);
+  window.addEventListener('mousemove', move);
+  window.addEventListener('mouseup', up);
+  canvas.addEventListener('touchstart', down, {passive:false});
+  canvas.addEventListener('touchmove', e => { e.preventDefault(); move(e); }, {passive:false});
+  window.addEventListener('touchend', up);
+
+  cutBtn.addEventListener('click', () => {
+    cutMode = !cutMode;
+    cutBtn.classList.toggle('on', cutMode);
+    canvas.classList.toggle('cut', cutMode);
+  });
+  resetBtn.addEventListener('click', rebuild);
+  gravSlider.addEventListener('input', () => gravity = +gravSlider.value);
+  windSlider.addEventListener('input', () => wind = +windSlider.value);
+
+  window.addEventListener('keydown', e => {
+    if (e.key === 'c' || e.key === 'C') cutBtn.click();
+    if (e.key === 'r' || e.key === 'R') rebuild();
+  });
+
+  // ---------- Simulation ----------
+  let t = 0;
+  function integrate(dt){
+    const g = gravity * dt;
+    const windForce = wind * 0.02 * Math.sin(t * 1.7) * dt;
+    for (const p of points){
+      if (p.pinned) continue;
+      let vx = (p.x - p.px) * FRICTION;
+      let vy = (p.y - p.py) * FRICTION;
+      p.px = p.x; p.py = p.y;
+      p.x += vx + windForce;
+      p.y += vy + g;
+    }
+  }
+  function constrain(){
+    // Drag: pull the grabbed point toward the mouse
+    if (dragPoint && mouse.down){
+      dragPoint.x = mouse.x; dragPoint.y = mouse.y;
+      dragPoint.px = mouse.x; dragPoint.py = mouse.y;
+    }
+    // Solve stick constraints several times for stiffness
+    const iterations = 12;
+    for (let k = 0; k < iterations; k++){
+      for (const s of sticks){
+        if (!s.live) continue;
+        const p = points[s.a], q = points[s.b];
+        let dx = q.x - p.x, dy = q.y - p.y;
+        let d = Math.hypot(dx, dy) || 0.0001;
+        const diff = (s.len - d) / d * 0.5;
+        const ox = dx * diff, oy = dy * diff;
+        if (!p.pinned){ p.x -= ox; p.y -= oy; }
+        if (!q.pinned){ q.x += ox; q.y += oy; }
+      }
+      // keep points on screen
+      for (const p of points){
+        if (p.pinned) continue;
+        if (p.x < 0){ p.x = 0; p.px = p.x + (p.x - p.px)*BOUNCE; }
+        else if (p.x > W){ p.x = W; p.px = p.x + (p.x - p.px)*BOUNCE; }
+        if (p.y > H){ p.y = H; p.py = p.y + (p.y - p.py)*BOUNCE; }
+      }
+    }
+  }
+
+  // ---------- Render ----------
+  function render(){
+    ctx.clearRect(0, 0, W, H);
+
+    // subtle vignette background
+    const bg = ctx.createRadialGradient(W/2, H*0.3, 50, W/2, H*0.5, Math.max(W,H)*0.8);
+    bg.addColorStop(0, 'rgba(30,40,66,.55)');
+    bg.addColorStop(1, 'rgba(6,9,17,0)');
+    ctx.fillStyle = bg;
+    ctx.fillRect(0,0,W,H);
+
+    // strands
+    ctx.lineCap = 'round';
+    ctx.lineWidth = 2.4;
+    let live = 0;
+    for (const s of sticks){
+      if (!s.live) continue;
+      live++;
+      const p = points[s.a], q = points[s.b];
+      const d = Math.hypot(q.x-p.x, q.y-p.y);
+      // color from tension (stretched = warmer)
+      const stretch = Math.min(1, Math.max(0, (d - s.len) / (s.len)));
+      const hue = 195 - stretch * 200;      // cyan -> pink when taut
+      const sat = 70 + stretch * 30;
+      ctx.strokeStyle = `hsla(${hue},${sat}%,${62 - stretch*10}%,0.9)`;
+      ctx.beginPath();
+      ctx.moveTo(p.x, p.y);
+      ctx.lineTo(q.x, q.y);
+      ctx.stroke();
+    }
+
+    // nodes
+    for (const p of points){
+      if (p.pinned){
+        ctx.fillStyle = '#ffd166';
+        ctx.beginPath(); ctx.arc(p.x, p.y, 4.5, 0, Math.PI*2); ctx.fill();
+        ctx.strokeStyle = 'rgba(255,209,102,.35)';
+        ctx.lineWidth = 1;
+        ctx.beginPath(); ctx.arc(p.x, p.y, 8, 0, Math.PI*2); ctx.stroke();
+      } else {
+        ctx.fillStyle = 'rgba(200,225,255,.55)';
+        ctx.beginPath(); ctx.arc(p.x, p.y, 2.2, 0, Math.PI*2); ctx.fill();
+      }
+    }
+
+    // highlight the point under the cursor / being dragged
+    if (dragPoint){
+      ctx.strokeStyle = 'var(--accent)';
+      ctx.strokeStyle = '#5ad1ff';
+      ctx.lineWidth = 2;
+      ctx.beginPath(); ctx.arc(dragPoint.x, dragPoint.y, 12, 0, Math.PI*2); ctx.stroke();
+    }
+
+    // cut blade trail
+    if (cutMode && mouse.down){
+      ctx.strokeStyle = 'rgba(255,90,138,.9)';
+      ctx.lineWidth = 2;
+      ctx.beginPath(); ctx.moveTo(mouse.px, mouse.py); ctx.lineTo(mouse.x, mouse.y); ctx.stroke();
+    }
+
+    // cut flashes
+    for (const f of cutFlashes){
+      ctx.fillStyle = `rgba(255,120,160,${f.life*0.8})`;
+      ctx.beginPath(); ctx.arc(f.x, f.y, (1-f.life)*14 + 3, 0, Math.PI*2); ctx.fill();
+      f.life -= 0.06;
+    }
+    cutFlashes = cutFlashes.filter(f => f.life > 0);
+
+    statsEl.textContent = `${points.length} points · ${live} strands · ` +
+      `gravity ${gravity} · wind ${wind}  —  keys: C cut · R reset`;
+  }
+
+  // ---------- Loop ----------
+  let lastT = performance.now();
+  function frame(now){
+    let dt = (now - lastT) / 1000;
+    lastT = now;
+    dt = Math.min(dt, 0.033);       // clamp big gaps
+    t += dt;
+    // fixed-ish substeps for stability
+    const steps = 2;
+    for (let i = 0; i < steps; i++){
+      integrate(dt * 55 / steps);   // scale so units feel right
+      constrain();
+    }
+    render();
+    requestAnimationFrame(frame);
+  }
+
+  resize();
+  rebuild();
+  requestAnimationFrame(frame);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/68b40458a368/claude-code.png b/data/artifacts/68b40458a368/claude-code.png
new file mode 100644
index 0000000..ba69c75
Binary files /dev/null and b/data/artifacts/68b40458a368/claude-code.png differ
diff --git a/data/artifacts/68b40458a368/gemma3-12b.html b/data/artifacts/68b40458a368/gemma3-12b.html
new file mode 100644
index 0000000..c9c485c
--- /dev/null
+++ b/data/artifacts/68b40458a368/gemma3-12b.html
@@ -0,0 +1,195 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Rope Physics Toy</title>
+<style>
+body { margin: 0; overflow: hidden; }
+canvas { background-color: #eee; display: block; }
+#controls { position: absolute; top: 10px; left: 10px; font-family: sans-serif; }
+</style>
+</head>
+<body>
+
+<div id="controls">
+  <button id="resetBtn">Reset</button>
+</div>
+
+<canvas id="myCanvas"></canvas>
+
+<script>
+const canvas = document.getElementById("myCanvas");
+const ctx = canvas.getContext("2d");
+const resetBtn = document.getElementById("resetBtn");
+
+let points = [];
+let segments = [];
+const numPoints = 50;
+const segmentLength = 10;
+let damping = 0.98;
+let mouseDragging = false;
+let draggedPointIndex = -1;
+
+function init() {
+  for (let i = 0; i < numPoints; i++) {
+    points.push({ x: canvas.width / 2, y: i * segmentLength });
+  }
+
+  for (let i = 0; i < numPoints - 1; i++) {
+    segments.push([i, i + 1]);
+  }
+}
+
+function verletIntegrate(point) {
+  if (!point) return;
+
+  const prevX = point.px || point.x; // Use previous x if available
+  const prevY = point.py || point.y; // Use previous y if available
+
+  const tempX = 2 * point.x - point.px + (0.01) * (Math.sin(point.x*5) - Math.sin(point.px*5));
+  const tempY = 2 * point.y - point.py + (0.01) * (Math.cos(point.y*5) - Math.cos(point.py*5));
+
+
+  point.px = point.x;
+  point.py = point.y;
+
+  point.x = tempX;
+  point.y = tempY;
+}
+
+function update() {
+  for (let i = 0; i < points.length; i++) {
+    points[i].px = points[i].x;
+    points[i].py = points[i].y;
+    verletIntegrate(points[i]);
+  }
+
+  for (let i = 0; i < segments.length; i++) {
+    const p1Index = segments[i][0];
+    const p2Index = segments[i][1];
+
+    const x1 = points[p1Index].x;
+    const y1 = points[p1Index].y;
+    const x2 = points[p2Index].x;
+    const y2 = points[p2Index].y;
+
+    const dx = x2 - x1;
+    const dy = y2 - y1;
+    const distSq = dx * dx + dy * dy;
+    const expectedDistSq = segmentLength * segmentLength;
+
+      if (distSq > expectedDistSq) {
+        const ratio = Math.sqrt(distSq / expectedDistSq);
+        const correctionX = dx / ratio;
+        const correctionY = dy / ratio;
+
+        points[p1Index].x -= correctionX * 0.5;
+        points[p1Index].y -= correctionY * 0.5;
+        points[p2Index].x += correctionX * 0.5;
+        points[p2Index].y += correctionY * 0.5;
+      }
+
+
+  }
+
+    for (let i = 0; i < points.length; i++) {
+        points[i].x *= damping;
+        points[i].y *= damping;
+    }
+}
+
+
+
+
+function draw() {
+  ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+  ctx.beginPath();
+  ctx.moveTo(points[0].x, points[0].y);
+
+  for (let i = 1; i < points.length; i++) {
+    ctx.lineTo(points[i].x, points[i].y);
+  }
+
+  ctx.strokeStyle = "black";
+  ctx.lineWidth = 2;
+  ctx.stroke();
+
+
+  // Draw Points
+  ctx.fillStyle = 'red';
+  for (let i = 0; i < points.length; i++) {
+    ctx.beginPath();
+    ctx.arc(points[i].x, points[i].y, 5, 0, Math.PI * 2);
+    ctx.fill();
+  }
+
+}
+
+
+function cutSegment(index) {
+  if (segments.length === 0) return;
+  const segmentToRemove = segments.splice(index, 1)[0];
+  points[segmentToRemove[0]].px = points[segmentToRemove[0]].x;
+  points[segmentToRemove[0]].py = points[segmentToRemove[0]].y;
+  points[segmentToRemove[1]].px = points[segmentToRemove[1]].x;
+  points[segmentToRemove[1]].py = points[segmentToRemove[1]].y;
+
+}
+
+
+canvas.addEventListener("mousedown", (e) => {
+    mouseDragging = true;
+    draggedPointIndex = -1;
+
+    for(let i=0; i < points.length; ++i){
+        if(Math.abs(e.clientX - points[i].x) < 5 && Math.abs(e.clientY-points[i].y)<5){
+            draggedPointIndex = i;
+            break;
+        }
+    }
+
+});
+
+canvas.addEventListener("mouseup", (e) => {
+    mouseDragging = false;
+    draggedPointIndex = -1;
+});
+
+
+canvas.addEventListener("mousemove", (e) => {
+  if (!mouseDragging) return;
+
+  if(draggedPointIndex !==-1){
+      points[draggedPointIndex].x = e.clientX;
+      points[draggedPointIndex].y = e.clientY;
+  }
+
+
+});
+
+
+
+resetBtn.addEventListener("click", () => {
+    init();
+});
+
+
+
+
+let animationFrameId;
+
+function animate() {
+    update();
+    draw();
+    animationFrameId = requestAnimationFrame(animate);
+}
+
+canvas.width = 800;
+canvas.height = 600;
+
+init();
+animate();
+</script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/68b40458a368/gemma3-12b.png b/data/artifacts/68b40458a368/gemma3-12b.png
new file mode 100644
index 0000000..542f95d
Binary files /dev/null and b/data/artifacts/68b40458a368/gemma3-12b.png differ
diff --git a/data/artifacts/68b40458a368/qwen25-7b.html b/data/artifacts/68b40458a368/qwen25-7b.html
new file mode 100644
index 0000000..23e40f0
--- /dev/null
+++ b/data/artifacts/68b40458a368/qwen25-7b.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Rope Physics Toy</title>
+    <style>
+        body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f0f0f0; }
+        canvas { border: 2px solid black; cursor: pointer; user-select: none; }
+    </style>
+</head>
+<body>
+<canvas id="canvas" width="800" height="600"></canvas>
+<script>
+const canvas = document.getElementById('canvas');
+const ctx = canvas.getContext('2d');
+
+let points = [];
+let cutPoint = -1;
+let dragging = false;
+
+function setup() {
+    const ropeLength = 200;
+    for (let i = 0; i < ropeLength / 5 + 1; i++) {
+        points.push({ x: canvas.width / 2, y: canvas.height - 10 * i });
+    }
+}
+
+function draw() {
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+    for (let i = 0; i < points.length; i++) {
+        if (i === cutPoint) continue;
+        const p = points[i];
+        ctx.beginPath();
+        ctx.arc(p.x, p.y, 5, 0, Math.PI * 2);
+        ctx.fill();
+    }
+}
+
+function update() {
+    for (let i = points.length - 1; i > 0; i--) {
+        if (i === cutPoint) continue;
+        const prev = points[i - 1];
+        const curr = points[i];
+        const dx = prev.x - curr.x, dy = prev.y - curr.y;
+        curr.x += dx / 2, curr.y += dy / 2;
+    }
+}
+
+function handleMouseDown(event) {
+    dragging = true;
+    for (let i = 0; i < points.length; i++) {
+        if (Math.hypot(points[i].x - event.offsetX, points[i].y - event.offsetY) < 5) {
+            cutPoint = -1;
+            dragging = false;
+            return;
+        }
+    }
+    for (let i = 0; i < points.length; i++) {
+        if (Math.hypot(points[i].x - event.offsetX, points[i].y - event.offsetY) < 5) {
+            cutPoint = i;
+            dragging = false;
+            return;
+        }
+    }
+}
+
+function handleMouseMove(event) {
+    if (!dragging || cutPoint === -1) return;
+    const p = points[cutPoint];
+    p.x = event.offsetX, p.y = event.offsetY;
+}
+
+function handleMouseUp() {
+    dragging = false;
+    if (cutPoint !== -1) {
+        points.splice(cutPoint + 1, points.length - cutPoint - 1);
+        cutPoint = -1;
+    }
+}
+
+canvas.addEventListener('mousedown', handleMouseDown);
+canvas.addEventListener('mousemove', handleMouseMove);
+canvas.addEventListener('mouseup', handleMouseUp);
+
+function loop() {
+    update();
+    draw();
+    requestAnimationFrame(loop);
+}
+
+setup();
+loop();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/68b40458a368/qwen25-7b.png b/data/artifacts/68b40458a368/qwen25-7b.png
new file mode 100644
index 0000000..265de1c
Binary files /dev/null and b/data/artifacts/68b40458a368/qwen25-7b.png differ
diff --git a/data/artifacts/68b40458a368/qwen3-14b.html b/data/artifacts/68b40458a368/qwen3-14b.html
new file mode 100644
index 0000000..24e03af
--- /dev/null
+++ b/data/artifacts/68b40458a368/qwen3-14b.html
@@ -0,0 +1,210 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Rope Physics Toy</title>
+  <style>
+    html, body {
+      margin: 0;
+      padding: 0;
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+      background: #222;
+      font-family: sans-serif;
+    }
+    canvas {
+      display: block;
+      background: #111;
+    }
+    #controls {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      color: white;
+      font-size: 14px;
+      user-select: none;
+    }
+    button {
+      background: #444;
+      border: none;
+      color: white;
+      padding: 6px 12px;
+      margin-right: 8px;
+      cursor: pointer;
+      border-radius: 4px;
+    }
+    button:hover {
+      background: #666;
+    }
+  </style>
+</head>
+<body>
+  <div id="controls">
+    <button id="reset">Reset</button>
+    <button id="cut">Cut</button>
+  </div>
+  <canvas id="canvas"></canvas>
+  <script>
+    const canvas = document.getElementById('canvas');
+    const ctx = canvas.getContext('2d');
+    let width, height;
+    let points = [];
+    let segments = [];
+    let selectedPoint = null;
+    let isDragging = false;
+    let cutMode = false;
+
+    function resize() {
+      width = canvas.width = window.innerWidth;
+      height = canvas.height = window.innerHeight;
+    }
+    window.addEventListener('resize', resize);
+    resize();
+
+    function Vec2(x, y) {
+      this.x = x;
+      this.y = y;
+    }
+    Vec2.prototype.copy = function () {
+      return new Vec2(this.x, this.y);
+    };
+    Vec2.prototype.add = function (v) {
+      return new Vec2(this.x + v.x, this.y + v.y);
+    };
+    Vec2.prototype.sub = function (v) {
+      return new Vec2(this.x - v.x, this.y - v.y);
+    };
+    Vec2.prototype.scale = function (s) {
+      return new Vec2(this.x * s, this.y * s);
+    };
+    Vec2.prototype.dist = function (v) {
+      return Math.hypot(this.x - v.x, this.y - v.y);
+    };
+    Vec2.prototype.length = function () {
+      return Math.hypot(this.x, this.y);
+    };
+    Vec2.prototype.normalize = function () {
+      const len = this.length();
+      return len === 0 ? this : this.scale(1 / len);
+    };
+    Vec2.prototype.set = function (x, y) {
+      this.x = x;
+      this.y = y;
+    };
+
+    function createRope() {
+      points = [];
+      for (let i = 0; i < 100; i++) {
+        const x = width / 2;
+        const y = height / 2 + (i - 50) * 10;
+        points.push(new Vec2(x, y));
+      }
+      for (let i = 0; i < points.length - 1; i++) {
+        segments.push([points[i], points[i + 1]]);
+      }
+    }
+
+    createRope();
+
+    function update() {
+      const gravity = new Vec2(0, 0.5);
+      const damping = 0.99;
+
+      for (let i = 0; i < points.length; i++) {
+        points[i] = points[i].add(gravity);
+      }
+
+      for (let i = 0; i < segments.length; i++) {
+        const [a, b] = segments[i];
+        const dist = a.dist(b);
+        const desiredDist = 20;
+        const diff = a.sub(b);
+        const direction = diff.normalize();
+        const correction = diff.scale((desiredDist - dist) / 2);
+        a.set(a.x - correction.x, a.y - correction.y);
+        b.set(b.x + correction.x, b.y + correction.y);
+      }
+
+      for (let i = 0; i < points.length; i++) {
+        points[i] = points[i].scale(damping);
+      }
+    }
+
+    function draw() {
+      ctx.clearRect(0, 0, width, height);
+      ctx.beginPath();
+      for (let i = 0; i < segments.length; i++) {
+        const [a, b] = segments[i];
+        ctx.moveTo(a.x, a.y);
+        ctx.lineTo(b.x, b.y);
+      }
+      ctx.strokeStyle = '#88f';
+      ctx.stroke();
+
+      ctx.beginPath();
+      for (let p of points) {
+        ctx.moveTo(p.x, p.y);
+        ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
+      }
+      ctx.fillStyle = '#f88';
+      ctx.fill();
+    }
+
+    function findPointAt(x, y) {
+      for (let i = 0; i < points.length; i++) {
+        const p = points[i];
+        if (Math.hypot(x - p.x, y - p.y) < 10) {
+          return i;
+        }
+      }
+      return -1;
+    }
+
+    canvas.addEventListener('mousedown', (e) => {
+      const x = e.clientX;
+      const y = e.clientY;
+      const idx = findPointAt(x, y);
+      if (idx !== -1) {
+        selectedPoint = idx;
+        isDragging = true;
+      } else if (cutMode) {
+        const idxA = findPointAt(x, y);
+        if (idxA !== -1) {
+          const idxB = (idxA + 1) % points.length;
+          segments.splice(segments.indexOf([points[idxA], points[idxB]]), 1);
+        }
+      }
+    });
+
+    canvas.addEventListener('mousemove', (e) => {
+      const x = e.clientX;
+      const y = e.clientY;
+      if (isDragging && selectedPoint !== null) {
+        points[selectedPoint].set(x, y);
+      }
+    });
+
+    canvas.addEventListener('mouseup', () => {
+      isDragging = false;
+      selectedPoint = null;
+    });
+
+    document.getElementById('reset').addEventListener('click', () => {
+      createRope();
+    });
+
+    document.getElementById('cut').addEventListener('click', () => {
+      cutMode = !cutMode;
+    });
+
+    function loop() {
+      update();
+      draw();
+      requestAnimationFrame(loop);
+    }
+
+    loop();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/68b40458a368/qwen3-14b.png b/data/artifacts/68b40458a368/qwen3-14b.png
new file mode 100644
index 0000000..b370eb9
Binary files /dev/null and b/data/artifacts/68b40458a368/qwen3-14b.png differ
diff --git a/data/artifacts/7bf57916e775/hf-qwen-coder-32b.html b/data/artifacts/7bf57916e775/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..51d9e4d
--- /dev/null
+++ b/data/artifacts/7bf57916e775/hf-qwen-coder-32b.html
@@ -0,0 +1,224 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Neon Breakout</title>
+<style>
+  body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #000; }
+  canvas { border: 2px solid #fff; box-shadow: 0 0 20px rgba(0, 255, 255, 0.8); }
+</style>
+</head>
+<body>
+<canvas id="gameCanvas" width="480" height="320"></canvas>
+<script>
+const canvas = document.getElementById('gameCanvas');
+const ctx = canvas.getContext('2d');
+
+let x = canvas.width / 2;
+let y = canvas.height - 30;
+let dx = 2;
+let dy = -2;
+let ballRadius = 10;
+let paddleHeight = 10;
+let paddleWidth = 75;
+let paddleX = (canvas.width - paddleWidth) / 2;
+let rightPressed = false;
+let leftPressed = false;
+let brickRowCount = 5;
+let brickColumnCount = 3;
+let brickWidth = 75;
+let brickHeight = 20;
+let brickPadding = 10;
+let brickOffsetTop = 30;
+let brickOffsetLeft = 30;
+let score = 0;
+let lives = 3;
+let powerUpActive = false;
+let multiBall = false;
+let balls = [{ x, y, dx, dy }];
+let bricks = [];
+let widerPaddleTimer;
+
+for (let c = 0; c < brickColumnCount; c++) {
+  bricks[c] = [];
+  for (let r = 0; r < brickRowCount; r++) {
+    bricks[c][r] = { x: 0, y: 0, status: 1 };
+  }
+}
+
+document.addEventListener("keydown", keyDownHandler, false);
+document.addEventListener("keyup", keyUpHandler, false);
+document.addEventListener("mousemove", mouseMoveHandler, false);
+
+function keyDownHandler(e) {
+  if (e.key === "Right" || e.key === "ArrowRight") {
+    rightPressed = true;
+  } else if (e.key === "Left" || e.key === "ArrowLeft") {
+    leftPressed = true;
+  }
+}
+
+function keyUpHandler(e) {
+  if (e.key === "Right" || e.key === "ArrowRight") {
+    rightPressed = false;
+  } else if (e.key === "Left" || e.key === "ArrowLeft") {
+    leftPressed = false;
+  }
+}
+
+function mouseMoveHandler(e) {
+  let relativeX = e.clientX - canvas.offsetLeft;
+  if(relativeX > 0 && relativeX < canvas.width) {
+    paddleX = relativeX - paddleWidth / 2;
+  }
+}
+
+function collisionDetection() {
+  balls.forEach((ball, index) => {
+    for(let c=0; c<brickColumnCount; c++) {
+      for(let r=0; r<brickRowCount; r++) {
+        let b = bricks[c][r];
+        if(b.status === 1) {
+          if(ball.x > b.x && ball.x < b.x + brickWidth && ball.y > b.y && ball.y < b.y + brickHeight) {
+            ball.dy = -ball.dy;
+            b.status = 0;
+            score++;
+            let powerUpChance = Math.random();
+            if (powerUpChance < 0.1) activatePowerUp(ball);
+            if(score === brickRowCount*brickColumnCount) {
+              alert("YOU WIN, CONGRATULATIONS!");
+              document.location.reload();
+            }
+          }
+        }
+      }
+    }
+  });
+}
+
+function drawBall() {
+  balls.forEach((ball) => {
+    ctx.beginPath();
+    ctx.arc(ball.x, ball.y, ballRadius, 0, Math.PI*2);
+    ctx.fillStyle = "#0F0";
+    ctx.fill();
+    ctx.closePath();
+  });
+}
+
+function drawPaddle() {
+  ctx.beginPath();
+  ctx.rect(paddleX, canvas.height-paddleHeight, paddleWidth, paddleHeight);
+  ctx.fillStyle = "#0FF";
+  ctx.fill();
+  ctx.closePath();
+}
+
+function drawBricks() {
+  for(let c=0; c<brickColumnCount; c++) {
+    for(let r=0; r<brickRowCount; r++) {
+      if(bricks[c][r].status === 1) {
+        let brickX = (c*(brickWidth+brickPadding))+brickOffsetLeft;
+        let brickY = (r*(brickHeight+brickPadding))+brickOffsetTop;
+        bricks[c][r].x = brickX;
+        bricks[c][r].y = brickY;
+        ctx.beginPath();
+        ctx.rect(brickX, brickY, brickWidth, brickHeight);
+        ctx.fillStyle = "#FF0";
+        ctx.fill();
+        ctx.closePath();
+      }
+    }
+  }
+}
+
+function drawScore() {
+  ctx.font = "16px Arial";
+  ctx.fillStyle = "#0F0";
+  ctx.fillText("Score: "+score, 8, 20);
+}
+
+function drawLives() {
+  ctx.font = "16px Arial";
+  ctx.fillStyle = "#FF0";
+  ctx.fillText("Lives: "+lives, canvas.width-65, 20);
+}
+
+function activatePowerUp(ball) {
+  let powerUpType = Math.random() < 0.5 ? 'widerPaddle' : 'multiBall';
+  if (powerUpType === 'widerPaddle') {
+    paddleWidth *= 1.5;
+    clearTimeout(widerPaddleTimer);
+    widerPaddleTimer = setTimeout(() => {
+      paddleWidth /= 1.5;
+    }, 5000);
+  } else if (powerUpType === 'multiBall' && !multiBall) {
+    multiBall = true;
+    balls.push({ x: canvas.width / 2, y: canvas.height - 30, dx: -dx, dy });
+    setTimeout(() => {
+      balls.pop();
+      multiBall = false;
+    }, 5000);
+  }
+}
+
+function draw() {
+  ctx.clearRect(0, 0, canvas.width, canvas.height);
+  drawBricks();
+  drawBall();
+  drawPaddle();
+  drawScore();
+  drawLives();
+  collisionDetection();
+
+  balls.forEach((ball) => {
+    if(ball.x + dx > canvas.width-ballRadius || ball.x + dx < ballRadius) {
+      ball.dx = -ball.dx;
+    }
+    if(ball.y + dy < ballRadius) {
+      ball.dy = -ball.dy;
+    } else if(ball.y + dy > canvas.height-ballRadius) {
+      let paddleHit = false;
+      for(let c=0; c<brickColumnCount; c++) {
+        for(let r=0; r<brickRowCount; r++) {
+          let b = bricks[c][r];
+          if(b.status === 1) {
+            if(ball.y > b.y && ball.y < b.y + brickHeight && ball.x > b.x && ball.x < b.x + brickWidth) {
+              paddleHit = true;
+              break;
+            }
+          }
+        }
+      }
+      if(paddleHit) {
+        ball.dy = -ball.dy;
+      } else {
+        lives--;
+        if(!lives) {
+          alert("GAME OVER");
+          document.location.reload();
+        } else {
+          balls = [{ x: canvas.width / 2, y: canvas.height - 30, dx, dy }];
+          paddleX = (canvas.width-paddleWidth)/2;
+        }
+      }
+    }
+
+    if(rightPressed && paddleX < canvas.width-paddleWidth) {
+      paddleX += 7;
+    } else if(leftPressed && paddleX > 0) {
+      paddleX -= 7;
+    }
+
+    ball.x += ball.dx;
+    ball.y += ball.dy;
+  });
+
+  requestAnimationFrame(draw);
+}
+
+draw();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/7bf57916e775/hf-qwen-coder-32b.png b/data/artifacts/7bf57916e775/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..bd782fc
Binary files /dev/null and b/data/artifacts/7bf57916e775/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/7bf57916e775/kimi.html b/data/artifacts/7bf57916e775/kimi.html
new file mode 100644
index 0000000..44333b3
--- /dev/null
+++ b/data/artifacts/7bf57916e775/kimi.html
@@ -0,0 +1,499 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Neon Breakout</title>
+    <style>
+        body {
+            margin: 0;
+            padding: 0;
+            background: #050510;
+            overflow: hidden;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            height: 100vh;
+            font-family: 'Courier New', monospace;
+        }
+        canvas {
+            box-shadow: 0 0 20px rgba(0, 255, 255, 0.5), 0 0 40px rgba(255, 0, 255, 0.3);
+            border: 2px solid #0ff;
+            background: #000;
+        }
+        #ui {
+            position: absolute;
+            top: 20px;
+            width: 100%;
+            text-align: center;
+            color: #0ff;
+            font-size: 24px;
+            text-shadow: 0 0 10px #0ff, 0 0 20px #0ff;
+            pointer-events: none;
+            z-index: 10;
+        }
+        #gameOver {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            text-align: center;
+            display: none;
+            background: rgba(0, 0, 0, 0.95);
+            padding: 40px;
+            border: 2px solid #f0f;
+            box-shadow: 0 0 30px #f0f;
+            z-index: 20;
+        }
+        #gameOver h1 {
+            color: #f0f;
+            text-shadow: 0 0 20px #f0f;
+            margin: 0 0 20px 0;
+            font-size: 48px;
+        }
+        button {
+            background: transparent;
+            border: 2px solid #0ff;
+            color: #0ff;
+            padding: 15px 40px;
+            font-size: 20px;
+            cursor: pointer;
+            text-shadow: 0 0 10px #0ff;
+            box-shadow: 0 0 15px #0ff;
+            font-family: inherit;
+            text-transform: uppercase;
+            letter-spacing: 2px;
+            transition: all 0.3s;
+        }
+        button:hover {
+            background: #0ff;
+            color: #000;
+            box-shadow: 0 0 30px #0ff;
+        }
+    </style>
+</head>
+<body>
+    <div id="ui">
+        <span style="position: absolute; left: 30px;">SCORE: <span id="score">0</span></span>
+        <span style="position: absolute; right: 30px;">LIVES: <span id="lives">3</span></span>
+    </div>
+    <canvas id="gameCanvas" width="800" height="600"></canvas>
+    <div id="gameOver">
+        <h1 id="gameOverText">GAME OVER</h1>
+        <button onclick="resetGame()">RESTART</button>
+    </div>
+    <script>
+        const canvas = document.getElementById('gameCanvas');
+        const ctx = canvas.getContext('2d');
+        
+        let score = 0;
+        let lives = 3;
+        let gameRunning = true;
+        let animationId;
+        
+        const colors = ['#ff00ff', '#00ffff', '#ffff00', '#00ff00', '#ff0080'];
+        
+        const paddle = {
+            x: canvas.width / 2 - 60,
+            y: canvas.height - 40,
+            width: 120,
+            height: 15,
+            speed: 8,
+            originalWidth: 120,
+            glow: 0
+        };
+        
+        let balls = [];
+        let bricks = [];
+        let powerUps = [];
+        let particles = [];
+        
+        const brickRowCount = 5;
+        const brickColumnCount = 8;
+        const brickWidth = 80;
+        const brickHeight = 20;
+        const brickPadding = 10;
+        const brickOffsetTop = 80;
+        const brickOffsetLeft = 35;
+        
+        function initBricks() {
+            bricks = [];
+            for(let r = 0; r < brickRowCount; r++) {
+                bricks[r] = [];
+                for(let c = 0; c < brickColumnCount; c++) {
+                    bricks[r][c] = { 
+                        x: 0, 
+                        y: 0, 
+                        status: 1, 
+                        color: colors[r % colors.length],
+                        value: (brickRowCount - r) * 10
+                    };
+                }
+            }
+        }
+        
+        function createBall(x, y, dx, dy) {
+            return {
+                x: x || canvas.width / 2,
+                y: y || canvas.height / 2,
+                dx: dx || (Math.random() > 0.5 ? 4 : -4),
+                dy: dy || -5,
+                radius: 6,
+                speed: 5,
+                trail: []
+            };
+        }
+        
+        let rightPressed = false;
+        let leftPressed = false;
+        
+        document.addEventListener('keydown', (e) => {
+            if(e.key === 'Right' || e.key === 'ArrowRight') rightPressed = true;
+            if(e.key === 'Left' || e.key === 'ArrowLeft') leftPressed = true;
+        });
+        
+        document.addEventListener('keyup', (e) => {
+            if(e.key === 'Right' || e.key === 'ArrowRight') rightPressed = false;
+            if(e.key === 'Left' || e.key === 'ArrowLeft') leftPressed = false;
+        });
+        
+        canvas.addEventListener('mousemove', (e) => {
+            const rect = canvas.getBoundingClientRect();
+            const mouseX = e.clientX - rect.left;
+            paddle.x = mouseX - paddle.width / 2;
+            
+            if(paddle.x < 0) paddle.x = 0;
+            if(paddle.x + paddle.width > canvas.width) paddle.x = canvas.width - paddle.width;
+        });
+        
+        function collisionDetection() {
+            for(let r = 0; r < brickRowCount; r++) {
+                for(let c = 0; c < brickColumnCount; c++) {
+                    const b = bricks[r][c];
+                    if(b.status === 1) {
+                        balls.forEach(ball => {
+                            if(ball.x + ball.radius > b.x && 
+                               ball.x - ball.radius < b.x + brickWidth &&
+                               ball.y + ball.radius > b.y && 
+                               ball.y - ball.radius < b.y + brickHeight) {
+                                ball.dy = -ball.dy;
+                                b.status = 0;
+                                score += b.value;
+                                updateUI();
+                                createParticles(b.x + brickWidth/2, b.y + brickHeight/2, b.color);
+                                
+                                if(Math.random() < 0.2) {
+                                    spawnPowerUp(b.x + brickWidth/2, b.y + brickHeight/2);
+                                }
+                                
+                                checkWin();
+                            }
+                        });
+                    }
+                }
+            }
+        }
+        
+        function createParticles(x, y, color) {
+            for(let i = 0; i < 12; i++) {
+                particles.push({
+                    x: x,
+                    y: y,
+                    dx: (Math.random() - 0.5) * 6,
+                    dy: (Math.random() - 0.5) * 6,
+                    life: 1,
+                    color: color,
+                    size: Math.random() * 4 + 2
+                });
+            }
+        }
+        
+        function updateParticles() {
+            for(let i = particles.length - 1; i >= 0; i--) {
+                const p = particles[i];
+                p.x += p.dx;
+                p.y += p.dy;
+                p.life -= 0.02;
+                p.dy += 0.1;
+                if(p.life <= 0) particles.splice(i, 1);
+            }
+        }
+        
+        function drawParticles() {
+            particles.forEach(p => {
+                ctx.save();
+                ctx.globalAlpha = p.life;
+                ctx.fillStyle = p.color;
+                ctx.shadowBlur = 10;
+                ctx.shadowColor = p.color;
+                ctx.beginPath();
+                ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
+                ctx.fill();
+                ctx.restore();
+            });
+        }
+        
+        function spawnPowerUp(x, y) {
+            const types = ['expand', 'multiball'];
+            const type = types[Math.floor(Math.random() * types.length)];
+            powerUps.push({
+                x: x,
+                y: y,
+                type: type,
+                width: 24,
+                height: 24,
+                speed: 2,
+                rotation: 0
+            });
+        }
+        
+        function updatePowerUps() {
+            for(let i = powerUps.length - 1; i >= 0; i--) {
+                const p = powerUps[i];
+                p.y += p.speed;
+                p.rotation += 0.05;
+                
+                if(p.y + p.height > paddle.y && 
+                   p.y < paddle.y + paddle.height &&
+                   p.x + p.width > paddle.x && 
+                   p.x < paddle.x + paddle.width) {
+                    applyPowerUp(p.type);
+                    createParticles(p.x + 12, p.y + 12, p.type === 'expand' ? '#0f0' : '#f0f');
+                    powerUps.splice(i, 1);
+                } else if(p.y > canvas.height) {
+                    powerUps.splice(i, 1);
+                }
+            }
+        }
+        
+        function applyPowerUp(type) {
+            if(type === 'expand') {
+                paddle.width = paddle.originalWidth * 1.6;
+                paddle.glow = 20;
+                setTimeout(() => {
+                    paddle.width = paddle.originalWidth;
+                    paddle.glow = 0;
+                }, 8000);
+            } else if(type === 'multiball') {
+                const baseBall = balls[0];
+                if(baseBall) {
+                    balls.push(createBall(baseBall.x, baseBall.y, -baseBall.speed * 0.8, -baseBall.speed * 0.8));
+                    balls.push(createBall(baseBall.x, baseBall.y, baseBall.speed * 0.8, -baseBall.speed * 0.8));
+                }
+            }
+        }
+        
+        function drawPowerUps() {
+            powerUps.forEach(p => {
+                ctx.save();
+                ctx.translate(p.x + 12, p.y + 12);
+                ctx.rotate(p.rotation);
+                
+                const color = p.type === 'expand' ? '#00ff00' : '#ff00ff';
+                ctx.fillStyle = color;
+                ctx.shadowBlur = 15;
+                ctx.shadowColor = color;
+                
+                if(p.type === 'expand') {
+                    ctx.fillRect(-10, -4, 20, 8);
+                    ctx.fillRect(-4, -10, 8, 20);
+                } else {
+                    ctx.beginPath();
+                    ctx.arc(0, 0, 8, 0, Math.PI * 2);
+                    ctx.fill();
+                    ctx.beginPath();
+                    ctx.arc(-6, 0, 5, 0, Math.PI * 2);
+                    ctx.fill();
+                    ctx.beginPath();
+                    ctx.arc(6, 0, 5, 0, Math.PI * 2);
+                    ctx.fill();
+                }
+                
+                ctx.restore();
+            });
+        }
+        
+        function drawBall(ball) {
+            ball.trail.forEach((pos, index) => {
+                ctx.beginPath();
+                ctx.arc(pos.x, pos.y, ball.radius * (index / ball.trail.length), 0, Math.PI * 2);
+                ctx.fillStyle = `rgba(0, 255, 255, ${index / ball.trail.length * 0.4})`;
+                ctx.fill();
+            });
+            
+            ctx.beginPath();
+            ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
+            ctx.fillStyle = '#0ff';
+            ctx.shadowBlur = 15;
+            ctx.shadowColor = '#0ff';
+            ctx.fill();
+            ctx.closePath();
+            ctx.shadowBlur = 0;
+            
+            ball.trail.push({x: ball.x, y: ball.y});
+            if(ball.trail.length > 8) ball.trail.shift();
+        }
+        
+        function drawPaddle() {
+            ctx.fillStyle = '#f0f';
+            ctx.shadowBlur = 20 + paddle.glow;
+            ctx.shadowColor = '#f0f';
+            ctx.fillRect(paddle.x, paddle.y, paddle.width, paddle.height);
+            
+            ctx.fillStyle = '#fff';
+            ctx.fillRect(paddle.x + 2, paddle.y + 2, paddle.width - 4, 4);
+            ctx.shadowBlur = 0;
+        }
+        
+        function drawBricks() {
+            for(let r = 0; r < brickRowCount; r++) {
+                for(let c = 0; c < brickColumnCount; c++) {
+                    if(bricks[r][c].status === 1) {
+                        const brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
+                        const brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
+                        bricks[r][c].x = brickX;
+                        bricks[r][c].y = brickY;
+                        
+                        ctx.fillStyle = bricks[r][c].color;
+                        ctx.shadowBlur = 10;
+                        ctx.shadowColor = bricks[r][c].color;
+                        ctx.fillRect(brickX, brickY, brickWidth, brickHeight);
+                        
+                        ctx.fillStyle = 'rgba(255,255,255,0.3)';
+                        ctx.fillRect(brickX, brickY, brickWidth, 3);
+                        ctx.shadowBlur = 0;
+                    }
+                }
+            }
+        }
+        
+        function updateBalls() {
+            for(let i = balls.length - 1; i >= 0; i--) {
+                const ball = balls[i];
+                
+                ball.x += ball.dx;
+                ball.y += ball.dy;
+                
+                if(ball.x + ball.dx > canvas.width - ball.radius || ball.x + ball.dx < ball.radius) {
+                    ball.dx = -ball.dx;
+                }
+                if(ball.y + ball.dy < ball.radius) {
+                    ball.dy = -ball.dy;
+                }
+                
+                if(ball.y + ball.dy > paddle.y - ball.radius &&
+                   ball.y < paddle.y + paddle.height &&
+                   ball.x > paddle.x &&
+                   ball.x < paddle.x + paddle.width) {
+                    
+                    let hitPos = (ball.x - paddle.x) / paddle.width;
+                    ball.dx = 8 * (hitPos - 0.5);
+                    ball.dy = -Math.abs(ball.dy);
+                    
+                    createParticles(ball.x, paddle.y, '#f0f');
+                }
+                
+                if(ball.y > canvas.height + 20) {
+                    balls.splice(i, 1);
+                }
+            }
+            
+            if(balls.length === 0 && gameRunning) {
+                lives--;
+                updateUI();
+                if(lives === 0) {
+                    gameOver();
+                } else {
+                    balls.push(createBall());
+                    paddle.x = canvas.width / 2 - paddle.width / 2;
+                }
+            }
+        }
+        
+        function updatePaddle() {
+            if(rightPressed && paddle.x < canvas.width - paddle.width) {
+                paddle.x += paddle.speed;
+            }
+            if(leftPressed && paddle.x > 0) {
+                paddle.x -= paddle.speed;
+            }
+        }
+        
+        function updateUI() {
+            document.getElementById('score').textContent = score;
+            document.getElementById('lives').textContent = lives;
+        }
+        
+        function checkWin() {
+            let activeBricks = 0;
+            for(let r = 0; r < brickRowCount; r++) {
+                for(let c = 0; c < brickColumnCount; c++) {
+                    if(bricks[r][c].status === 1) activeBricks++;
+                }
+            }
+            if(activeBricks === 0) {
+                gameWin();
+            }
+        }
+        
+        function gameOver() {
+            gameRunning = false;
+            document.getElementById('gameOverText').textContent = 'GAME OVER';
+            document.getElementById('gameOver').style.display = 'block';
+        }
+        
+        function gameWin() {
+            gameRunning = false;
+            document.getElementById('gameOverText').textContent = 'YOU WIN!';
+            document.getElementById('gameOver').style.borderColor = '#0f0';
+            document.getElementById('gameOverText').style.color = '#0f0';
+            document.getElementById('gameOverText').style.textShadow = '0 0 20px #0f0';
+            document.getElementById('gameOver').style.display = 'block';
+        }
+        
+        function resetGame() {
+            score = 0;
+            lives = 3;
+            gameRunning = true;
+            balls = [createBall()];
+            powerUps = [];
+            particles = [];
+            paddle.width = paddle.originalWidth;
+            paddle.x = canvas.width / 2 - paddle.width / 2;
+            initBricks();
+            updateUI();
+            document.getElementById('gameOver').style.display = 'none';
+            document.getElementById('gameOver').style.borderColor = '#f0f';
+            document.getElementById('gameOverText').style.color = '#f0f';
+            document.getElementById('gameOverText').style.textShadow = '0 0 20px #f0f';
+            if(animationId) cancelAnimationFrame(animationId);
+            gameLoop();
+        }
+        
+        function gameLoop() {
+            if(!gameRunning) return;
+            
+            ctx.fillStyle = 'rgba(5, 5, 16, 0.25)';
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+            
+            drawBricks();
+            drawPaddle();
+            balls.forEach(drawBall);
+            drawPowerUps();
+            drawParticles();
+            
+            updatePaddle();
+            updateBalls();
+            collisionDetection();
+            updatePowerUps();
+            updateParticles();
+            
+            animationId = requestAnimationFrame(gameLoop);
+        }
+        
+        initBricks();
+        balls.push(createBall());
+        gameLoop();
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/7bf57916e775/kimi.png b/data/artifacts/7bf57916e775/kimi.png
new file mode 100644
index 0000000..5736bdd
Binary files /dev/null and b/data/artifacts/7bf57916e775/kimi.png differ
diff --git a/data/artifacts/9cbc4e3c60a6/gemma3-12b.html b/data/artifacts/9cbc4e3c60a6/gemma3-12b.html
new file mode 100644
index 0000000..b42b67f
--- /dev/null
+++ b/data/artifacts/9cbc4e3c60a6/gemma3-12b.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Markdown Editor</title>
+    <style>
+        body {
+            font-family: sans-serif;
+            margin: 0;
+            display: flex;
+            height: 100vh;
+        }
+
+        textarea {
+            width: 50%;
+            height: 100vh;
+            border: 1px solid #ccc;
+            padding: 10px;
+            font-family: monospace;
+            resize: none;
+        }
+
+        .preview {
+            width: 50%;
+            height: 100vh;
+            padding: 10px;
+            border: 1px solid #ccc;
+            overflow-y: auto; /* Allow scrolling if content is too long */
+        }
+
+        button {
+            margin-top: 10px;
+            padding: 5px 10px;
+        }
+    </style>
+</head>
+<body>
+    <textarea id="markdown"></textarea>
+    <div class="preview" id="preview"></div>
+    <button onclick="copyHtml()">Copy HTML</button>
+
+    <script>
+        const markdownEditor = document.getElementById('markdown');
+        const previewPane = document.getElementById('preview');
+
+        function renderMarkdown(markdownText) {
+            let html = '';
+            const lines = markdownText.split('\n');
+
+            for (let i = 0; i < lines.length; i++) {
+                const line = lines[i];
+
+                if (line.startsWith('# ')) {
+                    html += '<h1>' + line.substring(2) + '</h1>';
+                } else if (line.startsWith('## ')) {
+                    html += '<h2>' + line.substring(3) + '</h2>';
+                } else if (line.startsWith('### ')) {
+                    html += '<h3>' + line.substring(4) + '</h3>';
+                } else if (line.startsWith('* ')) {
+                    if (html === '') {
+                        html += '<ul>';
+                    }
+                    html += '<li>' + line.substring(2) + '</li>';
+                    if (i === lines.length - 1 || !lines[i+1].startsWith('* ')) {
+                      html += '</ul>';
+                    }
+
+                } else if (line.startsWith('
\ No newline at end of file
diff --git a/data/artifacts/9cbc4e3c60a6/gemma3-12b.png b/data/artifacts/9cbc4e3c60a6/gemma3-12b.png
new file mode 100644
index 0000000..3cc7154
Binary files /dev/null and b/data/artifacts/9cbc4e3c60a6/gemma3-12b.png differ
diff --git a/data/artifacts/9cbc4e3c60a6/gpt.html b/data/artifacts/9cbc4e3c60a6/gpt.html
new file mode 100644
index 0000000..a506948
--- /dev/null
+++ b/data/artifacts/9cbc4e3c60a6/gpt.html
@@ -0,0 +1,400 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>Live Markdown Editor</title>
+<style>
+  * { box-sizing: border-box; }
+  body {
+    margin: 0;
+    font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+    background: #f5f5f7;
+    color: #222;
+    height: 100vh;
+    display: flex;
+    flex-direction: column;
+  }
+  header {
+    padding: 10px 16px;
+    background: #1f2933;
+    color: #f9fafb;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  header h1 {
+    font-size: 16px;
+    margin: 0;
+    font-weight: 600;
+  }
+  header .actions {
+    display: flex;
+    gap: 8px;
+    align-items: center;
+  }
+  button {
+    border: none;
+    background: #2563eb;
+    color: #fff;
+    padding: 6px 10px;
+    border-radius: 4px;
+    font-size: 12px;
+    cursor: pointer;
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+  }
+  button:hover {
+    background: #1d4ed8;
+  }
+  button:active {
+    background: #1e40af;
+  }
+  #copyStatus {
+    font-size: 11px;
+    color: #d1fae5;
+    min-width: 70px;
+  }
+  main {
+    flex: 1;
+    display: flex;
+    overflow: hidden;
+  }
+  .pane {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    min-width: 0;
+  }
+  .pane-header {
+    padding: 6px 10px;
+    background: #e5e7eb;
+    font-size: 12px;
+    font-weight: 600;
+    border-bottom: 1px solid #d1d5db;
+  }
+  textarea {
+    flex: 1;
+    width: 100%;
+    border: none;
+    padding: 10px;
+    resize: none;
+    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+    font-size: 13px;
+    line-height: 1.4;
+    outline: none;
+    background: #f9fafb;
+  }
+  #preview {
+    flex: 1;
+    padding: 10px 14px;
+    overflow: auto;
+    background: #ffffff;
+  }
+  #preview h1, #preview h2, #preview h3, #preview h4, #preview h5, #preview h6 {
+    margin: 0.7em 0 0.4em;
+    font-weight: 600;
+    line-height: 1.25;
+  }
+  #preview h1 { font-size: 1.6em; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.2em; }
+  #preview h2 { font-size: 1.4em; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.15em; }
+  #preview h3 { font-size: 1.25em; }
+  #preview h4 { font-size: 1.1em; }
+  #preview p { margin: 0.4em 0; }
+  #preview strong { font-weight: 600; }
+  #preview em { font-style: italic; }
+  #preview code {
+    background: #f3f4f6;
+    padding: 0.1em 0.3em;
+    border-radius: 3px;
+    font-size: 0.95em;
+  }
+  #preview pre {
+    background: #0b1020;
+    color: #e5e7eb;
+    padding: 8px 10px;
+    border-radius: 4px;
+    overflow: auto;
+    font-size: 12px;
+    line-height: 1.4;
+  }
+  #preview pre code {
+    background: none;
+    padding: 0;
+    border-radius: 0;
+    color: inherit;
+  }
+  #preview ul, #preview ol {
+    margin: 0.3em 0 0.3em 1.3em;
+    padding-left: 0.7em;
+  }
+  #preview li { margin: 0.2em 0; }
+  #preview blockquote {
+    margin: 0.4em 0;
+    padding: 0.3em 0.7em;
+    border-left: 3px solid #d1d5db;
+    color: #4b5563;
+    background: #f9fafb;
+  }
+  #preview a {
+    color: #2563eb;
+    text-decoration: none;
+  }
+  #preview a:hover {
+    text-decoration: underline;
+  }
+  #preview hr {
+    border: none;
+    border-top: 1px solid #e5e7eb;
+    margin: 0.8em 0;
+  }
+  @media (max-width: 800px) {
+    main { flex-direction: column; }
+    .pane { min-height: 40vh; }
+  }
+</style>
+</head>
+<body>
+<header>
+  <h1>Live Markdown Editor</h1>
+  <div class="actions">
+    <button id="copyBtn" type="button">Copy HTML</button>
+    <span id="copyStatus"></span>
+  </div>
+</header>
+<main>
+  <section class="pane">
+    <div class="pane-header">Markdown</div>
+    <textarea id="editor" spellcheck="false"></textarea>
+  </section>
+  <section class="pane">
+    <div class="pane-header">Preview</div>
+    <div id="preview"></div>
+  </section>
+</main>
+<script>
+(function () {
+  const editor = document.getElementById('editor');
+  const preview = document.getElementById('preview');
+  const copyBtn = document.getElementById('copyBtn');
+  const copyStatus = document.getElementById('copyStatus');
+
+  function escapeHtml(str) {
+    return str.replace(/[&<>"']/g, function (ch) {
+      switch (ch) {
+        case '&': return '&amp;';
+        case '<': return '&lt;';
+        case '>': return '&gt;';
+        case '"': return '&quot;';
+        case "'": return '&#39;';
+      }
+    });
+  }
+
+  function parseInline(text) {
+    // Inline code
+    text = text.replace(/`([^`]+)`/g, function(_, code){
+      return '<code>' + escapeHtml(code) + '</code>';
+    });
+    // Links [text](url)
+    text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, function(_, label, url){
+      return '<a href="' + escapeHtml(url.trim()) + '" target="_blank" rel="noopener noreferrer">' +
+             escapeHtml(label) + '</a>';
+    });
+    // Bold: **text** or __text__
+    text = text.replace(/(\*\*|__)(.+?)\1/g, '<strong>$2</strong>');
+    // Italic: *text* or _text_
+    text = text.replace(/(\*|_)([^*_]+?)\1/g, '<em>$2</em>');
+    return text;
+  }
+
+  function parseMarkdown(src) {
+    const lines = src.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
+    let html = '';
+    let inCodeBlock = false;
+    let codeBuffer = [];
+    let listMode = null; // "ul" or "ol"
+    let blockquoteOpen = false;
+
+    function closeList() {
+      if (listMode) {
+        html += '</' + listMode + '>\n';
+        listMode = null;
+      }
+    }
+    function openList(mode) {
+      if (listMode !== mode) {
+        closeList();
+        listMode = mode;
+        html += '<' + mode + '>\n';
+      }
+    }
+    function closeBlockquote() {
+      if (blockquoteOpen) {
+        html += '</blockquote>\n';
+        blockquoteOpen = false;
+      }
+    }
+
+    for (let i = 0; i < lines.length; i++) {
+      let line = lines[i];
+
+      // Code block fences
+      const fenceMatch = line.match(/^```(.*)$/);
+      if (fenceMatch) {
+        if (!inCodeBlock) {
+          inCodeBlock = true;
+          codeBuffer = [];
+        } else {
+          inCodeBlock = false;
+          html += '<pre><code>' + escapeHtml(codeBuffer.join('\n')) + '</code></pre>\n';
+          codeBuffer = [];
+        }
+        continue;
+      }
+
+      if (inCodeBlock) {
+        codeBuffer.push(line);
+        continue;
+      }
+
+      // Horizontal rule
+      if (/^\s*(\*\s*\*\s*\*|-{3,}|_{3,})\s*$/.test(line)) {
+        closeList();
+        closeBlockquote();
+        html += '<hr>\n';
+        continue;
+      }
+
+      // Blockquote
+      let bqLevel = 0;
+      while (/^>\s?/.test(line)) {
+        bqLevel++;
+        line = line.replace(/^>\s?/, '');
+      }
+      if (bqLevel > 0) {
+        if (!blockquoteOpen) {
+          closeList();
+          html += '<blockquote>\n';
+          blockquoteOpen = true;
+        }
+      } else {
+        closeBlockquote();
+      }
+
+      // Heading
+      const headingMatch = line.match(/^(#{1,6})\s+(.*)$/);
+      if (headingMatch) {
+        closeList();
+        const level = headingMatch[1].length;
+        const content = parseInline(escapeHtml(headingMatch[2]).trim());
+        html += '<h' + level + '>' + content + '</h' + level + '>\n';
+        continue;
+      }
+
+      // Lists
+      const ulMatch = line.match(/^\s*([-+*])\s+(.*)$/);
+      const olMatch = line.match(/^\s*(\d+)\.\s+(.*)$/);
+
+      if (ulMatch) {
+        openList('ul');
+        html += '<li>' + parseInline(escapeHtml(ulMatch[2].trim())) + '</li>\n';
+        continue;
+      } else if (olMatch) {
+        openList('ol');
+        html += '<li>' + parseInline(escapeHtml(olMatch[2].trim())) + '</li>\n';
+        continue;
+      } else {
+        closeList();
+      }
+
+      // Blank line
+      if (/^\s*$/.test(line)) {
+        html += '\n';
+        continue;
+      }
+
+      // Paragraph
+      html += '<p>' + parseInline(escapeHtml(line.trim())) + '</p>\n';
+    }
+
+    if (inCodeBlock && codeBuffer.length) {
+      html += '<pre><code>' + escapeHtml(codeBuffer.join('\n')) + '</code></pre>\n';
+    }
+    closeList();
+    closeBlockquote();
+    return html;
+  }
+
+  function updatePreview() {
+    preview.innerHTML = parseMarkdown(editor.value);
+  }
+
+  editor.addEventListener('input', updatePreview);
+
+  copyBtn.addEventListener('click', function () {
+    const htmlContent = preview.innerHTML;
+    if (navigator.clipboard && navigator.clipboard.writeText) {
+      navigator.clipboard.writeText(htmlContent).then(function () {
+        copyStatus.textContent = 'Copied!';
+        setTimeout(function () { copyStatus.textContent = ''; }, 1200);
+      }).catch(function () {
+        fallbackCopy(htmlContent);
+      });
+    } else {
+      fallbackCopy(htmlContent);
+    }
+  });
+
+  function fallbackCopy(text) {
+    const temp = document.createElement('textarea');
+    temp.value = text;
+    temp.style.position = 'fixed';
+    temp.style.left = '-9999px';
+    document.body.appendChild(temp);
+    temp.select();
+    try {
+      document.execCommand('copy');
+      copyStatus.textContent = 'Copied!';
+    } catch (e) {
+      copyStatus.textContent = 'Failed';
+    }
+    document.body.removeChild(temp);
+    setTimeout(function () { copyStatus.textContent = ''; }, 1200);
+  }
+
+  const initial = [
+    '# Live Markdown Editor',
+    '',
+    'Type **Markdown** on the left, see HTML preview on the right.',
+    '',
+    '## Features',
+    '',
+    '- Headings (`#` – `######`)',
+    '- **Bold** and *italic*',
+    '- Lists (ordered and unordered)',
+    '- [Links](https://example.com)',
+    '- `Inline code` and fenced code blocks:',
+    '',
+    '```js',
+    'function hello(name) {',
+    '  console.log("Hello, " + name + "!");',
+    '}',
+    '```',
+    '',
+    '- Blockquotes:',
+    '',
+    '> Markdown is a lightweight markup language',
+    '> for creating formatted text.',
+    '',
+    '---',
+    '',
+    'Happy editing!'
+  ].join('\n');
+
+  editor.value = initial;
+  updatePreview();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9cbc4e3c60a6/gpt.png b/data/artifacts/9cbc4e3c60a6/gpt.png
new file mode 100644
index 0000000..f1267a5
Binary files /dev/null and b/data/artifacts/9cbc4e3c60a6/gpt.png differ
diff --git a/data/artifacts/9cbc4e3c60a6/grok.html b/data/artifacts/9cbc4e3c60a6/grok.html
new file mode 100644
index 0000000..a108f8f
--- /dev/null
+++ b/data/artifacts/9cbc4e3c60a6/grok.html
@@ -0,0 +1,447 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Live Markdown Editor</title>
+<style>
+  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+  html, body { height: 100%; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
+  body { display: flex; flex-direction: column; background: #1a1b26; color: #c0caf5; }
+  header {
+    display: flex; align-items: center; justify-content: space-between;
+    padding: 10px 16px; background: #16161e; border-bottom: 1px solid #3b4261;
+    flex-shrink: 0;
+  }
+  header h1 { font-size: 16px; font-weight: 600; color: #7aa2f7; letter-spacing: 0.02em; }
+  header .actions { display: flex; gap: 8px; }
+  button {
+    background: #7aa2f7; color: #1a1b26; border: none; border-radius: 6px;
+    padding: 6px 14px; font-size: 13px; font-weight: 600; cursor: pointer;
+    transition: background 0.15s, transform 0.1s;
+  }
+  button:hover { background: #89b4fa; }
+  button:active { transform: scale(0.97); }
+  button.copied { background: #9ece6a; }
+  .container { display: flex; flex: 1; overflow: hidden; }
+  .pane { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
+  .pane-label {
+    padding: 6px 14px; font-size: 11px; font-weight: 600; text-transform: uppercase;
+    letter-spacing: 0.08em; color: #565f89; background: #16161e;
+    border-bottom: 1px solid #3b4261; flex-shrink: 0;
+  }
+  .editor-pane { border-right: 1px solid #3b4261; }
+  #input {
+    flex: 1; width: 100%; border: none; outline: none; resize: none;
+    padding: 16px; font-family: "SF Mono", "Fira Code", "Consolas", monospace;
+    font-size: 14px; line-height: 1.6; background: #1a1b26; color: #c0caf5;
+    tab-size: 2;
+  }
+  #preview {
+    flex: 1; overflow-y: auto; padding: 16px 20px; background: #1a1b26;
+    font-size: 15px; line-height: 1.7; color: #c0caf5; word-wrap: break-word;
+  }
+  #preview h1, #preview h2, #preview h3, #preview h4, #preview h5, #preview h6 {
+    margin: 1.2em 0 0.5em; font-weight: 700; line-height: 1.3; color: #c0caf5;
+  }
+  #preview h1 { font-size: 2em; border-bottom: 1px solid #3b4261; padding-bottom: 0.3em; }
+  #preview h2 { font-size: 1.5em; border-bottom: 1px solid #3b4261; padding-bottom: 0.3em; }
+  #preview h3 { font-size: 1.25em; }
+  #preview h4 { font-size: 1.1em; }
+  #preview h5 { font-size: 1em; }
+  #preview h6 { font-size: 0.9em; color: #565f89; }
+  #preview h1:first-child, #preview h2:first-child, #preview h3:first-child { margin-top: 0; }
+  #preview p { margin: 0.8em 0; }
+  #preview a { color: #7aa2f7; text-decoration: none; }
+  #preview a:hover { text-decoration: underline; }
+  #preview strong { color: #e0af68; font-weight: 700; }
+  #preview em { color: #bb9af7; font-style: italic; }
+  #preview del { color: #565f89; }
+  #preview code {
+    font-family: "SF Mono", "Fira Code", "Consolas", monospace;
+    background: #24283b; color: #f7768e; padding: 0.15em 0.4em;
+    border-radius: 4px; font-size: 0.88em;
+  }
+  #preview pre {
+    background: #24283b; border-radius: 8px; padding: 14px 16px;
+    overflow-x: auto; margin: 1em 0; border: 1px solid #3b4261;
+  }
+  #preview pre code {
+    background: none; color: #9ece6a; padding: 0; font-size: 13px; line-height: 1.5;
+  }
+  #preview blockquote {
+    border-left: 4px solid #7aa2f7; margin: 1em 0; padding: 0.4em 1em;
+    color: #a9b1d6; background: #24283b; border-radius: 0 6px 6px 0;
+  }
+  #preview blockquote p { margin: 0.3em 0; }
+  #preview ul, #preview ol { margin: 0.8em 0; padding-left: 1.8em; }
+  #preview li { margin: 0.25em 0; }
+  #preview li > ul, #preview li > ol { margin: 0.2em 0; }
+  #preview hr { border: none; border-top: 2px solid #3b4261; margin: 1.5em 0; }
+  #preview img { max-width: 100%; border-radius: 6px; }
+  #preview table { border-collapse: collapse; margin: 1em 0; width: 100%; }
+  #preview th, #preview td { border: 1px solid #3b4261; padding: 8px 12px; text-align: left; }
+  #preview th { background: #24283b; font-weight: 600; color: #7aa2f7; }
+  .toast {
+    position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(80px);
+    background: #9ece6a; color: #1a1b26; padding: 10px 20px; border-radius: 8px;
+    font-size: 13px; font-weight: 600; opacity: 0; transition: all 0.3s ease;
+    pointer-events: none; z-index: 100;
+  }
+  .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
+</style>
+</head>
+<body>
+<header>
+  <h1>Markdown Editor</h1>
+  <div class="actions">
+    <button id="copyBtn" type="button">Copy HTML</button>
+    <button id="clearBtn" type="button" style="background:#f7768e">Clear</button>
+  </div>
+</header>
+<div class="container">
+  <div class="pane editor-pane">
+    <div class="pane-label">Markdown</div>
+    <textarea id="input" spellcheck="false" placeholder="Type markdown here..."></textarea>
+  </div>
+  <div class="pane">
+    <div class="pane-label">Preview</div>
+    <div id="preview"></div>
+  </div>
+</div>
+<div class="toast" id="toast">HTML copied to clipboard</div>
+<script>
+(function () {
+  "use strict";
+
+  function escapeHtml(str) {
+    return str
+      .replace(/&/g, "&amp;")
+      .replace(/</g, "&lt;")
+      .replace(/>/g, "&gt;")
+      .replace(/"/g, "&quot;")
+      .replace(/'/g, "&#39;");
+  }
+
+  function parseInline(text) {
+    var s = escapeHtml(text);
+    // images
+    s = s.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">');
+    // links
+    s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
+    // inline code
+    s = s.replace(/`([^`]+)`/g, function (_, code) {
+      return "<code>" + code + "</code>";
+    });
+    // bold+italic
+    s = s.replace(/\*\*\*([^*]+)\*\*\*/g, "<strong><em>$1</em></strong>");
+    s = s.replace(/___([^_]+)___/g, "<strong><em>$1</em></strong>");
+    // bold
+    s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
+    s = s.replace(/__([^_]+)__/g, "<strong>$1</strong>");
+    // italic
+    s = s.replace(/\*([^*]+)\*/g, "<em>$1</em>");
+    s = s.replace(/_([^_]+)_/g, "<em>$1</em>");
+    // strikethrough
+    s = s.replace(/~~([^~]+)~~/g, "<del>$1</del>");
+    // autolinks
+    s = s.replace(/&lt;(https?:\/\/[^&]+)&gt;/g, '<a href="$1" target="_blank" rel="noopener">$1</a>');
+    return s;
+  }
+
+  function parseMarkdown(src) {
+    var lines = src.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
+    var html = [];
+    var i = 0;
+    var n = lines.length;
+
+    function isBlank(idx) {
+      return idx >= n || /^\s*$/.test(lines[idx]);
+    }
+
+    while (i < n) {
+      var line = lines[i];
+
+      // fenced code block
+      var fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
+      if (fenceMatch) {
+        var fence = fenceMatch[1];
+        var lang = fenceMatch[2].trim();
+        var codeLines = [];
+        i++;
+        while (i < n && !lines[i].match(new RegExp("^" + fence[0] + "{" + fence.length + ",}\\s*$"))) {
+          codeLines.push(lines[i]);
+          i++;
+        }
+        if (i < n) i++; // skip closing fence
+        var codeContent = escapeHtml(codeLines.join("\n"));
+        var langClass = lang ? ' class="language-' + escapeHtml(lang) + '"' : "";
+        html.push("<pre><code" + langClass + ">" + codeContent + "</code></pre>");
+        continue;
+      }
+
+      // ATX headings
+      var headingMatch = line.match(/^(#{1,6})\s+(.*?)(?:\s+#*)?\s*$/);
+      if (headingMatch) {
+        var level = headingMatch[1].length;
+        html.push("<h" + level + ">" + parseInline(headingMatch[2]) + "</h" + level + ">");
+        i++;
+        continue;
+      }
+
+      // horizontal rule
+      if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line)) {
+        html.push("<hr>");
+        i++;
+        continue;
+      }
+
+      // blockquote
+      if (/^>\s?/.test(line)) {
+        var bqLines = [];
+        while (i < n && /^>\s?/.test(lines[i])) {
+          bqLines.push(lines[i].replace(/^>\s?/, ""));
+          i++;
+        }
+        var bqInner = parseMarkdown(bqLines.join("\n"));
+        html.push("<blockquote>" + bqInner + "</blockquote>");
+        continue;
+      }
+
+      // unordered list
+      if (/^[\t ]*[-*+]\s+/.test(line)) {
+        html.push(parseList(false));
+        continue;
+      }
+
+      // ordered list
+      if (/^[\t ]*\d+[.)]\s+/.test(line)) {
+        html.push(parseList(true));
+        continue;
+      }
+
+      // table
+      if (i + 1 < n && /^\|?.+\|/.test(line) && /^\|?[\t ]*:?-+:?[\t ]*(\|[\t ]*:?-+:?[\t ]*)+\|?\s*$/.test(lines[i + 1])) {
+        html.push(parseTable());
+        continue;
+      }
+
+      // blank line
+      if (/^\s*$/.test(line)) {
+        i++;
+        continue;
+      }
+
+      // paragraph
+      var paraLines = [];
+      while (i < n && !/^\s*$/.test(lines[i]) &&
+             !/^#{1,6}\s/.test(lines[i]) &&
+             !/^(\*{3,}|-{3,}|_{3,})\s*$/.test(lines[i]) &&
+             !/^>\s?/.test(lines[i]) &&
+             !/^[\t ]*[-*+]\s+/.test(lines[i]) &&
+             !/^[\t ]*\d+[.)]\s+/.test(lines[i]) &&
+             !/^(`{3,}|~{3,})/.test(lines[i])) {
+        paraLines.push(lines[i]);
+        i++;
+      }
+      html.push("<p>" + parseInline(paraLines.join(" ")) + "</p>");
+    }
+
+    function parseList(ordered) {
+      var tag = ordered ? "ol" : "ul";
+      var items = [];
+      var marker = ordered ? /^[\t ]*\d+[.)]\s+/ : /^[\t ]*[-*+]\s+/;
+
+      while (i < n && marker.test(lines[i])) {
+        var itemText = lines[i].replace(marker, "");
+        i++;
+        // continuation lines
+        while (i < n && /^[\t ]{2,}\S/.test(lines[i]) && !marker.test(lines[i])) {
+          itemText += " " + lines[i].replace(/^[\t ]+/, "");
+          i++;
+        }
+        // nested lists
+        var nested = "";
+        if (i < n && /^[\t ]{2,}[-*+]\s+/.test(lines[i])) {
+          var nestedLines = [];
+          var baseIndent = lines[i].match(/^([\t ]+)/)[1].length;
+          while (i < n && new RegExp("^[\t ]{" + baseIndent + "}").test(lines[i])) {
+            nestedLines.push(lines[i].replace(new RegExp("^[\t ]{" + baseIndent + "}"), ""));
+            i++;
+          }
+          nested = parseMarkdown(nestedLines.join("\n"));
+        } else if (i < n && /^[\t ]{2,}\d+[.)]\s+/.test(lines[i])) {
+          var nestedLines2 = [];
+          var baseIndent2 = lines[i].match(/^([\t ]+)/)[1].length;
+          while (i < n && new RegExp("^[\t ]{" + baseIndent2 + "}").test(lines[i])) {
+            nestedLines2.push(lines[i].replace(new RegExp("^[\t ]{" + baseIndent2 + "}"), ""));
+            i++;
+          }
+          nested = parseMarkdown(nestedLines2.join("\n"));
+        }
+        items.push("<li>" + parseInline(itemText) + nested + "</li>");
+      }
+      return "<" + tag + ">" + items.join("") + "</" + tag + ">";
+    }
+
+    function parseTable() {
+      function splitRow(row) {
+        var r = row.trim();
+        if (r.charAt(0) === "|") r = r.slice(1);
+        if (r.charAt(r.length - 1) === "|") r = r.slice(0, -1);
+        return r.split("|").map(function (c) { return c.trim(); });
+      }
+
+      var headers = splitRow(lines[i]);
+      i++; // header
+      var aligns = splitRow(lines[i]).map(function (cell) {
+        var left = cell.charAt(0) === ":";
+        var right = cell.charAt(cell.length - 1) === ":";
+        if (left && right) return "center";
+        if (right) return "right";
+        if (left) return "left";
+        return "";
+      });
+      i++; // separator
+
+      var rows = [];
+      while (i < n && /^\|?.+\|/.test(lines[i]) && !/^\s*$/.test(lines[i])) {
+        rows.push(splitRow(lines[i]));
+        i++;
+      }
+
+      var out = "<table><thead><tr>";
+      for (var h = 0; h < headers.length; h++) {
+        var style = aligns[h] ? ' style="text-align:' + aligns[h] + '"' : "";
+        out += "<th" + style + ">" + parseInline(headers[h]) + "</th>";
+      }
+      out += "</tr></thead><tbody>";
+      for (var r = 0; r < rows.length; r++) {
+        out += "<tr>";
+        for (var c = 0; c < headers.length; c++) {
+          var style2 = aligns[c] ? ' style="text-align:' + aligns[c] + '"' : "";
+          out += "<td" + style2 + ">" + parseInline(rows[r][c] || "") + "</td>";
+        }
+        out += "</tr>";
+      }
+      out += "</tbody></table>";
+      return out;
+    }
+
+    return html.join("\n");
+  }
+
+  var input = document.getElementById("input");
+  var preview = document.getElementById("preview");
+  var copyBtn = document.getElementById("copyBtn");
+  var clearBtn = document.getElementById("clearBtn");
+  var toast = document.getElementById("toast");
+
+  var defaultMd = [
+    "# Live Markdown Editor",
+    "",
+    "Type on the **left**, see the *preview* on the right.",
+    "",
+    "## Features",
+    "",
+    "- Headings (h1–h6)",
+    "- **Bold**, *italic*, ***both***, ~~strikethrough~~",
+    "- Ordered and unordered lists",
+    "- [Links](https://example.com) and images",
+    "- `inline code` and fenced code blocks",
+    "- > Blockquotes",
+    "- Horizontal rules",
+    "",
+    "---",
+    "",
+    "### Code Example",
+    "",
+    "```js",
+    "function greet(name) {",
+    "  return 'Hello, ' + name + '!';",
+    "}",
+    "console.log(greet('World'));",
+    "```",
+    "",
+    "### Blockquote",
+    "",
+    "> This is a blockquote.",
+    "> It can span multiple lines.",
+    "",
+    "### Ordered List",
+    "",
+    "1. First item",
+    "2. Second item",
+    "3. Third item",
+    "",
+    "### Nested List",
+    "",
+    "- Fruits",
+    "  - Apple",
+    "  - Banana",
+    "- Vegetables",
+    "  - Carrot",
+    "  - Spinach",
+    "",
+    "Happy writing!"
+  ].join("\n");
+
+  input.value = defaultMd;
+
+  function render() {
+    preview.innerHTML = parseMarkdown(input.value);
+  }
+
+  input.addEventListener("input", render);
+  render();
+
+  // Tab key inserts spaces
+  input.addEventListener("keydown", function (e) {
+    if (e.key === "Tab") {
+      e.preventDefault();
+      var start = input.selectionStart;
+      var end = input.selectionEnd;
+      input.value = input.value.substring(0, start) + "  " + input.value.substring(end);
+      input.selectionStart = input.selectionEnd = start + 2;
+      render();
+    }
+  });
+
+  copyBtn.addEventListener("click", function () {
+    var htmlContent = preview.innerHTML;
+    if (navigator.clipboard && navigator.clipboard.writeText) {
+      navigator.clipboard.writeText(htmlContent).then(showToast, fallbackCopy);
+    } else {
+      fallbackCopy();
+    }
+    function fallbackCopy() {
+      var ta = document.createElement("textarea");
+      ta.value = htmlContent;
+      ta.style.cssText = "position:fixed;left:-9999px";
+      document.body.appendChild(ta);
+      ta.select();
+      try { document.execCommand("copy"); showToast(); } catch (e) {}
+      document.body.removeChild(ta);
+    }
+  });
+
+  function showToast() {
+    copyBtn.textContent = "Copied!";
+    copyBtn.classList.add("copied");
+    toast.classList.add("show");
+    setTimeout(function () {
+      copyBtn.textContent = "Copy HTML";
+      copyBtn.classList.remove("copied");
+      toast.classList.remove("show");
+    }, 1800);
+  }
+
+  clearBtn.addEventListener("click", function () {
+    input.value = "";
+    render();
+    input.focus();
+  });
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9cbc4e3c60a6/grok.png b/data/artifacts/9cbc4e3c60a6/grok.png
new file mode 100644
index 0000000..8425245
Binary files /dev/null and b/data/artifacts/9cbc4e3c60a6/grok.png differ
diff --git a/data/artifacts/9cbc4e3c60a6/hermes3-8b.html b/data/artifacts/9cbc4e3c60a6/hermes3-8b.html
new file mode 100644
index 0000000..4c7f24d
--- /dev/null
+++ b/data/artifacts/9cbc4e3c60a6/hermes3-8b.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Live Markdown Editor</title>
+    <style>
+        body {
+            display: flex;
+            flex-direction: column;
+            align-items: stretch;
+            height: 100vh;
+            margin: 0;
+            font-family: Arial, sans-serif;
+        }
+        
+        #editor, #preview {
+            flex-grow: 1;
+            border: 1px solid #ccc;
+            padding: 10px;
+            font-family: Monaco, Consolas, "Courier New", monospace;
+            font-size: 14px;
+        }
+        
+        #editor {
+            resize: vertical;
+        }
+        
+        #preview {
+            border-left: none;
+        }
+        
+        .code {
+            background-color: #f4f4f4;
+            padding: 5px;
+        }
+        
+        button {
+            margin-top: 10px;
+        }
+    </style>
+</head>
+<body>
+    <textarea id="editor"></textarea>
+    
+    <div id="preview"></div>
+    
+    <button onclick="copyHTML()">Copy HTML</button>
+    
+    <script>
+        function parseMarkdown(markdown) {
+            const parsed = markdown.replace(/### (\S+)/g, '<h3>$1</h3>').replace(/## (\S+)/g, '<h2>$1</h2>').replace(/# (\S+)/g, '<h1>$1</h1>').replace(/\*\*([\s\S]*?)\*\*/g, '<strong>$1</strong>').replace(/\_([^_]+)\_/g, '<em>$1</em>').replace(/\n/g, '<br>').replace(/^(\s*)/gm, '$1<pre><code class="code">').replace(/<\/pre>/g, '</code></pre>');
+            return parsed;
+        }
+        
+        function updatePreview() {
+            const editor = document.getElementById('editor');
+            const preview = document.getElementById('preview');
+            const markdown = editor.value;
+            
+            preview.innerHTML = parseMarkdown(markdown);
+        }
+        
+        document.getElementById('editor').addEventListener('input', updatePreview);
+        
+        function copyHTML() {
+            const preview = document.getElementById('preview');
+            const html = preview.innerHTML;
+            
+            navigator.clipboard.writeText(html).then(() => {
+                alert("HTML copied to clipboard!");
+            });
+        }
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9cbc4e3c60a6/hermes3-8b.png b/data/artifacts/9cbc4e3c60a6/hermes3-8b.png
new file mode 100644
index 0000000..9a64df7
Binary files /dev/null and b/data/artifacts/9cbc4e3c60a6/hermes3-8b.png differ
diff --git a/data/artifacts/9cbc4e3c60a6/hf-qwen-coder-32b.html b/data/artifacts/9cbc4e3c60a6/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..d68a1e6
--- /dev/null
+++ b/data/artifacts/9cbc4e3c60a6/hf-qwen-coder-32b.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Markdown Editor</title>
+<style>
+  body { font-family: Arial, sans-serif; display: flex; height: 100vh; margin: 0; }
+  #editor, #preview { width: 50%; padding: 20px; box-sizing: border-box; overflow-y: auto; }
+  #editor { border-right: 1px solid #ccc; resize: none; outline: none; font-size: 16px; }
+  #preview p { margin: 0.5em 0; }
+  #preview h1, #preview h2, #preview h3, #preview h4, #preview h5, #preview h6 { margin-top: 1em; }
+  #preview ul, #preview ol { margin: 1em 0 1em 2em; padding: 0; list-style-position: outside; }
+  #preview pre { background-color: #f4f4f4; padding: 10px; border-radius: 5px; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word; }
+  #preview blockquote { margin: 20px 0; padding-left: 1em; border-left: 4px solid #ccc; }
+  button { position: absolute; top: 10px; right: 10px; cursor: pointer; background-color: #007BFF; color: white; border: none; padding: 5px 10px; border-radius: 3px; }
+</style>
+</head>
+<body>
+<textarea id="editor"># Markdown Editor\nType here...\n\n- List item\n- Another list item\n\n[Link](http://example.com)\n\n\`\`\`\ncode block\n\`\`\`\n\n> Blockquote</textarea>
+<div id="preview"></div>
+<button onclick="copyHTML()">Copy HTML</button>
+<script>
+  const editor = document.getElementById('editor');
+  const preview = document.getElementById('preview');
+
+  function parseMarkdown(text) {
+    text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
+    text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>').replace(/\*([^*]+)\*/g, '<em>$1</em>');
+    text = text.replace(/__([^_]+)__/g, '<strong>$1</strong>').replace(/_([^_]+)_/g, '<em>$1</em>');
+    text = text.replace(/!\[([^\]]+)\]\(([^\)]+)\)/g, "<img src='$2' alt='$1'>");
+    text = text.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, "<a href='$2'>$1</a>");
+    text = text.replace(/#{1,6} (.+)/g, (match, p1) => `<h${match.split(' ')[0].length}>${p1}</h${match.split(' ')[0].length}>`);
+    text = text.replace(/^(> .+)/gm, '<blockquote>$1</blockquote>').replace(/^>\s+(.+)/gm, '$1');
+    text = text.replace(/^- (.+)/gm, '<ul><li>$1</li></ul>').replace(/^\d+\.\s+(.+)/gm, '<ol><li>$1</li></ol>');
+    text = text.replace(/<\/(ul|ol)>\n<ul>/g, '').replace(/<\/(ul|ol)>\n<ol>/g, '');
+    text = text.replace(/\n/g, '<br>').replace(/<br><\/(ul|ol)>/g, '</$1>');
+    text = text.replace(/```([\s\S]*?)```/g, (match, p1) => `<pre><code>${p1.trim()}</code></pre>`);
+    return text;
+  }
+
+  function updatePreview() {
+    preview.innerHTML = parseMarkdown(editor.value);
+  }
+
+  editor.addEventListener('input', updatePreview);
+
+  function copyHTML() {
+    const tempInput = document.createElement("textarea");
+    tempInput.value = preview.innerHTML;
+    document.body.appendChild(tempInput);
+    tempInput.select();
+    document.execCommand("copy");
+    document.body.removeChild(tempInput);
+    alert("HTML copied to clipboard!");
+  }
+
+  updatePreview();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9cbc4e3c60a6/hf-qwen-coder-32b.png b/data/artifacts/9cbc4e3c60a6/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..61c4915
Binary files /dev/null and b/data/artifacts/9cbc4e3c60a6/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/9cbc4e3c60a6/kimi.html b/data/artifacts/9cbc4e3c60a6/kimi.html
new file mode 100644
index 0000000..97fb123
--- /dev/null
+++ b/data/artifacts/9cbc4e3c60a6/kimi.html
@@ -0,0 +1,258 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Live Markdown Editor</title>
+<style>
+  * { box-sizing: border-box; margin: 0; padding: 0; }
+  body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; height: 100vh; display: flex; flex-direction: column; background: #f5f5f5; }
+  header { background: #24292e; color: #fff; padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
+  h1 { font-size: 1.25rem; font-weight: 600; }
+  button { background: #2ea44f; color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-size: 0.875rem; font-weight: 500; transition: background 0.2s; }
+  button:hover { background: #2c974b; }
+  button:active { transform: translateY(1px); }
+  .container { flex: 1; display: flex; overflow: hidden; }
+  .pane { flex: 1; display: flex; flex-direction: column; background: white; border-right: 1px solid #e1e4e8; }
+  .pane:last-child { border-right: none; }
+  label { padding: 0.75rem 1rem; background: #f6f8fa; border-bottom: 1px solid #e1e4e8; font-size: 0.75rem; font-weight: 600; color: #586069; text-transform: uppercase; letter-spacing: 0.5px; }
+  textarea { flex: 1; width: 100%; border: none; padding: 1.5rem; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; font-size: 14px; line-height: 1.6; resize: none; outline: none; background: #fafbfc; color: #24292e; }
+  #preview { flex: 1; padding: 1.5rem; overflow-y: auto; background: white; color: #24292e; line-height: 1.6; }
+  
+  /* Markdown Preview Styles */
+  #preview h1, #preview h2, #preview h3, #preview h4, #preview h5, #preview h6 { margin-top: 0; margin-bottom: 16px; font-weight: 600; line-height: 1.25; color: #1a1a1a; }
+  #preview h1 { font-size: 2em; border-bottom: 1px solid #eaecef; padding-bottom: 0.3em; }
+  #preview h2 { font-size: 1.5em; border-bottom: 1px solid #eaecef; padding-bottom: 0.3em; }
+  #preview h3 { font-size: 1.25em; }
+  #preview h4 { font-size: 1em; }
+  #preview p { margin-bottom: 16px; }
+  #preview ul, #preview ol { margin-bottom: 16px; padding-left: 2em; }
+  #preview ul ul, #preview ul ol, #preview ol ul, #preview ol ol { margin-bottom: 0; }
+  #preview li { margin-bottom: 0.25em; }
+  #preview li > p { margin-bottom: 0; }
+  #preview blockquote { padding: 0 1em; color: #6a737d; border-left: 0.25em solid #dfe2e5; margin-bottom: 16px; }
+  #preview blockquote > :first-child { margin-top: 0; }
+  #preview blockquote > :last-child { margin-bottom: 0; }
+  #preview code { background: rgba(27,31,35,0.05); padding: 0.2em 0.4em; border-radius: 3px; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; font-size: 85%; }
+  #preview pre { background: #f6f8fa; padding: 16px; overflow: auto; border-radius: 6px; margin-bottom: 16px; line-height: 1.45; }
+  #preview pre code { background: transparent; padding: 0; font-size: 100%; }
+  #preview a { color: #0366d6; text-decoration: none; }
+  #preview a:hover { text-decoration: underline; }
+  #preview strong { font-weight: 600; }
+  #preview em { font-style: italic; }
+  #preview hr { height: 0.25em; padding: 0; margin: 24px 0; background: #e1e4e8; border: 0; }
+  
+  @media (max-width: 768px) {
+    .container { flex-direction: column; }
+    .pane { border-right: none; border-bottom: 1px solid #e1e4e8; }
+    .pane:last-child { border-bottom: none; }
+  }
+</style>
+</head>
+<body>
+<header>
+  <h1>Markdown Editor</h1>
+  <button id="copyBtn">Copy HTML</button>
+</header>
+<div class="container">
+  <div class="pane">
+    <label>Markdown Input</label>
+    <textarea id="editor" placeholder="Type your markdown here..."># Live Markdown Editor
+
+This is a **real-time** markdown editor with *no external dependencies*.
+
+## Features
+
+- **Headings** (H1-H6)
+- **Bold** and *italic* text
+- [Links](https://example.com)
+- `Inline code` and code blocks
+- Blockquotes
+- Lists (ordered and unordered)
+
+### Code Example
+
+```javascript
+function greet(name) {
+  return `Hello, ${name}!`;
+}
+```
+
+### Blockquote
+
+> "The best way to predict the future is to invent it."
+> — Alan Kay
+
+### Lists
+
+1. First item
+2. Second item
+   - Nested bullet
+   - Another bullet
+3. Third item
+
+---
+
+Enjoy writing!</textarea>
+  </div>
+  <div class="pane">
+    <label>Preview</label>
+    <div id="preview"></div>
+  </div>
+</div>
+
+<script>
+(function() {
+  const editor = document.getElementById('editor');
+  const preview = document.getElementById('preview');
+  const copyBtn = document.getElementById('copyBtn');
+  
+  function escapeHtml(text) {
+    return text.replace(/&/g, '&amp;')
+               .replace(/</g, '&lt;')
+               .replace(/>/g, '&gt;');
+  }
+  
+  function parseInline(text) {
+    // Line breaks
+    text = text.replace(/\n/g, '<br>');
+    
+    // Bold & Italic (order matters: *** first, then **, then *)
+    text = text.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>');
+    text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
+    text = text.replace(/\*(.*?)\*/g, '<em>$1</em>');
+    text = text.replace(/___(.*?)___/g, '<strong><em>$1</em></strong>');
+    text = text.replace(/__(.*?)__/g, '<strong>$1</strong>');
+    text = text.replace(/_(.*?)_/g, '<em>$1</em>');
+    
+    // Links
+    text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
+    
+    return text;
+  }
+  
+  function parseMarkdown(md) {
+    // Escape HTML entities
+    let text = escapeHtml(md);
+    
+    // Protect code blocks
+    const codeBlocks = [];
+    text = text.replace(/```([\s\S]*?)```/g, function(match, code) {
+      codeBlocks.push(code.replace(/^\n|\n$/g, ''));
+      return '\u0000CODEBLOCK' + (codeBlocks.length - 1) + '\u0000';
+    });
+    
+    // Protect inline code
+    const inlineCodes = [];
+    text = text.replace(/`([^`]+)`/g, function(match, code) {
+      inlineCodes.push(code);
+      return '\u0000INLINECODE' + (inlineCodes.length - 1) + '\u0000';
+    });
+    
+    // Split into blocks (separated by blank lines)
+    const blocks = text.split(/\n\n+/);
+    const htmlBlocks = blocks.map(function(block) {
+      block = block.trim();
+      if (!block) return '';
+      
+      // Restore code blocks
+      if (block.indexOf('\u0000CODEBLOCK') === 0) {
+        const idx = parseInt(block.match(/\d+/)[0], 10);
+        return '<pre><code>' + codeBlocks[idx] + '</code></pre>';
+      }
+      
+      // Headings (ATX style)
+      if (block.startsWith('#')) {
+        const match = block.match(/^(#{1,6})\s+(.+)$/);
+        if (match) {
+          const level = match[1].length;
+          const content = parseInline(match[2]);
+          return '<h' + level + '>' + content + '</h' + level + '>';
+        }
+      }
+      
+      // Horizontal rules
+      if (block.match(/^-{3,}$/) || block.match(/^\*{3,}$/) || block.match(/^_{3,}$/)) {
+        return '<hr>';
+      }
+      
+      // Blockquotes
+      if (block.startsWith('&gt;')) {
+        const lines = block.split('\n');
+        const content = lines.map(function(line) {
+          return line.replace(/^&gt;\s?/, '');
+        }).join('\n');
+        return '<blockquote>' + parseInline(content) + '</blockquote>';
+      }
+      
+      // Unordered lists
+      if (block.match(/^[\*\-\+]\s/)) {
+        const lines = block.split('\n');
+        const items = lines.map(function(line) {
+          const match = line.match(/^[\*\-\+]\s+(.*)/);
+          if (match) return '<li>' + parseInline(match[1]) + '</li>';
+          return null;
+        }).filter(function(x) { return x !== null; });
+        if (items.length > 0) return '<ul>' + items.join('') + '</ul>';
+      }
+      
+      // Ordered lists
+      if (block.match(/^\d+\.\s/)) {
+        const lines = block.split('\n');
+        const items = lines.map(function(line) {
+          const match = line.match(/^\d+\.\s+(.*)/);
+          if (match) return '<li>' + parseInline(match[1]) + '</li>';
+          return null;
+        }).filter(function(x) { return x !== null; });
+        if (items.length > 0) return '<ol>' + items.join('') + '</ol>';
+      }
+      
+      // Paragraph (default)
+      return '<p>' + parseInline(block) + '</p>';
+    });
+    
+    let html = htmlBlocks.join('\n');
+    
+    // Restore inline codes
+    html = html.replace(/\u0000INLINECODE(\d+)\u0000/g, function(match, idx) {
+      return '<code>' + inlineCodes[parseInt(idx, 10)] + '</code>';
+    });
+    
+    return html;
+  }
+  
+  function update() {
+    preview.innerHTML = parseMarkdown(editor.value);
+  }
+  
+  function copyHtml() {
+    const html = preview.innerHTML;
+    if (navigator.clipboard && navigator.clipboard.writeText) {
+      navigator.clipboard.writeText(html).then(function() {
+        const original = copyBtn.textContent;
+        copyBtn.textContent = 'Copied!';
+        setTimeout(function() { copyBtn.textContent = original; }, 2000);
+      });
+    } else {
+      // Fallback
+      const textarea = document.createElement('textarea');
+      textarea.value = html;
+      document.body.appendChild(textarea);
+      textarea.select();
+      document.execCommand('copy');
+      document.body.removeChild(textarea);
+      const original = copyBtn.textContent;
+      copyBtn.textContent = 'Copied!';
+      setTimeout(function() { copyBtn.textContent = original; }, 2000);
+    }
+  }
+  
+  editor.addEventListener('input', update);
+  copyBtn.addEventListener('click', copyHtml);
+  
+  // Initial render
+  update();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9cbc4e3c60a6/kimi.png b/data/artifacts/9cbc4e3c60a6/kimi.png
new file mode 100644
index 0000000..834f40c
Binary files /dev/null and b/data/artifacts/9cbc4e3c60a6/kimi.png differ
diff --git a/data/artifacts/9cbc4e3c60a6/qwen25-7b.html b/data/artifacts/9cbc4e3c60a6/qwen25-7b.html
new file mode 100644
index 0000000..1ee13ca
--- /dev/null
+++ b/data/artifacts/9cbc4e3c60a6/qwen25-7b.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Live Markdown Editor</title>
+    <style>
+        body { font-family: Arial, sans-serif; }
+        .container { display: flex; width: 95%; margin: auto; }
+        textarea, div.preview { padding: 10px; width: 48%; height: 300px; box-sizing: border-box; overflow-y: scroll; }
+        #copyButton { float: right; margin-top: -32px; margin-right: 10px; }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <textarea id="editor" placeholder="# Welcome to the Markdown Editor"></textarea>
+        <div id="preview" class="preview"></div>
+    </div>
+    <button id="copyButton">Copy HTML</button>
+
+    <script>
+        const editor = document.getElementById('editor');
+        const preview = document.getElementById('preview');
+
+        function parseMarkdown(markdown) {
+            return markdown
+                .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
+                .replace(/__(.*?)__/g, '<em>$1</em>')
+                .replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>')
+                .replace(/^(\s*)# (.+)/gm, (match, p1, p2) => `<h${p1.length - 1}>${p2}</h${p1.length - 1}>`)
+                .replace(/^( {4})(.+)$/gm, '<pre><code>$2</code></pre>')
+                .replace(/^> (.+)/gm, (match, p1) => `<blockquote>${p1}</blockquote>`);
+        }
+
+        function updatePreview() {
+            preview.innerHTML = parseMarkdown(editor.value);
+        }
+
+        editor.addEventListener('input', updatePreview);
+
+        document.getElementById('copyButton').addEventListener('click', () => {
+            const tempInput = document.createElement('textarea');
+            tempInput.value = `<div>${preview.innerHTML}</div>`;
+            document.body.appendChild(tempInput);
+            tempInput.select();
+            document.execCommand('copy');
+            document.body.removeChild(tempInput);
+            alert('HTML copied to clipboard!');
+        });
+
+        updatePreview(); // Initial render
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9cbc4e3c60a6/qwen25-7b.png b/data/artifacts/9cbc4e3c60a6/qwen25-7b.png
new file mode 100644
index 0000000..0035f99
Binary files /dev/null and b/data/artifacts/9cbc4e3c60a6/qwen25-7b.png differ
diff --git a/data/artifacts/9cbc4e3c60a6/qwen3-14b.html b/data/artifacts/9cbc4e3c60a6/qwen3-14b.html
new file mode 100644
index 0000000..a81b2c6
--- /dev/null
+++ b/data/artifacts/9cbc4e3c60a6/qwen3-14b.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Live Markdown Editor</title>
+  <style>
+    body {
+      margin: 0;
+      font-family: sans-serif;
+      display: flex;
+      height: 100vh;
+    }
+    textarea, .preview {
+      flex: 1;
+      padding: 10px;
+      box-sizing: border-box;
+      overflow: auto;
+    }
+    textarea {
+      resize: none;
+      font-family: monospace;
+      font-size: 14px;
+      line-height: 1.4;
+    }
+    .preview {
+      background: #f9f9f9;
+      padding: 15px;
+    }
+    button {
+      position: absolute;
+      top: 10px;
+      right: 10px;
+      padding: 8px 16px;
+      font-size: 14px;
+      cursor: pointer;
+    }
+    pre {
+      background: #272822;
+      color: #f8f8f2;
+      padding: 10px;
+      overflow: auto;
+      margin: 10px 0;
+      border-radius: 4px;
+    }
+    code {
+      background: #272822;
+      color: #f8f8f2;
+      padding: 2px 4px;
+      border-radius: 3px;
+    }
+    h1, h2, h3, h4, h5, h6 {
+      margin-top: 1em;
+    }
+    ul, ol {
+      margin-left: 20px;
+      padding-left: 10px;
+    }
+    blockquote {
+      margin: 1em 0;
+      padding: 10px 20px;
+      background: #eaeaea;
+      border-left: 4px solid #ccc;
+    }
+  </style>
+</head>
+<body>
+  <textarea id="editor" placeholder="Write your markdown here..."></textarea>
+  <div class="preview" id="preview"></div>
+  <button id="copyHtml">Copy HTML</button>
+
+  <script>
+    const editor = document.getElementById('editor');
+    const preview = document.getElementById('preview');
+    const copyButton = document.getElementById('copyHtml');
+
+    function parseMarkdown(markdown) {
+      let html = '';
+
+      // Headings
+      html += markdown.replace(/^#{1,6}\s+(.+)$/gm, (_, level, text) => {
+        return `<h${level}>${text}</h${level}>`;
+      });
+
+      // Bold and italic
+      html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
+      html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
+
+      // Lists
+      html = html.replace(/^\s*-\s+(.+)$/gm, '<li>$1</li>');
+      html = html.replace(/^\s*\d+\.\s+(.+)$/gm, '<li>$1</li>');
+      html = html.replace(/<li>(.*?)<\/li>/g, (_, content) => {
+        return '<ul><li>' + content + '</li></ul>';
+      });
+      html = html.replace(/(<ul><li>.*?<\/li><\/ul>)(?=\s*<ul><li>)/g, '$1\n');
+
+      // Links
+      html = html.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>');
+
+      // Code blocks
+      html = html.replace(/
\ No newline at end of file
diff --git a/data/artifacts/9cbc4e3c60a6/qwen3-14b.png b/data/artifacts/9cbc4e3c60a6/qwen3-14b.png
new file mode 100644
index 0000000..d4ea812
Binary files /dev/null and b/data/artifacts/9cbc4e3c60a6/qwen3-14b.png differ
diff --git a/data/artifacts/e840f1972643/claude-code.html b/data/artifacts/e840f1972643/claude-code.html
new file mode 100644
index 0000000..87ac1bf
--- /dev/null
+++ b/data/artifacts/e840f1972643/claude-code.html
@@ -0,0 +1,554 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Wireframe Orbit</title>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  html, body { height: 100%; overflow: hidden; }
+  body {
+    background: radial-gradient(circle at 50% 30%, #14192b 0%, #05060c 70%, #000 100%);
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+    color: #cfd6e6;
+    user-select: none;
+    -webkit-user-select: none;
+  }
+  #stage { position: fixed; inset: 0; }
+  canvas { display: block; width: 100%; height: 100%; cursor: grab; }
+  canvas.dragging { cursor: grabbing; }
+
+  #panel {
+    position: fixed;
+    top: 18px; left: 18px;
+    background: rgba(16, 20, 34, 0.72);
+    backdrop-filter: blur(10px);
+    -webkit-backdrop-filter: blur(10px);
+    border: 1px solid rgba(120, 140, 200, 0.22);
+    border-radius: 14px;
+    padding: 16px 18px 18px;
+    width: 232px;
+    box-shadow: 0 10px 40px rgba(0,0,0,0.5);
+    z-index: 10;
+  }
+  #panel h1 {
+    font-size: 13px;
+    font-weight: 600;
+    letter-spacing: 0.14em;
+    text-transform: uppercase;
+    color: #7fd0ff;
+    margin-bottom: 4px;
+  }
+  #panel .sub {
+    font-size: 11px;
+    color: #6f7690;
+    margin-bottom: 14px;
+    line-height: 1.4;
+  }
+  .row { margin-bottom: 13px; }
+  .row label {
+    display: block;
+    font-size: 10px;
+    letter-spacing: 0.1em;
+    text-transform: uppercase;
+    color: #8790a8;
+    margin-bottom: 6px;
+  }
+  .seg {
+    display: flex;
+    gap: 4px;
+    background: rgba(0,0,0,0.3);
+    padding: 4px;
+    border-radius: 9px;
+  }
+  .seg button {
+    flex: 1;
+    background: transparent;
+    border: none;
+    color: #aab2c8;
+    font-size: 11px;
+    font-family: inherit;
+    padding: 7px 4px;
+    border-radius: 6px;
+    cursor: pointer;
+    transition: background 0.15s, color 0.15s;
+  }
+  .seg button:hover { color: #fff; }
+  .seg button.active {
+    background: linear-gradient(180deg, #3d6bff, #2a4fd6);
+    color: #fff;
+    box-shadow: 0 2px 10px rgba(60,100,255,0.4);
+  }
+  select {
+    width: 100%;
+    background: rgba(0,0,0,0.3);
+    color: #dfe5f2;
+    border: 1px solid rgba(120,140,200,0.25);
+    border-radius: 8px;
+    padding: 8px 10px;
+    font-size: 12px;
+    font-family: inherit;
+    cursor: pointer;
+    outline: none;
+  }
+  input[type=range] {
+    width: 100%;
+    -webkit-appearance: none;
+    appearance: none;
+    height: 4px;
+    border-radius: 3px;
+    background: rgba(120,140,200,0.25);
+    outline: none;
+  }
+  input[type=range]::-webkit-slider-thumb {
+    -webkit-appearance: none;
+    width: 15px; height: 15px;
+    border-radius: 50%;
+    background: #7fd0ff;
+    cursor: pointer;
+    box-shadow: 0 0 8px rgba(127,208,255,0.7);
+  }
+  input[type=range]::-moz-range-thumb {
+    width: 15px; height: 15px; border: none;
+    border-radius: 50%; background: #7fd0ff; cursor: pointer;
+  }
+  .val { float: right; color: #7fd0ff; font-variant-numeric: tabular-nums; }
+  .hint {
+    font-size: 10.5px;
+    color: #5b melody;
+    color: #5c6480;
+    margin-top: 4px;
+    line-height: 1.5;
+  }
+</style>
+</head>
+<body>
+<div id="stage">
+  <canvas id="c"></canvas>
+</div>
+
+<div id="panel">
+  <h1>Wireframe Orbit</h1>
+  <div class="sub">Hand-rolled perspective 3D. Drag the scene to orbit.</div>
+
+  <div class="row">
+    <label>Shape</label>
+    <select id="shape">
+      <option value="icosahedron">Icosahedron</option>
+      <option value="torus">Torus</option>
+      <option value="octahedron">Octahedron</option>
+      <option value="cube">Cube</option>
+    </select>
+  </div>
+
+  <div class="row">
+    <label>Render</label>
+    <div class="seg" id="renderSeg">
+      <button data-mode="wire" class="active">Wireframe</button>
+      <button data-mode="solid">Solid</button>
+    </div>
+  </div>
+
+  <div class="row">
+    <label>Spin <span class="val" id="spinVal">0.6</span></label>
+    <input type="range" id="spin" min="0" max="2" step="0.01" value="0.6">
+  </div>
+
+  <div class="row">
+    <label>Field of View <span class="val" id="fovVal">60°</span></label>
+    <input type="range" id="fov" min="25" max="100" step="1" value="60">
+  </div>
+
+  <div class="hint">Drag: orbit &nbsp;•&nbsp; Scroll: zoom &nbsp;•&nbsp; Double-click: reset</div>
+</div>
+
+<script>
+(function () {
+  "use strict";
+
+  const canvas = document.getElementById('c');
+  const ctx = canvas.getContext('2d');
+
+  // ---- State ----
+  const state = {
+    shapeName: 'icosahedron',
+    mode: 'wire',
+    spin: 0.6,
+    fovDeg: 60,
+    rotX: -0.45,
+    rotY: 0.6,
+    autoRot: 0,          // accumulated auto-spin about Y
+    dist: 4.2,           // camera distance
+    dragging: false,
+    lastX: 0, lastY: 0,
+    W: 0, H: 0, DPR: 1
+  };
+
+  // ============================================================
+  //  Geometry builders  -> { verts: [[x,y,z]...], edges: [[a,b]...], faces: [[i,j,k...]...] }
+  // ============================================================
+
+  function icosahedron() {
+    const t = (1 + Math.sqrt(5)) / 2;
+    let verts = [
+      [-1, t, 0], [1, t, 0], [-1, -t, 0], [1, -t, 0],
+      [0, -1, t], [0, 1, t], [0, -1, -t], [0, 1, -t],
+      [t, 0, -1], [t, 0, 1], [-t, 0, -1], [-t, 0, 1]
+    ];
+    const faces = [
+      [0,11,5],[0,5,1],[0,1,7],[0,7,10],[0,10,11],
+      [1,5,9],[5,11,4],[11,10,2],[10,7,6],[7,1,8],
+      [3,9,4],[3,4,2],[3,2,6],[3,6,8],[3,8,9],
+      [4,9,5],[2,4,11],[6,2,10],[8,6,7],[9,8,1]
+    ];
+    verts = normalizeScale(verts, 1.35);
+    return { verts, edges: edgesFromFaces(faces), faces };
+  }
+
+  function octahedron() {
+    let verts = [
+      [1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]
+    ];
+    const faces = [
+      [0,2,4],[2,1,4],[1,3,4],[3,0,4],
+      [2,0,5],[1,2,5],[3,1,5],[0,3,5]
+    ];
+    verts = normalizeScale(verts, 1.5);
+    return { verts, edges: edgesFromFaces(faces), faces };
+  }
+
+  function cube() {
+    let verts = [
+      [-1,-1,-1],[1,-1,-1],[1,1,-1],[-1,1,-1],
+      [-1,-1, 1],[1,-1, 1],[1,1, 1],[-1,1, 1]
+    ];
+    const faces = [
+      [0,1,2,3], // back
+      [5,4,7,6], // front
+      [4,0,3,7], // left
+      [1,5,6,2], // right
+      [3,2,6,7], // top
+      [4,5,1,0]  // bottom
+    ];
+    verts = verts.map(v => v.map(c => c * 0.9));
+    return { verts, edges: edgesFromFaces(faces), faces };
+  }
+
+  function torus(R, r, seg, ring) {
+    const verts = [];
+    const faces = [];
+    for (let i = 0; i < seg; i++) {
+      const u = (i / seg) * Math.PI * 2;
+      const cu = Math.cos(u), su = Math.sin(u);
+      for (let j = 0; j < ring; j++) {
+        const v = (j / ring) * Math.PI * 2;
+        const cv = Math.cos(v), sv = Math.sin(v);
+        verts.push([
+          (R + r * cv) * cu,
+          r * sv,
+          (R + r * cv) * su
+        ]);
+      }
+    }
+    const idx = (i, j) => (i % seg) * ring + (j % ring);
+    for (let i = 0; i < seg; i++) {
+      for (let j = 0; j < ring; j++) {
+        faces.push([ idx(i,j), idx(i+1,j), idx(i+1,j+1), idx(i,j+1) ]);
+      }
+    }
+    return { verts, edges: edgesFromFaces(faces), faces };
+  }
+
+  // ---- geometry helpers ----
+  function normalizeScale(verts, target) {
+    // scale so the max radius == target
+    let m = 0;
+    for (const v of verts) m = Math.max(m, Math.hypot(v[0], v[1], v[2]));
+    const s = target / m;
+    return verts.map(v => [v[0]*s, v[1]*s, v[2]*s]);
+  }
+
+  function edgesFromFaces(faces) {
+    const set = new Set();
+    const edges = [];
+    for (const f of faces) {
+      for (let k = 0; k < f.length; k++) {
+        let a = f[k], b = f[(k + 1) % f.length];
+        const key = a < b ? a + '_' + b : b + '_' + a;
+        if (!set.has(key)) { set.add(key); edges.push([a, b]); }
+      }
+    }
+    return edges;
+  }
+
+  function buildShape(name) {
+    switch (name) {
+      case 'torus': return torus(1.25, 0.5, 40, 20);
+      case 'octahedron': return octahedron();
+      case 'cube': return cube();
+      case 'icosahedron':
+      default: return icosahedron();
+    }
+  }
+
+  let geom = buildShape(state.shapeName);
+
+  // ============================================================
+  //  Math: rotation + perspective projection (by hand)
+  // ============================================================
+
+  function rotate(p, ax, ay) {
+    // rotate about X then Y
+    const cx = Math.cos(ax), sx = Math.sin(ax);
+    const cy = Math.cos(ay), sy = Math.sin(ay);
+    let x = p[0], y = p[1], z = p[2];
+    // X axis
+    let y1 = y * cx - z * sx;
+    let z1 = y * sx + z * cx;
+    // Y axis
+    let x2 = x * cy + z1 * sy;
+    let z2 = -x * sy + z1 * cy;
+    return [x2, y1, z2];
+  }
+
+  // ============================================================
+  //  Render
+  // ============================================================
+
+  function resize() {
+    const dpr = window.devicePixelRatio || 1;
+    state.DPR = dpr;
+    state.W = window.innerWidth;
+    state.H = window.innerHeight;
+    canvas.width = Math.floor(state.W * dpr);
+    canvas.height = Math.floor(state.H * dpr);
+    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+  }
+  window.addEventListener('resize', resize);
+  resize();
+
+  function project(p3) {
+    // perspective divide. Camera looks down -Z; move object away by dist.
+    const fov = state.fovDeg * Math.PI / 180;
+    const f = 1 / Math.tan(fov / 2);
+    const zc = p3[2] - state.dist;          // camera space z (negative in front)
+    const denom = -zc;                       // positive distance in front of camera
+    const minZ = 0.05;
+    const d = Math.max(denom, minZ);
+    const scale = (Math.min(state.W, state.H) / 2) * f;
+    return {
+      x: state.W / 2 + (p3[0] * f / d) * scale / f * 1, // simplify below
+      y: 0, z: zc, d: d
+    };
+  }
+
+  // simpler, correct projection
+  function projectPoint(p3) {
+    const fov = state.fovDeg * Math.PI / 180;
+    const f = 1 / Math.tan(fov / 2);
+    const d = Math.max(state.dist - p3[2], 0.05); // distance in front of camera
+    const s = (Math.min(state.W, state.H) / 2) * f;
+    return {
+      x: state.W / 2 + (p3[0] / d) * s,
+      y: state.H / 2 - (p3[1] / d) * s,
+      depth: d
+    };
+  }
+
+  const LIGHT = (function () {
+    const l = [0.4, 0.7, 1.0];
+    const m = Math.hypot(l[0], l[1], l[2]);
+    return [l[0]/m, l[1]/m, l[2]/m];
+  })();
+
+  function frame(dt) {
+    state.autoRot += state.spin * dt;
+
+    const ax = state.rotX;
+    const ay = state.rotY + state.autoRot;
+
+    // transform all vertices once
+    const tv = geom.verts.map(v => rotate(v, ax, ay));
+    const pv = tv.map(projectPoint);
+
+    ctx.clearRect(0, 0, state.W, state.H);
+
+    if (state.mode === 'solid') {
+      drawSolid(tv, pv);
+    } else {
+      drawWire(tv, pv);
+    }
+  }
+
+  function drawWire(tv, pv) {
+    // subtle vertex glow + edges
+    ctx.lineWidth = 1.1;
+    ctx.lineJoin = 'round';
+    // draw edges with depth-based shading
+    for (const e of geom.edges) {
+      const a = pv[e[0]], b = pv[e[1]];
+      const za = tv[e[0]][2], zb = tv[e[1]][2];
+      const zm = (za + zb) / 2;
+      // map z (roughly -2..2) to brightness
+      const t = clamp((zm + 2) / 4, 0, 1);
+      const alpha = 0.25 + 0.75 * t;
+      const g = Math.round(120 + 135 * t);
+      ctx.strokeStyle = 'rgba(' + Math.round(90 + 70*t) + ',' + g + ',255,' + alpha.toFixed(3) + ')';
+      ctx.beginPath();
+      ctx.moveTo(a.x, a.y);
+      ctx.lineTo(b.x, b.y);
+      ctx.stroke();
+    }
+    // vertex dots
+    for (let i = 0; i < pv.length; i++) {
+      const t = clamp((tv[i][2] + 2) / 4, 0, 1);
+      ctx.fillStyle = 'rgba(180,235,255,' + (0.3 + 0.6*t).toFixed(3) + ')';
+      ctx.beginPath();
+      ctx.arc(pv[i].x, pv[i].y, 1.4 + 1.6 * t, 0, Math.PI * 2);
+      ctx.fill();
+    }
+  }
+
+  function drawSolid(tv, pv) {
+    // painter's algorithm: sort faces back-to-front by avg camera depth
+    const list = [];
+    for (const f of geom.faces) {
+      // face normal (from first 3 verts, in rotated/world space)
+      const p0 = tv[f[0]], p1 = tv[f[1]], p2 = tv[f[2]];
+      const ux = p1[0]-p0[0], uy = p1[1]-p0[1], uz = p1[2]-p0[2];
+      const vx = p2[0]-p0[0], vy = p2[1]-p0[1], vz = p2[2]-p0[2];
+      let nx = uy*vz - uz*vy;
+      let ny = uz*vx - ux*vz;
+      let nz = ux*vy - uy*vx;
+      const nm = Math.hypot(nx, ny, nz) || 1;
+      nx/=nm; ny/=nm; nz/=nm;
+
+      // face centroid
+      let cx=0, cy=0, cz=0;
+      for (const i of f) { cx+=tv[i][0]; cy+=tv[i][1]; cz+=tv[i][2]; }
+      const n = f.length; cx/=n; cy/=n; cz/=n;
+
+      // view direction from face to camera (camera at (0,0,dist))
+      const vdx = 0 - cx, vdy = 0 - cy, vdz = state.dist - cz;
+      const vdm = Math.hypot(vdx, vdy, vdz) || 1;
+      const facing = (nx*vdx + ny*vdy + nz*vdz) / vdm;
+      if (facing <= 0) continue; // backface cull
+
+      // lambert
+      let lam = nx*LIGHT[0] + ny*LIGHT[1] + nz*LIGHT[2];
+      lam = Math.max(0, lam);
+      const shade = 0.18 + 0.82 * lam;
+
+      list.push({ f, depth: state.dist - cz, shade });
+    }
+    list.sort((a, b) => b.depth - a.depth); // far first
+
+    for (const item of list) {
+      const f = item.f;
+      ctx.beginPath();
+      const p0 = pv[f[0]];
+      ctx.moveTo(p0.x, p0.y);
+      for (let k = 1; k < f.length; k++) ctx.lineTo(pv[f[k]].x, pv[f[k]].y);
+      ctx.closePath();
+
+      const s = item.shade;
+      const r = Math.round(40 + 70 * s);
+      const g = Math.round(90 + 130 * s);
+      const b = Math.round(150 + 105 * s);
+      ctx.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')';
+      ctx.fill();
+      ctx.lineWidth = 1;
+      ctx.strokeStyle = 'rgba(190,225,255,' + (0.35 + 0.4*s).toFixed(3) + ')';
+      ctx.stroke();
+    }
+  }
+
+  function clamp(v, a, b) { return v < a ? a : v > b ? b : v; }
+
+  // ============================================================
+  //  Animation loop
+  // ============================================================
+  let last = performance.now();
+  function loop(now) {
+    const dt = Math.min((now - last) / 1000, 0.05);
+    last = now;
+    frame(dt);
+    requestAnimationFrame(loop);
+  }
+  requestAnimationFrame(loop);
+
+  // ============================================================
+  //  Interaction
+  // ============================================================
+  function pointerDown(x, y) {
+    state.dragging = true;
+    state.lastX = x; state.lastY = y;
+    canvas.classList.add('dragging');
+  }
+  function pointerMove(x, y) {
+    if (!state.dragging) return;
+    const dx = x - state.lastX;
+    const dy = y - state.lastY;
+    state.lastX = x; state.lastY = y;
+    state.rotY += dx * 0.008;
+    state.rotX += dy * 0.008;
+    state.rotX = clamp(state.rotX, -Math.PI/2 + 0.05, Math.PI/2 - 0.05);
+  }
+  function pointerUp() {
+    state.dragging = false;
+    canvas.classList.remove('dragging');
+  }
+
+  canvas.addEventListener('mousedown', e => pointerDown(e.clientX, e.clientY));
+  window.addEventListener('mousemove', e => pointerMove(e.clientX, e.clientY));
+  window.addEventListener('mouseup', pointerUp);
+
+  canvas.addEventListener('touchstart', e => {
+    if (e.touches.length) { pointerDown(e.touches[0].clientX, e.touches[0].clientY); }
+  }, { passive: true });
+  canvas.addEventListener('touchmove', e => {
+    if (e.touches.length) { pointerMove(e.touches[0].clientX, e.touches[0].clientY); e.preventDefault(); }
+  }, { passive: false });
+  canvas.addEventListener('touchend', pointerUp);
+
+  canvas.addEventListener('wheel', e => {
+    e.preventDefault();
+    state.dist = clamp(state.dist + (e.deltaY > 0 ? 0.3 : -0.3), 2.2, 12);
+  }, { passive: false });
+
+  canvas.addEventListener('dblclick', () => {
+    state.rotX = -0.45; state.rotY = 0.6; state.autoRot = 0; state.dist = 4.2;
+  });
+
+  // ---- UI wiring ----
+  const shapeSel = document.getElementById('shape');
+  shapeSel.addEventListener('change', () => {
+    state.shapeName = shapeSel.value;
+    geom = buildShape(state.shapeName);
+  });
+
+  document.getElementById('renderSeg').addEventListener('click', e => {
+    const btn = e.target.closest('button');
+    if (!btn) return;
+    state.mode = btn.dataset.mode;
+    for (const b of e.currentTarget.querySelectorAll('button')) b.classList.remove('active');
+    btn.classList.add('active');
+  });
+
+  const spin = document.getElementById('spin');
+  const spinVal = document.getElementById('spinVal');
+  spin.addEventListener('input', () => {
+    state.spin = parseFloat(spin.value);
+    spinVal.textContent = state.spin.toFixed(2);
+  });
+
+  const fov = document.getElementById('fov');
+  const fovVal = document.getElementById('fovVal');
+  fov.addEventListener('input', () => {
+    state.fovDeg = parseFloat(fov.value);
+    fovVal.textContent = state.fovDeg + '°';
+  });
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e840f1972643/claude-code.png b/data/artifacts/e840f1972643/claude-code.png
new file mode 100644
index 0000000..b864647
Binary files /dev/null and b/data/artifacts/e840f1972643/claude-code.png differ
diff --git a/data/artifacts/e840f1972643/gemma3-12b.html b/data/artifacts/e840f1972643/gemma3-12b.html
new file mode 100644
index 0000000..0fb43bc
--- /dev/null
+++ b/data/artifacts/e840f1972643/gemma3-12b.html
@@ -0,0 +1,239 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>3D Wireframe</title>
+<style>
+body { margin: 0; overflow: hidden; }
+canvas { display: block; }
+#controls { position: absolute; top: 10px; left: 10px; padding: 5px; background-color: rgba(255, 255, 255, 0.7); border-radius: 5px; }
+
+</style>
+</head>
+<body>
+<div id="controls">
+  <select id="shapeSelect">
+    <option value="torus">Torus</option>
+    <option value="icosahedron">Icosahedron</option>
+  </select>
+  <button id="wireframeToggle">Wireframe</button>
+</div>
+<canvas id="sceneCanvas"></canvas>
+
+<script>
+const canvas = document.getElementById('sceneCanvas');
+const ctx = canvas.getContext('2d');
+const shapeSelect = document.getElementById('shapeSelect');
+const wireframeToggle = document.getElementById('wireframeToggle');
+
+let width = window.innerWidth;
+let height = window.innerHeight;
+
+canvas.width = width;
+canvas.height = height;
+
+
+let cameraDistance = 3;
+let rotationX = 0;
+let rotationY = 0;
+let mouseX = 0;
+let mouseY = 0;
+let lastMouseX = 0;
+let lastMouseY = 0;
+
+let shapeType = 'torus';
+let wireframeMode = true;
+
+
+function torus(radiusMajor, radiusMinor) {
+    const vertices = [];
+    const indices = [];
+    for (let u = 0; u < 36; u++) {
+        for (let v = 0; v < 18; v++) {
+            const angleU = u * Math.PI / 18;
+            const angleV = v * Math.PI / 9;
+
+            const x = (radiusMajor + radiusMinor * Math.cos(angleV)) * Math.cos(angleU);
+            const y = radiusMinor * Math.sin(angleV);
+            const z = (radiusMajor + radiusMinor * Math.cos(angleV)) * Math.sin(angleU);
+
+            vertices.push(x, y, z);
+            indices.push(u * 18 + v);
+        }
+    }
+    return { vertices: vertices, indices: indices };
+}
+
+
+function icosahedron() {
+   const vertices = [
+     [-1, 0, 0], [0, -1, 0], [0, 0, -1], [1, 0, 0], [0, 1, 0]
+   ];
+
+  const faces = [
+    [0, 2, 1], [2, 3, 1], [3, 4, 1], [4, 0, 1],
+    [0, 4, 2], [4, 3, 2], [3, 0, 2], [0, 1, 2],
+    [1, 3, 4], [3, 2, 4], [2, 1, 4]
+  ];
+
+  const indexedVertices = [];
+
+  for (let i = 0; i < faces.length; i++) {
+      indexedVertices.push(vertices[faces[i][0]]);
+      indexedVertices.push(vertices[faces[i][1]]);
+      indexedVertices.push(vertices[faces[i][2]]);
+  }
+
+  return { vertices: indexedVertices };
+}
+
+
+let shapeData;
+if (shapeType === 'torus') {
+    shapeData = torus(1, 0.5);
+} else {
+    shapeData = icosahedron();
+}
+
+
+
+
+function project(x, y, z) {
+  const perspective = 500 / (cameraDistance + Math.abs(z)); // Adjust for perspective
+
+  return [x * perspective, y * perspective];
+}
+
+
+let points = [];
+function drawScene() {
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+    points = shapeData.vertices.map((v, i) => {
+        const x = v[0];
+        const y = v[1];
+        const z = v[2];
+
+         // Rotate around X axis
+        let rotatedX = x;
+        let rotatedY = y * Math.cos(rotationX) - z * Math.sin(rotationX);
+        let rotatedZ = y * Math.sin(rotationX) + z * Math.cos(rotationX);
+
+       // Rotate around Y axis
+        const finalX = rotatedX * Math.cos(rotationY) - rotatedZ * Math.sin(rotationY);
+        const finalY = rotatedY;
+        const finalZ = rotatedX * Math.sin(rotationY) + rotatedZ * Math.cos(rotationY);
+
+        const projected = project(finalX, finalY, finalZ);
+        return { x: projected[0], y: projected[1], originalZ: z };
+    });
+
+
+    if (wireframeMode) {
+      ctx.strokeStyle = 'rgba(255, 255, 255, 1)';
+       ctx.lineWidth = 1;
+
+        for (let i = 0; i < shapeData.indices.length; i += 3) {
+            const p1 = points[shapeData.indices[i]];
+            const p2 = points[shapeData.indices[i + 1]];
+            const p3 = points[shapeData.indices[i + 2]];
+
+            ctx.beginPath();
+            ctx.moveTo(p1.x, p1.y);
+            ctx.lineTo(p2.x, p2.y);
+            ctx.stroke();
+
+            ctx.beginPath();
+            ctx.moveTo(p2.x, p2.y);
+            ctx.lineTo(p3.x, p3.y);
+            ctx.stroke();
+
+            ctx.beginPath();
+            ctx.moveTo(p3.x, p3.y);
+            ctx.lineTo(p1.x, p1.y);
+            ctx.stroke();
+
+        }
+    } else {
+      ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
+
+      for (let i = 0; i < shapeData.indices.length; i += 3) {
+          const p1 = points[shapeData.indices[i]];
+          const p2 = points[shapeData.indices[i + 1]];
+          const p3 = points[shapeData.indices[i + 2]];
+
+           ctx.beginPath();
+            ctx.moveTo(p1.x, p1.y);
+            ctx.lineTo(p2.x, p2.y);
+            ctx.lineTo(p3.x, p3.y);
+            ctx.closePath();
+            ctx.fill();
+
+      }
+    }
+
+
+}
+
+
+
+function handleMouseDown(event) {
+  document.addEventListener('mousemove', handleMouseMove);
+  document.addEventListener('mouseup', handleMouseUp);
+}
+
+function handleMouseMove(event) {
+  const dx = event.clientX - lastMouseX;
+  const dy = event.clientY - lastMouseY;
+
+  rotationY += dx * 0.01;
+  rotationX -= dy * 0.01;
+
+
+  lastMouseX = event.clientX;
+  lastMouseY = event.clientY;
+
+  drawScene();
+}
+
+function handleMouseUp(event) {
+   document.removeEventListener('mousemove', handleMouseMove);
+    document.removeEventListener('mouseup', handleMouseUp);
+}
+
+
+
+shapeSelect.addEventListener('change', (e) => {
+  shapeType = e.target.value;
+  if (shapeType === 'torus') {
+    shapeData = torus(1, 0.5);
+  } else {
+    shapeData = icosahedron();
+  }
+
+   drawScene();
+});
+
+
+wireframeToggle.addEventListener('click', () => {
+  wireframeMode = !wireframeMode;
+  drawScene();
+});
+
+
+
+window.addEventListener('resize', () => {
+  width = window.innerWidth;
+  height = window.innerHeight;
+  canvas.width = width;
+  canvas.height = height;
+  drawScene();
+});
+
+canvas.addEventListener("mousedown", handleMouseDown);
+
+
+drawScene(); // Initial draw
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e840f1972643/gemma3-12b.png b/data/artifacts/e840f1972643/gemma3-12b.png
new file mode 100644
index 0000000..589dcaf
Binary files /dev/null and b/data/artifacts/e840f1972643/gemma3-12b.png differ
diff --git a/data/artifacts/e840f1972643/gpt.html b/data/artifacts/e840f1972643/gpt.html
new file mode 100644
index 0000000..2dc1d04
--- /dev/null
+++ b/data/artifacts/e840f1972643/gpt.html
@@ -0,0 +1,381 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>3D Wireframe Scene</title>
+<style>
+    html, body {
+        margin: 0;
+        padding: 0;
+        overflow: hidden;
+        background: #111;
+        color: #eee;
+        font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+        height: 100%;
+    }
+    #ui {
+        position: fixed;
+        top: 10px;
+        left: 50%;
+        transform: translateX(-50%);
+        display: flex;
+        gap: 10px;
+        align-items: center;
+        background: rgba(0,0,0,0.55);
+        padding: 6px 10px;
+        border-radius: 6px;
+        font-size: 13px;
+        z-index: 10;
+        backdrop-filter: blur(4px);
+    }
+    #ui label {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+        white-space: nowrap;
+    }
+    #ui select, #ui input[type="checkbox"] {
+        cursor: pointer;
+    }
+    #canvas {
+        width: 100vw;
+        height: 100vh;
+        display: block;
+    }
+</style>
+</head>
+<body>
+<div id="ui">
+    <label>
+        Shape:
+        <select id="shapeSelect">
+            <option value="torus">Torus</option>
+            <option value="icosahedron">Icosahedron</option>
+        </select>
+    </label>
+    <label>
+        <input type="checkbox" id="solidToggle">
+        Solid
+    </label>
+</div>
+<canvas id="canvas"></canvas>
+<script>
+(function() {
+    const canvas = document.getElementById('canvas');
+    const ctx = canvas.getContext('2d');
+
+    function resize() {
+        const dpr = window.devicePixelRatio || 1;
+        const rect = canvas.getBoundingClientRect();
+        canvas.width = rect.width * dpr;
+        canvas.height = rect.height * dpr;
+        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+    }
+    window.addEventListener('resize', resize);
+    resize();
+
+    // Simple vector helpers
+    function vec3(x,y,z){return {x:x,y:y,z:z};}
+    function add(a,b){return vec3(a.x+b.x,a.y+b.y,a.z+b.z);}
+    function sub(a,b){return vec3(a.x-b.x,a.y-b.y,a.z-b.z);}
+    function dot(a,b){return a.x*b.x+a.y*b.y+a.z*b.z;}
+    function cross(a,b){
+        return vec3(
+            a.y*b.z-a.z*b.y,
+            a.z*b.x-a.x*b.z,
+            a.x*b.y-a.y*b.x
+        );
+    }
+    function norm(v){
+        const l = Math.hypot(v.x,v.y,v.z) || 1;
+        return vec3(v.x/l,v.y/l,v.z/l);
+    }
+
+    // Shapes
+    function createTorus(R=1, r=0.4, segMajor=40, segMinor=20) {
+        const vertices = [];
+        const edgesSet = new Set();
+        const faces = [];
+
+        function idx(i,j){
+            return i*segMinor + j;
+        }
+
+        for (let i=0;i<segMajor;i++) {
+            const u = i/segMajor * Math.PI*2;
+            const cu = Math.cos(u), su = Math.sin(u);
+            for (let j=0;j<segMinor;j++) {
+                const v = j/segMinor * Math.PI*2;
+                const cv = Math.cos(v), sv = Math.sin(v);
+                const x = (R + r*cv)*cu;
+                const y = (R + r*cv)*su;
+                const z = r*sv;
+                vertices.push(vec3(x,y,z));
+            }
+        }
+
+        for (let i=0;i<segMajor;i++) {
+            for (let j=0;j<segMinor;j++) {
+                const i1 = (i+1)%segMajor;
+                const j1 = (j+1)%segMinor;
+                const a = idx(i,j);
+                const b = idx(i1,j);
+                const c = idx(i1,j1);
+                const d = idx(i,j1);
+
+                // edges
+                const ePairs = [[a,b],[a,d],[b,c],[c,d]];
+                for (const [u,v] of ePairs) {
+                    const key = u<v?u+"-"+v:v+"-"+u;
+                    if (!edgesSet.has(key)) edgesSet.add(key);
+                }
+
+                // faces as quads (a,b,c,d)
+                faces.push([a,b,c,d]);
+            }
+        }
+
+        const edges = Array.from(edgesSet).map(k=>{
+            const [a,b] = k.split('-').map(Number);
+            return [a,b];
+        });
+
+        return {vertices, edges, faces};
+    }
+
+    function createIcosahedron() {
+        const t = (1 + Math.sqrt(5))/2;
+        const s = 1 / Math.sqrt(1 + t*t);
+        const ts = t * s;
+
+        const verts = [
+            vec3(-s, ts, 0), vec3(s, ts, 0), vec3(-s,-ts,0), vec3(s,-ts,0),
+            vec3(0,-s, ts), vec3(0,s, ts), vec3(0,-s,-ts), vec3(0,s,-ts),
+            vec3(ts,0,-s), vec3(ts,0,s), vec3(-ts,0,-s), vec3(-ts,0,s)
+        ];
+
+        const faces = [
+            [0,11,5],[0,5,1],[0,1,7],[0,7,10],[0,10,11],
+            [1,5,9],[5,11,4],[11,10,2],[10,7,6],[7,1,8],
+            [3,9,4],[3,4,2],[3,2,6],[3,6,8],[3,8,9],
+            [4,9,5],[2,4,11],[6,2,10],[8,6,7],[9,8,1]
+        ];
+
+        const edgesSet = new Set();
+        for (const f of faces) {
+            const [a,b,c] = f;
+            const e = [[a,b],[b,c],[c,a]];
+            for (const [u,v] of e) {
+                const key = u<v?u+"-"+v:v+"-"+u;
+                if (!edgesSet.has(key)) edgesSet.add(key);
+            }
+        }
+        const edges = Array.from(edgesSet).map(k=>{
+            const [a,b] = k.split('-').map(Number);
+            return [a,b];
+        });
+
+        return {vertices: verts, edges, faces};
+    }
+
+    // Camera & interaction
+    let angleX = 0.6;
+    let angleY = -0.8;
+    let autoRotate = true;
+
+    let isDragging = false;
+    let lastX = 0, lastY = 0;
+
+    canvas.addEventListener('mousedown', e=>{
+        isDragging = true;
+        autoRotate = false;
+        lastX = e.clientX;
+        lastY = e.clientY;
+    });
+    window.addEventListener('mouseup', ()=>{
+        isDragging = false;
+    });
+    window.addEventListener('mousemove', e=>{
+        if (!isDragging) return;
+        const dx = e.clientX - lastX;
+        const dy = e.clientY - lastY;
+        lastX = e.clientX;
+        lastY = e.clientY;
+        const speed = 0.005;
+        angleY += dx * speed;
+        angleX += dy * speed;
+        const limit = Math.PI/2 - 0.05;
+        angleX = Math.max(-limit, Math.min(limit, angleX));
+    });
+
+    // Touch
+    canvas.addEventListener('touchstart', e=>{
+        if (e.touches.length!==1) return;
+        isDragging = true;
+        autoRotate = false;
+        lastX = e.touches[0].clientX;
+        lastY = e.touches[0].clientY;
+    }, {passive:true});
+    window.addEventListener('touchend', ()=>{
+        isDragging = false;
+    }, {passive:true});
+    window.addEventListener('touchmove', e=>{
+        if (!isDragging || e.touches.length!==1) return;
+        const t = e.touches[0];
+        const dx = t.clientX - lastX;
+        const dy = t.clientY - lastY;
+        lastX = t.clientX;
+        lastY = t.clientY;
+        const speed = 0.005;
+        angleY += dx * speed;
+        angleX += dy * speed;
+        const limit = Math.PI/2 - 0.05;
+        angleX = Math.max(-limit, Math.min(limit, angleX));
+    }, {passive:true});
+
+    // Projection
+    function rotateY(v,a){
+        const c = Math.cos(a), s = Math.sin(a);
+        return vec3(
+            v.x*c + v.z*s,
+            v.y,
+            -v.x*s + v.z*c
+        );
+    }
+    function rotateX(v,a){
+        const c = Math.cos(a), s = Math.sin(a);
+        return vec3(
+            v.x,
+            v.y*c - v.z*s,
+            v.y*s + v.z*c
+        );
+    }
+
+    function project(v, w, h, fov, zOffset) {
+        const z = v.z + zOffset;
+        const invz = 1 / (z || 1e-6);
+        const scale = fov * invz;
+        return {
+            x: w*0.5 + v.x*scale,
+            y: h*0.5 - v.y*scale,
+            z
+        };
+    }
+
+    // Scene
+    const shapeSelect = document.getElementById('shapeSelect');
+    const solidToggle = document.getElementById('solidToggle');
+
+    let currentShapeName = 'torus';
+    let shapes = {
+        torus: createTorus(1.0,0.4,42,22),
+        icosahedron: createIcosahedron()
+    };
+
+    shapeSelect.addEventListener('change', ()=>{
+        currentShapeName = shapeSelect.value;
+    });
+
+    let tLast = performance.now();
+    let spin = 0;
+
+    function render() {
+        const tNow = performance.now();
+        const dt = (tNow - tLast)/1000;
+        tLast = tNow;
+        if (autoRotate) spin += dt*0.8;
+
+        const w = canvas.clientWidth;
+        const h = canvas.clientHeight;
+        ctx.clearRect(0,0,w,h);
+
+        const {vertices, edges, faces} = shapes[currentShapeName];
+
+        const rotated = [];
+        const worldRotY = spin;
+        // Model rotation about its own Y
+        for (let i=0;i<vertices.length;i++) {
+            let v = vertices[i];
+            v = rotateY(v, worldRotY);
+            // orbit rotation by camera angles (inverse: rotate world)
+            v = rotateX(v, angleX);
+            v = rotateY(v, angleY);
+            rotated.push(v);
+        }
+
+        const zOffset = 4;
+        const fov = 500;
+        const projected = rotated.map(v=>project(v,w,h,fov,zOffset));
+
+        const wireColor = '#00e0ff';
+        const solid = solidToggle.checked;
+
+        if (solid && faces && faces.length>0) {
+            // Back-face culling & simple painter's algorithm
+            const faceData = [];
+            for (let i=0;i<faces.length;i++) {
+                const f = faces[i];
+                if (f.length<3) continue;
+                const v0 = rotated[f[0]];
+                const v1 = rotated[f[1]];
+                const v2 = rotated[f[2]];
+                const n = cross(sub(v1,v0), sub(v2,v0));
+                // View direction ~ (0,0,1) after rotation+camera
+                if (n.z >= 0) continue; // back face
+                let depth = 0;
+                for (const idx of f) depth += rotated[idx].z;
+                depth /= f.length;
+                faceData.push({face:f, depth});
+            }
+            faceData.sort((a,b)=>b.depth - a.depth);
+
+            ctx.lineWidth = 1;
+            for (const {face} of faceData) {
+                ctx.beginPath();
+                const p0 = projected[face[0]];
+                ctx.moveTo(p0.x,p0.y);
+                for (let i=1;i<face.length;i++) {
+                    const p = projected[face[i]];
+                    ctx.lineTo(p.x,p.y);
+                }
+                ctx.closePath();
+
+                // simple Lambert from normal.z
+                const v0 = rotated[face[0]];
+                const v1 = rotated[face[1]];
+                const v2 = rotated[face[2]];
+                const n = norm(cross(sub(v1,v0), sub(v2,v0)));
+                const lightDir = norm(vec3(0.4,0.7,1.0));
+                let l = dot(n,lightDir);
+                l = Math.max(0.1, l);
+                const base = {r:0,g:200,b:255};
+                const r = Math.floor(base.r*l);
+                const g = Math.floor(base.g*l);
+                const b = Math.floor(base.b*l);
+                ctx.fillStyle = `rgba(${r},${g},${b},0.9)`;
+                ctx.strokeStyle = 'rgba(0,0,0,0.3)';
+                ctx.fill();
+                ctx.stroke();
+            }
+        }
+
+        // Wireframe overlay
+        ctx.beginPath();
+        for (const [a,b] of edges) {
+            const pa = projected[a], pb = projected[b];
+            if (pa.z<=0 || pb.z<=0) continue;
+            ctx.moveTo(pa.x,pa.y);
+            ctx.lineTo(pb.x,pb.y);
+        }
+        ctx.strokeStyle = wireColor;
+        ctx.lineWidth = 1;
+        ctx.stroke();
+
+        requestAnimationFrame(render);
+    }
+
+    render();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e840f1972643/gpt.png b/data/artifacts/e840f1972643/gpt.png
new file mode 100644
index 0000000..85a04eb
Binary files /dev/null and b/data/artifacts/e840f1972643/gpt.png differ
diff --git a/data/artifacts/e840f1972643/grok.html b/data/artifacts/e840f1972643/grok.html
new file mode 100644
index 0000000..19b909b
--- /dev/null
+++ b/data/artifacts/e840f1972643/grok.html
@@ -0,0 +1,476 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>3D Wireframe Scene</title>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    background: #0a0a12;
+    overflow: hidden;
+    font-family: system-ui, -apple-system, sans-serif;
+    color: #c8d0e0;
+    height: 100vh;
+    width: 100vw;
+  }
+  canvas {
+    display: block;
+    width: 100vw;
+    height: 100vh;
+    cursor: grab;
+  }
+  canvas:active { cursor: grabbing; }
+  #ui {
+    position: fixed;
+    top: 16px;
+    left: 16px;
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+    z-index: 10;
+    background: rgba(12, 14, 24, 0.85);
+    border: 1px solid rgba(100, 140, 255, 0.25);
+    border-radius: 10px;
+    padding: 14px 16px;
+    backdrop-filter: blur(8px);
+    box-shadow: 0 4px 24px rgba(0,0,0,0.4);
+  }
+  #ui h1 {
+    font-size: 13px;
+    font-weight: 600;
+    letter-spacing: 0.08em;
+    text-transform: uppercase;
+    color: #7a9cff;
+    margin-bottom: 4px;
+  }
+  label {
+    font-size: 12px;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    color: #a0a8b8;
+  }
+  select, button {
+    background: #1a1e2e;
+    color: #d0d8e8;
+    border: 1px solid rgba(100, 140, 255, 0.3);
+    border-radius: 6px;
+    padding: 6px 10px;
+    font-size: 12px;
+    cursor: pointer;
+    outline: none;
+  }
+  select:hover, button:hover {
+    border-color: rgba(100, 140, 255, 0.6);
+    background: #222840;
+  }
+  select { flex: 1; min-width: 120px; }
+  .row { display: flex; align-items: center; gap: 8px; }
+  #hint {
+    position: fixed;
+    bottom: 14px;
+    right: 16px;
+    font-size: 11px;
+    color: rgba(160, 170, 190, 0.5);
+    pointer-events: none;
+  }
+</style>
+</head>
+<body>
+<div id="ui">
+  <h1>3D Wireframe</h1>
+  <div class="row">
+    <label>Shape
+      <select id="shape">
+        <option value="torus">Torus</option>
+        <option value="icosahedron">Icosahedron</option>
+        <option value="cube">Cube</option>
+        <option value="octahedron">Octahedron</option>
+      </select>
+    </label>
+  </div>
+  <div class="row">
+    <button id="modeBtn">Mode: Wireframe</button>
+  </div>
+</div>
+<div id="hint">drag to orbit · scroll to zoom</div>
+<canvas id="c"></canvas>
+<script>
+(function () {
+  const canvas = document.getElementById('c');
+  const ctx = canvas.getContext('2d');
+  let W, H, CX, CY;
+
+  function resize() {
+    W = canvas.width = window.innerWidth * devicePixelRatio;
+    H = canvas.height = window.innerHeight * devicePixelRatio;
+    CX = W / 2;
+    CY = H / 2;
+    ctx.setTransform(1, 0, 0, 1, 0, 0);
+  }
+  window.addEventListener('resize', resize);
+  resize();
+
+  // ─── Vec / Mat helpers ───────────────────────────────────
+  function v3(x, y, z) { return [x, y, z]; }
+  function add(a, b) { return [a[0]+b[0], a[1]+b[1], a[2]+b[2]]; }
+  function sub(a, b) { return [a[0]-b[0], a[1]-b[1], a[2]-b[2]]; }
+  function scale(a, s) { return [a[0]*s, a[1]*s, a[2]*s]; }
+  function dot(a, b) { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }
+  function cross(a, b) {
+    return [a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]];
+  }
+  function norm(a) {
+    const l = Math.sqrt(dot(a, a)) || 1;
+    return scale(a, 1 / l);
+  }
+  function len(a) { return Math.sqrt(dot(a, a)); }
+
+  // Rotate point around X then Y (camera orbit applied inversely to model)
+  function rotX(p, a) {
+    const c = Math.cos(a), s = Math.sin(a);
+    return [p[0], p[1]*c - p[2]*s, p[1]*s + p[2]*c];
+  }
+  function rotY(p, a) {
+    const c = Math.cos(a), s = Math.sin(a);
+    return [p[0]*c + p[2]*s, p[1], -p[0]*s + p[2]*c];
+  }
+  function rotZ(p, a) {
+    const c = Math.cos(a), s = Math.sin(a);
+    return [p[0]*c - p[1]*s, p[0]*s + p[1]*c, p[2]];
+  }
+
+  // Perspective projection
+  const FOV = 500;
+  function project(p) {
+    const z = p[2] + camDist;
+    if (z <= 0.1) return null;
+    const f = FOV * devicePixelRatio / z;
+    return [CX + p[0] * f, CY - p[1] * f, z];
+  }
+
+  // ─── Geometry builders ───────────────────────────────────
+  function buildTorus(R, r, segR, segr) {
+    const verts = [];
+    const edges = [];
+    const faces = [];
+    for (let i = 0; i <= segR; i++) {
+      const u = (i / segR) * Math.PI * 2;
+      for (let j = 0; j <= segr; j++) {
+        const v = (j / segr) * Math.PI * 2;
+        const x = (R + r * Math.cos(v)) * Math.cos(u);
+        const y = (R + r * Math.cos(v)) * Math.sin(u);
+        const z = r * Math.sin(v);
+        verts.push(v3(x, y, z));
+      }
+    }
+    const stride = segr + 1;
+    for (let i = 0; i < segR; i++) {
+      for (let j = 0; j < segr; j++) {
+        const a = i * stride + j;
+        const b = a + 1;
+        const c = (i + 1) * stride + j + 1;
+        const d = (i + 1) * stride + j;
+        edges.push([a, b], [a, d]);
+        if (i === segR - 1) edges.push([d, c]);
+        if (j === segr - 1) edges.push([b, c]);
+        faces.push([a, b, c, d]);
+      }
+    }
+    return { verts, edges, faces };
+  }
+
+  function buildIcosahedron(radius) {
+    const t = (1 + Math.sqrt(5)) / 2;
+    let raw = [
+      [-1, t, 0], [1, t, 0], [-1, -t, 0], [1, -t, 0],
+      [0, -1, t], [0, 1, t], [0, -1, -t], [0, 1, -t],
+      [t, 0, -1], [t, 0, 1], [-t, 0, -1], [-t, 0, 1]
+    ];
+    const verts = raw.map(p => scale(norm(p), radius));
+    const faces = [
+      [0,11,5],[0,5,1],[0,1,7],[0,7,10],[0,10,11],
+      [1,5,9],[5,11,4],[11,10,2],[10,7,6],[7,1,8],
+      [3,9,4],[3,4,2],[3,2,6],[3,6,8],[3,8,9],
+      [4,9,5],[2,4,11],[6,2,10],[8,6,7],[9,8,1]
+    ];
+    const edgeSet = new Set();
+    const edges = [];
+    faces.forEach(f => {
+      for (let i = 0; i < 3; i++) {
+        const a = f[i], b = f[(i + 1) % 3];
+        const key = Math.min(a, b) + ',' + Math.max(a, b);
+        if (!edgeSet.has(key)) {
+          edgeSet.add(key);
+          edges.push([a, b]);
+        }
+      }
+    });
+    return { verts, edges, faces };
+  }
+
+  function buildCube(s) {
+    const h = s / 2;
+    const verts = [
+      [-h,-h,-h],[h,-h,-h],[h,h,-h],[-h,h,-h],
+      [-h,-h,h],[h,-h,h],[h,h,h],[-h,h,h]
+    ];
+    const edges = [
+      [0,1],[1,2],[2,3],[3,0],
+      [4,5],[5,6],[6,7],[7,4],
+      [0,4],[1,5],[2,6],[3,7]
+    ];
+    const faces = [
+      [0,1,2,3],[4,5,6,7],[0,1,5,4],
+      [2,3,7,6],[0,3,7,4],[1,2,6,5]
+    ];
+    return { verts, edges, faces };
+  }
+
+  function buildOctahedron(r) {
+    const verts = [
+      [0,r,0],[r,0,0],[0,0,r],[-r,0,0],[0,0,-r],[0,-r,0]
+    ];
+    const faces = [
+      [0,1,2],[0,2,3],[0,3,4],[0,4,1],
+      [5,2,1],[5,3,2],[5,4,3],[5,1,4]
+    ];
+    const edgeSet = new Set();
+    const edges = [];
+    faces.forEach(f => {
+      for (let i = 0; i < 3; i++) {
+        const a = f[i], b = f[(i + 1) % 3];
+        const key = Math.min(a, b) + ',' + Math.max(a, b);
+        if (!edgeSet.has(key)) {
+          edgeSet.add(key);
+          edges.push([a, b]);
+        }
+      }
+    });
+    return { verts, edges, faces };
+  }
+
+  // ─── State ───────────────────────────────────────────────
+  let mesh = buildTorus(1.2, 0.45, 32, 16);
+  let shapeName = 'torus';
+  let wireframe = true;
+  let camDist = 5;
+  let angleX = 0.4;
+  let angleY = 0.0;
+  let autoRot = 0;
+  let dragging = false;
+  let lastMX = 0, lastMY = 0;
+  let velX = 0, velY = 0;
+
+  const shapeSelect = document.getElementById('shape');
+  const modeBtn = document.getElementById('modeBtn');
+
+  shapeSelect.addEventListener('change', () => {
+    shapeName = shapeSelect.value;
+    if (shapeName === 'torus') mesh = buildTorus(1.2, 0.45, 32, 16);
+    else if (shapeName === 'icosahedron') mesh = buildIcosahedron(1.4);
+    else if (shapeName === 'cube') mesh = buildCube(1.8);
+    else mesh = buildOctahedron(1.5);
+  });
+
+  modeBtn.addEventListener('click', () => {
+    wireframe = !wireframe;
+    modeBtn.textContent = wireframe ? 'Mode: Wireframe' : 'Mode: Solid';
+  });
+
+  canvas.addEventListener('pointerdown', e => {
+    dragging = true;
+    lastMX = e.clientX;
+    lastMY = e.clientY;
+    canvas.setPointerCapture(e.pointerId);
+  });
+  canvas.addEventListener('pointermove', e => {
+    if (!dragging) return;
+    const dx = e.clientX - lastMX;
+    const dy = e.clientY - lastMY;
+    angleY += dx * 0.008;
+    angleX += dy * 0.008;
+    angleX = Math.max(-Math.PI / 2 + 0.05, Math.min(Math.PI / 2 - 0.05, angleX));
+    velY = dx * 0.008;
+    velX = dy * 0.008;
+    lastMX = e.clientX;
+    lastMY = e.clientY;
+  });
+  canvas.addEventListener('pointerup', () => { dragging = false; });
+  canvas.addEventListener('pointercancel', () => { dragging = false; });
+  canvas.addEventListener('wheel', e => {
+    e.preventDefault();
+    camDist += e.deltaY * 0.005;
+    camDist = Math.max(2.2, Math.min(14, camDist));
+  }, { passive: false });
+
+  // ─── Colors ──────────────────────────────────────────────
+  function depthColor(z, base) {
+    const t = Math.max(0, Math.min(1, 1 - (z - 2) / 10));
+    const r = Math.floor(base[0] * t + 10 * (1 - t));
+    const g = Math.floor(base[1] * t + 12 * (1 - t));
+    const b = Math.floor(base[2] * t + 28 * (1 - t));
+    return 'rgb(' + r + ',' + g + ',' + b + ')';
+  }
+
+  function faceNormal(vs, face) {
+    const a = vs[face[0]], b = vs[face[1]], c = vs[face[2]];
+    return norm(cross(sub(b, a), sub(c, a)));
+  }
+
+  // ─── Render ──────────────────────────────────────────────
+  function frame(t) {
+    requestAnimationFrame(frame);
+
+    if (!dragging) {
+      autoRot += 0.008;
+      angleY += 0.006 + velY;
+      angleX += velX;
+      angleX = Math.max(-Math.PI / 2 + 0.05, Math.min(Math.PI / 2 - 0.05, angleX));
+      velX *= 0.92;
+      velY *= 0.92;
+    }
+
+    // Transform verts
+    const spin = autoRot;
+    const tv = mesh.verts.map(p => {
+      let q = rotZ(p, spin * 0.3);
+      q = rotY(q, spin);
+      q = rotX(q, angleX);
+      q = rotY(q, angleY);
+      return q;
+    });
+
+    // Clear
+    ctx.fillStyle = '#0a0a12';
+    ctx.fillRect(0, 0, W, H);
+
+    // Subtle grid on "floor"
+    drawGrid(angleX, angleY);
+
+    if (wireframe) {
+      // Sort edges by avg depth for painter's-ish draw
+      const edgeDepths = mesh.edges.map((e, i) => {
+        const za = tv[e[0]][2] + camDist;
+        const zb = tv[e[1]][2] + camDist;
+        return { i, z: (za + zb) / 2 };
+      });
+      edgeDepths.sort((a, b) => b.z - a.z);
+
+      ctx.lineWidth = 1.2 * devicePixelRatio;
+      ctx.lineCap = 'round';
+      for (const ed of edgeDepths) {
+        const e = mesh.edges[ed.i];
+        const pa = project(tv[e[0]]);
+        const pb = project(tv[e[1]]);
+        if (!pa || !pb) continue;
+        const zmid = (pa[2] + pb[2]) / 2;
+        ctx.strokeStyle = depthColor(zmid, [120, 180, 255]);
+        ctx.globalAlpha = 0.55 + 0.45 * Math.max(0, Math.min(1, 1 - (zmid - 2) / 12));
+        ctx.beginPath();
+        ctx.moveTo(pa[0], pa[1]);
+        ctx.lineTo(pb[0], pb[1]);
+        ctx.stroke();
+      }
+      ctx.globalAlpha = 1;
+
+      // Vertices as dots
+      for (let i = 0; i < tv.length; i++) {
+        const p = project(tv[i]);
+        if (!p) continue;
+        const r = Math.max(1, 2.2 * devicePixelRatio * (FOV * devicePixelRatio / p[2]) / (FOV * devicePixelRatio / 5));
+        ctx.fillStyle = depthColor(p[2], [180, 220, 255]);
+        ctx.beginPath();
+        ctx.arc(p[0], p[1], r, 0, Math.PI * 2);
+        ctx.fill();
+      }
+    } else {
+      // Solid faces with simple lighting + painter's algorithm
+      const light = norm([0.4, 0.7, 0.5]);
+      const faceData = mesh.faces.map((f, i) => {
+        let cx = 0, cy = 0, cz = 0;
+        for (const idx of f) {
+          cx += tv[idx][0]; cy += tv[idx][1]; cz += tv[idx][2];
+        }
+        const n = f.length;
+        cx /= n; cy /= n; cz /= n;
+        const normal = faceNormal(tv, f);
+        // Back-face cull (camera looks down +Z toward origin from +Z)
+        const viewDir = norm([-cx, -cy, -(cz + camDist)]);
+        const facing = dot(normal, viewDir);
+        return { f, z: cz + camDist, normal, facing, center: [cx, cy, cz] };
+      });
+
+      faceData.sort((a, b) => b.z - a.z);
+
+      for (const fd of faceData) {
+        if (fd.facing <= 0) continue;
+        const pts = [];
+        let ok = true;
+        for (const idx of fd.f) {
+          const p = project(tv[idx]);
+          if (!p) { ok = false; break; }
+          pts.push(p);
+        }
+        if (!ok || pts.length < 3) continue;
+
+        const intensity = Math.max(0.08, Math.min(1, dot(fd.normal, light) * 0.85 + 0.15));
+        const baseCol = shapeName === 'torus' ? [60, 130, 255]
+          : shapeName === 'icosahedron' ? [255, 100, 140]
+          : shapeName === 'cube' ? [80, 220, 160]
+          : [220, 180, 80];
+        const r = Math.floor(baseCol[0] * intensity);
+        const g = Math.floor(baseCol[1] * intensity);
+        const b = Math.floor(baseCol[2] * intensity);
+
+        ctx.beginPath();
+        ctx.moveTo(pts[0][0], pts[0][1]);
+        for (let k = 1; k < pts.length; k++) ctx.lineTo(pts[k][0], pts[k][1]);
+        ctx.closePath();
+        ctx.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')';
+        ctx.fill();
+        ctx.strokeStyle = 'rgba(255,255,255,0.12)';
+        ctx.lineWidth = 0.8 * devicePixelRatio;
+        ctx.stroke();
+      }
+    }
+  }
+
+  function drawGrid(ax, ay) {
+    const gridN = 8;
+    const gridS = 0.7;
+    ctx.lineWidth = 0.7 * devicePixelRatio;
+    for (let i = -gridN; i <= gridN; i++) {
+      const ptsA = [], ptsB = [];
+      for (let j = -gridN; j <= gridN; j++) {
+        let p1 = v3(i * gridS, -1.8, j * gridS);
+        let p2 = v3(j * gridS, -1.8, i * gridS);
+        p1 = rotX(p1, ax); p1 = rotY(p1, ay);
+        p2 = rotX(p2, ax); p2 = rotY(p2, ay);
+        const q1 = project(p1);
+        const q2 = project(p2);
+        if (q1) ptsA.push(q1);
+        if (q2) ptsB.push(q2);
+      }
+      ctx.strokeStyle = 'rgba(60, 80, 140, 0.12)';
+      if (ptsA.length > 1) {
+        ctx.beginPath();
+        ctx.moveTo(ptsA[0][0], ptsA[0][1]);
+        for (let k = 1; k < ptsA.length; k++) ctx.lineTo(ptsA[k][0], ptsA[k][1]);
+        ctx.stroke();
+      }
+      if (ptsB.length > 1) {
+        ctx.beginPath();
+        ctx.moveTo(ptsB[0][0], ptsB[0][1]);
+        for (let k = 1; k < ptsB.length; k++) ctx.lineTo(ptsB[k][0], ptsB[k][1]);
+        ctx.stroke();
+      }
+    }
+  }
+
+  requestAnimationFrame(frame);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e840f1972643/grok.png b/data/artifacts/e840f1972643/grok.png
new file mode 100644
index 0000000..011da9a
Binary files /dev/null and b/data/artifacts/e840f1972643/grok.png differ
diff --git a/data/artifacts/e840f1972643/hermes3-8b.html b/data/artifacts/e840f1972643/hermes3-8b.html
new file mode 100644
index 0000000..5974df1
--- /dev/null
+++ b/data/artifacts/e840f1972643/hermes3-8b.html
@@ -0,0 +1,210 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>3D Wireframe Scene</title>
+    <style>
+        body {
+            margin: 0;
+            overflow: hidden;
+        }
+        canvas {
+            display: block;
+        }
+    </style>
+</head>
+<body>
+    <canvas id="scene" width="800" height="600"></canvas>
+    <label><input type="radio" name="shape" value="torus" checked> Torus</label>
+    <label><input type="radio" name="shape" value="icosahedron"> Icosahedron</label>
+    <label><input type="checkbox" id="solid"> Solid</label>
+    <script>
+        const canvas = document.getElementById('scene');
+        const ctx = canvas.getContext('2d');
+
+        let shape = 'torus';
+        let solidMode = false;
+
+        function drawScene() {
+            // Perspective projection
+            const fov = Math.PI / 3;
+            const distance = 600;
+            ctx.translate(canvas.width / 2, canvas.height / 2);
+            ctx.perspective(500, fov);
+
+            // Clear screen
+            ctx.fillStyle = 'black';
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+            if (shape === 'torus') {
+                drawTorus();
+            } else if (shape === 'icosahedron') {
+                drawIcosahedron();
+            }
+
+            // Rotate
+            requestAnimationFrame(drawScene);
+        }
+
+        function drawTorus() {
+            const slices = 100;
+            const segments = 20;
+
+            for (let i = 0; i <= slices; i++) {
+                for (let j = 0; j <= segments; j++) {
+                    const a = i * Math.PI / slices;
+                    const b = j * Math.PI / segments;
+                    
+                    const x = Math.cos(a) * Math.cos(b);
+                    const y = Math.sin(a);
+                    const z = Math.cos(a) * Math.sin(b);
+
+                    drawCubeFace(x, y, z, 1, 0.5, 1);
+                }
+            }
+        }
+
+        function drawIcosahedron() {
+            // Icosahedron vertices and faces
+            const vertices = [
+                [0, -Math.sqrt(2) / 2, -Math.sqrt(6) / 2],
+                [0, Math.sqrt(2) / 2, -Math.sqrt(6) / 2],
+                [0, -Math.sqrt(2) / 2, Math.sqrt(6) / 2],
+                [0, Math.sqrt(2) / 2, Math.sqrt(6) / 2],
+
+                [-Math.sqrt(3), 0, -1],
+                [Math.sqrt(3), 0, -1],
+                [-Math.sqrt(3), 0, 1],
+                [Math.sqrt(3), 0, 1],
+
+                [Math.sqrt(6) / 3, -Math.sqrt(2) / 3, 0],
+                [Math.sqrt(6) / 3, Math.sqrt(2) / 3, 0],
+                [-Math.sqrt(6) / 3, -Math.sqrt(2) / 3, 0],
+                [-Math.sqrt(6) / 3, Math.sqrt(2) / 3, 0]
+            ];
+
+            const faces = [
+                [7, 4, 1], [3, 2, 7],
+                [5, 8, 4], [0, 1, 5],
+                [6, 9, 8], [4, 5, 6],
+                [1, 7, 3], [2, 3, 6],
+
+                [8, 9, 11], [5, 10, 8],
+                [0, 9, 5], [4, 0, 11],
+
+                [7, 1, 12], [3, 7, 10],
+                [2, 6, 12], [7, 2, 11],
+
+                [4, 5, 13], [8, 4, 9],
+                [0, 4, 13], [11, 0, 14],
+
+                [1, 5, 15], [2, 1, 12],
+                [6, 2, 15], [3, 6, 10],
+
+                [8, 11, 16], [9, 8, 14],
+                [4, 0, 16], [13, 4, 15],
+
+                [5, 1, 17], [7, 5, 12],
+                [0, 2, 17], [6, 0, 14],
+
+                [1, 10, 18], [3, 1, 16],
+                [2, 11, 18], [7, 2, 17]
+            ];
+
+            // Draw faces
+            for (let i = 0; i < faces.length; i += 3) {
+                const a = vertices[faces[i][0]];
+                const b = vertices[faces[i][1]];
+                const c = vertices[faces[i][2]];
+
+                drawCubeFace(a, b, c);
+            }
+        }
+
+        function drawCubeFace(a, b, c, s = 100) {
+            const x1 = a[0] * s;
+            const y1 = a[1] * s;
+            const z1 = a[2] * s;
+
+            const x2 = b[0] * s;
+            const y2 = b[1] * s;
+            const z2 = b[2] * s;
+
+            const x3 = c[0] * s;
+            const y3 = c[1] * s;
+            const z3 = c[2] * s;
+
+            // Front face
+            ctx.beginPath();
+            ctx.moveTo(x1, y1);
+            ctx.lineTo(x2, y2);
+            ctx.lineTo(x3, y3);
+            ctx.lineTo(x1, y1);
+            ctx.closePath();
+
+            // Back face
+            ctx.beginPath();
+            ctx.moveTo(x3, y3);
+            ctx.lineTo(x2, y2);
+            ctx.lineTo(x1, y1);
+            ctx.lineTo(x3, y3);
+            ctx.closePath();
+
+            // Top face
+            ctx.beginPath();
+            ctx.moveTo(x1, y1);
+            ctx.lineTo(x2, y2);
+            ctx.lineTo(x3, y3);
+            ctx.lineTo(x1, y1);
+            ctx.closePath();
+
+            // Bottom face
+            ctx.beginPath();
+            ctx.moveTo(x3, y3);
+            ctx.lineTo(x2, y2);
+            ctx.lineTo(x1, y1);
+            ctx.lineTo(x3, y3);
+            ctx.closePath();
+
+            // Right face
+            ctx.beginPath();
+            ctx.moveTo(x3, y3);
+            ctx.lineTo(x3, y2);
+            ctx.lineTo(x2, y2);
+            ctx.lineTo(x2, y3);
+            ctx.lineTo(x3, y3);
+            ctx.closePath();
+
+            // Left face
+            ctx.beginPath();
+            ctx.moveTo(x1, y1);
+            ctx.lineTo(x1, y2);
+            ctx.lineTo(x2, y2);
+            ctx.lineTo(x2, y1);
+            ctx.lineTo(x1, y1);
+            ctx.closePath();
+        }
+
+        canvas.addEventListener('mousemove', (e) => {
+            const rect = e.target.getBoundingClientRect();
+            const x = e.clientX - rect.left;
+            const y = e.clientY - rect.top;
+
+            if (solidMode) {
+                return;
+            }
+
+            // Orbit camera
+            const distToCenter = Math.sqrt(Math.pow(400, 2) + Math.pow(y - canvas.height / 2, 2));
+            const angle = x / canvas.width * 360;
+            const targetX = (distToCenter * Math.cos(angle * Math.PI / 180)) + 400;
+            const targetY = (y - canvas.height / 2) * 0.5;
+
+            ctx.translate(-targetX, -targetY);
+        });
+
+        drawScene();
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e840f1972643/hermes3-8b.png b/data/artifacts/e840f1972643/hermes3-8b.png
new file mode 100644
index 0000000..16a103c
Binary files /dev/null and b/data/artifacts/e840f1972643/hermes3-8b.png differ
diff --git a/data/artifacts/e840f1972643/hf-qwen-coder-32b.html b/data/artifacts/e840f1972643/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..48f7b0c
--- /dev/null
+++ b/data/artifacts/e840f1972643/hf-qwen-coder-32b.html
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>3D Wireframe Scene</title>
+<style>
+body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #000; color: #fff; }
+canvas { border: 1px solid #fff; }
+.controls { position: absolute; top: 10px; left: 10px; }
+button { margin-right: 5px; cursor: pointer; }
+</style>
+</head>
+<body>
+<div class="controls">
+<button onclick="toggleShape()">Toggle Shape</button>
+<button onclick="toggleFill()">Toggle Fill</button>
+</div>
+<canvas id="c"></canvas>
+<script>
+const canvas = document.getElementById('c');
+const ctx = canvas.getContext('2d');
+let width, height;
+let angleX = 0, angleY = 0, dragStartX, dragStartY;
+let isDragging = false;
+let fillMode = false;
+let shapeType = 'torus';
+
+function resizeCanvas() {
+    width = canvas.width = window.innerWidth;
+    height = canvas.height = window.innerHeight;
+}
+window.addEventListener('resize', resizeCanvas);
+resizeCanvas();
+
+const PI2 = Math.PI * 2;
+
+function projectVertex(x, y, z) {
+    const focalLength = 200;
+    const scale = focalLength / (focalLength + z);
+    return {
+        x: width / 2 + x * scale,
+        y: height / 2 - y * scale
+    };
+}
+
+function rotateX(x, y, z, angle) {
+    const s = Math.sin(angle), c = Math.cos(angle);
+    return { x: x, y: y * c - z * s, z: y * s + z * c };
+}
+function rotateY(x, y, z, angle) {
+    const s = Math.sin(angle), c = Math.cos(angle);
+    return { x: x * c + z * s, y: y, z: -x * s + z * c };
+}
+
+function drawWireframe(vertices, edges) {
+    ctx.clearRect(0, 0, width, height);
+    ctx.strokeStyle = '#fff';
+    ctx.lineWidth = 1;
+    for (let edge of edges) {
+        const start = projectVertex(...vertices[edge[0]]);
+        const end = projectVertex(...vertices[edge[1]]);
+        if (fillMode) {
+            ctx.beginPath();
+            ctx.moveTo(start.x, start.y);
+            ctx.lineTo(end.x, end.y);
+            ctx.stroke();
+        } else {
+            ctx.fillStyle = '#fff';
+            ctx.fillRect(start.x - 0.5, start.y - 0.5, 1, 1);
+            ctx.fillRect(end.x - 0.5, end.y - 0.5, 1, 1);
+        }
+    }
+}
+
+function generateTorus() {
+    const vertices = [];
+    const edges = new Set();
+    for (let i = 0; i < 36; i++) {
+        for (let j = 0; j < 24; j++) {
+            const u = i / 18 * PI;
+            const v = j / 12 * PI;
+            const r1 = 50, r2 = 20;
+            const x = (r1 + r2 * Math.cos(v)) * Math.cos(u);
+            const y = (r1 + r2 * Math.cos(v)) * Math.sin(u);
+            const z = r2 * Math.sin(v);
+            vertices.push([x, y, z]);
+        }
+    }
+    for (let i = 0; i < 36; i++) {
+        for (let j = 0; j < 24; j++) {
+            const index = i * 24 + j;
+            edges.add([index, ((i + 1) % 36) * 24 + j]);
+            edges.add([index, i * 24 + (j + 1) % 24]);
+        }
+    }
+    return { vertices, edges };
+}
+
+function generateIcosahedron() {
+    const phi = (1 + Math.sqrt(5)) / 2;
+    const vertices = [
+        [-1, -1, -1], [1, -1, -1], [-1, 1, -1], [1, 1, -1],
+        [-1, -1, 1], [1, -1, 1], [-1, 1, 1], [1, 1, 1],
+        [0, -phi, 1 / phi], [0, phi, 1 / phi],
+        [0, -phi, -1 / phi], [0, phi, -1 / phi],
+        [-1 / phi, 0, -phi], [1 / phi, 0, -phi],
+        [-1 / phi, 0, phi], [1 / phi, 0, phi],
+        [-phi, 1 / phi, 0], [phi, 1 / phi, 0],
+        [-phi, -1 / phi, 0], [phi, -1 / phi, 0]
+    ].map(v => v.map(c => c * 30));
+    const edges = [
+        [0, 11], [1, 5], [2, 7], [3, 8],
+        [4, 10], [6, 9], [11, 0], [5, 1],
+        [7, 2], [8, 3], [10, 4], [9, 6],
+        [0, 3], [3, 1], [1, 2], [2, 0],
+        [11, 6], [6, 5], [5, 11], [8, 7],
+        [7, 4], [4, 8], [9, 10], [10, 9],
+        [3, 9], [9, 2], [2, 8], [8, 6],
+        [6, 7], [7, 5], [5, 11], [11, 4],
+        [4, 3], [0, 5], [10, 1], [9, 4]
+    ];
+    return { vertices, edges };
+}
+
+function animate() {
+    const { vertices, edges } = shapeType === 'torus' ? generateTorus() : generateIcosahedron();
+    let rotatedVertices = [];
+    for (let vertex of vertices) {
+        let v = rotateX(...vertex, angleY);
+        v = rotateY(v.x, v.y, v.z, angleX);
+        rotatedVertices.push([v.x, v.y, v.z]);
+    }
+    drawWireframe(rotatedVertices, edges);
+    requestAnimationFrame(animate);
+}
+
+function toggleShape() {
+    shapeType = shapeType === 'torus' ? 'icosahedron' : 'torus';
+}
+
+function toggleFill() {
+    fillMode = !fillMode;
+}
+
+canvas.addEventListener('mousedown', (e) => {
+    isDragging = true;
+    dragStartX = e.clientX;
+    dragStartY = e.clientY;
+});
+document.addEventListener('mousemove', (e) => {
+    if (!isDragging) return;
+    const dx = e.clientX - dragStartX;
+    const dy = e.clientY - dragStartY;
+    angleY += dx * 0.01;
+    angleX += dy * 0.01;
+    dragStartX = e.clientX;
+    dragStartY = e.clientY;
+});
+document.addEventListener('mouseup', () => isDragging = false);
+animate();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e840f1972643/hf-qwen-coder-32b.png b/data/artifacts/e840f1972643/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..b2c1a76
Binary files /dev/null and b/data/artifacts/e840f1972643/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/e840f1972643/kimi.html b/data/artifacts/e840f1972643/kimi.html
new file mode 100644
index 0000000..b8b6602
--- /dev/null
+++ b/data/artifacts/e840f1972643/kimi.html
@@ -0,0 +1,359 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>3D Wireframe/Solid Renderer</title>
+    <style>
+        body {
+            margin: 0;
+            overflow: hidden;
+            background: #000;
+            font-family: monospace;
+            color: #0f0;
+        }
+        canvas {
+            display: block;
+            width: 100vw;
+            height: 100vh;
+        }
+        #controls {
+            position: absolute;
+            top: 10px;
+            left: 10px;
+            background: rgba(0,20,0,0.8);
+            padding: 15px;
+            border: 1px solid #0f0;
+            border-radius: 4px;
+            user-select: none;
+        }
+        button, select {
+            background: #001100;
+            color: #0f0;
+            border: 1px solid #0f0;
+            padding: 5px 10px;
+            margin: 5px;
+            cursor: pointer;
+            font-family: monospace;
+        }
+        button:hover, select:hover {
+            background: #0f0;
+            color: #000;
+        }
+        label {
+            display: block;
+            margin: 5px;
+        }
+    </style>
+</head>
+<body>
+    <canvas id="canvas"></canvas>
+    <div id="controls">
+        <label>Shape:
+            <select id="shapeSelect">
+                <option value="torus">Torus</option>
+                <option value="icosahedron">Icosahedron</option>
+            </select>
+        </label>
+        <label>Mode:
+            <button id="modeToggle">Wireframe</button>
+        </label>
+        <label>Auto-rotate:
+            <button id="autoToggle">On</button>
+        </label>
+        <div style="margin-top:10px; font-size:12px; opacity:0.7;">
+            Drag to rotate
+        </div>
+    </div>
+
+    <script>
+        const canvas = document.getElementById('canvas');
+        const ctx = canvas.getContext('2d');
+        
+        let width, height;
+        function resize() {
+            width = canvas.width = window.innerWidth;
+            height = canvas.height = window.innerHeight;
+        }
+        window.addEventListener('resize', resize);
+        resize();
+
+        // 3D Math
+        class Vec3 {
+            constructor(x, y, z) {
+                this.x = x; this.y = y; this.z = z;
+            }
+        }
+
+        function mat4() {
+            return new Float32Array(16);
+        }
+
+        function identity() {
+            const m = mat4();
+            m[0] = 1; m[5] = 1; m[10] = 1; m[15] = 1;
+            return m;
+        }
+
+        function multiply(a, b) {
+            const out = mat4();
+            for(let i=0; i<4; i++) {
+                for(let j=0; j<4; j++) {
+                    out[i*4+j] = 
+                        a[i*4+0]*b[0*4+j] +
+                        a[i*4+1]*b[1*4+j] +
+                        a[i*4+2]*b[2*4+j] +
+                        a[i*4+3]*b[3*4+j];
+                }
+            }
+            return out;
+        }
+
+        function rotateX(angle) {
+            const c = Math.cos(angle), s = Math.sin(angle);
+            const m = mat4();
+            m[0] = 1; m[5] = c; m[6] = -s; 
+            m[9] = s; m[10] = c; m[15] = 1;
+            return m;
+        }
+
+        function rotateY(angle) {
+            const c = Math.cos(angle), s = Math.sin(angle);
+            const m = mat4();
+            m[0] = c; m[2] = s; m[5] = 1;
+            m[8] = -s; m[10] = c; m[15] = 1;
+            return m;
+        }
+
+        function transformVertex(v, m) {
+            const x = v.x * m[0] + v.y * m[4] + v.z * m[8] + m[12];
+            const y = v.x * m[1] + v.y * m[5] + v.z * m[9] + m[13];
+            const z = v.x * m[2] + v.y * m[6] + v.z * m[10] + m[14];
+            return new Vec3(x, y, z);
+        }
+
+        function project(v) {
+            const fov = 400;
+            const distance = 5;
+            const scale = fov / (v.z + distance);
+            return {
+                x: v.x * scale + width/2,
+                y: -v.y * scale + height/2,
+                z: v.z,
+                scale: scale
+            };
+        }
+
+        // Geometry
+        function generateTorus(segments = 24, rings = 16) {
+            const vertices = [];
+            const indices = [];
+            const R = 1.5, r = 0.5;
+            
+            for(let i=0; i<=rings; i++) {
+                const u = (i/rings) * Math.PI * 2;
+                for(let j=0; j<=segments; j++) {
+                    const v = (j/segments) * Math.PI * 2;
+                    const x = (R + r*Math.cos(v)) * Math.cos(u);
+                    const y = (R + r*Math.cos(v)) * Math.sin(u);
+                    const z = r * Math.sin(v);
+                    vertices.push(new Vec3(x, y, z));
+                }
+            }
+            
+            for(let i=0; i<rings; i++) {
+                for(let j=0; j<segments; j++) {
+                    const a = i*(segments+1) + j;
+                    const b = a + segments + 1;
+                    indices.push(a, b, a+1);
+                    indices.push(b, b+1, a+1);
+                }
+            }
+            return { vertices, indices };
+        }
+
+        function generateIcosahedron() {
+            const phi = (1 + Math.sqrt(5)) / 2;
+            const vertices = [
+                new Vec3(-1, phi, 0), new Vec3(1, phi, 0), new Vec3(-1, -phi, 0), new Vec3(1, -phi, 0),
+                new Vec3(0, -1, phi), new Vec3(0, 1, phi), new Vec3(0, -1, -phi), new Vec3(0, 1, -phi),
+                new Vec3(phi, 0, -1), new Vec3(phi, 0, 1), new Vec3(-phi, 0, -1), new Vec3(-phi, 0, 1)
+            ];
+            
+            for(let v of vertices) {
+                const len = Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
+                v.x = v.x/len * 1.5; 
+                v.y = v.y/len * 1.5; 
+                v.z = v.z/len * 1.5;
+            }
+            
+            const indices = [
+                0,11,5, 0,5,1, 0,1,7, 0,7,10, 0,10,11,
+                1,5,9, 5,11,4, 11,10,2, 10,7,6, 7,1,8,
+                3,9,4, 3,4,2, 3,2,6, 3,6,8, 3,8,9,
+                4,9,5, 2,4,11, 6,2,10, 8,6,7, 9,8,1
+            ];
+            
+            return { vertices, indices };
+        }
+
+        // State
+        let currentShape = 'torus';
+        let wireframeMode = true;
+        let autoRotate = true;
+        let rotationX = 0.3;
+        let rotationY = 0;
+        let isDragging = false;
+        let lastMouseX = 0;
+        let lastMouseY = 0;
+        let geometry = generateTorus();
+
+        function updateGeometry() {
+            geometry = currentShape === 'torus' ? generateTorus() : generateIcosahedron();
+        }
+
+        // Controls
+        document.getElementById('shapeSelect').addEventListener('change', (e) => {
+            currentShape = e.target.value;
+            updateGeometry();
+        });
+
+        document.getElementById('modeToggle').addEventListener('click', (e) => {
+            wireframeMode = !wireframeMode;
+            e.target.textContent = wireframeMode ? 'Wireframe' : 'Solid';
+        });
+
+        document.getElementById('autoToggle').addEventListener('click', (e) => {
+            autoRotate = !autoRotate;
+            e.target.textContent = autoRotate ? 'On' : 'Off';
+        });
+
+        // Input handling
+        function startDrag(x, y) {
+            isDragging = true;
+            lastMouseX = x;
+            lastMouseY = y;
+        }
+
+        function moveDrag(x, y) {
+            if(!isDragging) return;
+            const dx = x - lastMouseX;
+            const dy = y - lastMouseY;
+            rotationY += dx * 0.01;
+            rotationX += dy * 0.01;
+            lastMouseX = x;
+            lastMouseY = y;
+        }
+
+        function endDrag() {
+            isDragging = false;
+        }
+
+        canvas.addEventListener('mousedown', e => startDrag(e.clientX, e.clientY));
+        window.addEventListener('mousemove', e => moveDrag(e.clientX, e.clientY));
+        window.addEventListener('mouseup', endDrag);
+
+        canvas.addEventListener('touchstart', e => {
+            e.preventDefault();
+            startDrag(e.touches[0].clientX, e.touches[0].clientY);
+        }, {passive: false});
+        canvas.addEventListener('touchmove', e => {
+            e.preventDefault();
+            moveDrag(e.touches[0].clientX, e.touches[0].clientY);
+        }, {passive: false});
+        canvas.addEventListener('touchend', endDrag);
+
+        // Rendering
+        function render() {
+            ctx.fillStyle = '#000';
+            ctx.fillRect(0, 0, width, height);
+            
+            // Build transform matrix
+            let m = identity();
+            m = multiply(m, rotateY(rotationY));
+            m = multiply(m, rotateX(rotationX));
+            
+            // Transform vertices
+            const projected = [];
+            for(let v of geometry.vertices) {
+                const t = transformVertex(v, m);
+                projected.push(project(t));
+            }
+            
+            ctx.strokeStyle = '#0f0';
+            ctx.fillStyle = 'rgba(0,255,0,0.2)';
+            ctx.lineWidth = 1;
+            
+            if(wireframeMode) {
+                ctx.beginPath();
+                for(let i=0; i<geometry.indices.length; i+=3) {
+                    const i0 = geometry.indices[i];
+                    const i1 = geometry.indices[i+1];
+                    const i2 = geometry.indices[i+2];
+                    const p0 = projected[i0];
+                    const p1 = projected[i1];
+                    const p2 = projected[i2];
+                    
+                    // Simple clipping check
+                    if(p0.z > -4 && p1.z > -4) {
+                        ctx.moveTo(p0.x, p0.y);
+                        ctx.lineTo(p1.x, p1.y);
+                    }
+                    if(p1.z > -4 && p2.z > -4) {
+                        ctx.moveTo(p1.x, p1.y);
+                        ctx.lineTo(p2.x, p2.y);
+                    }
+                    if(p2.z > -4 && p0.z > -4) {
+                        ctx.moveTo(p2.x, p2.y);
+                        ctx.lineTo(p0.x, p0.y);
+                    }
+                }
+                ctx.stroke();
+            } else {
+                // Collect triangles with depth
+                const triangles = [];
+                for(let i=0; i<geometry.indices.length; i+=3) {
+                    const i0 = geometry.indices[i];
+                    const i1 = geometry.indices[i+1];
+                    const i2 = geometry.indices[i+2];
+                    const p0 = projected[i0];
+                    const p1 = projected[i1];
+                    const p2 = projected[i2];
+                    
+                    // Backface culling (screen space)
+                    const dx1 = p1.x - p0.x;
+                    const dy1 = p1.y - p0.y;
+                    const dx2 = p2.x - p0.x;
+                    const dy2 = p2.y - p0.y;
+                    if(dx1*dy2 - dy1*dx2 < 0) {
+                        const z = (p0.z + p1.z + p2.z) / 3;
+                        triangles.push({p0, p1, p2, z});
+                    }
+                }
+                
+                // Painter's algorithm (sort back to front)
+                triangles.sort((a,b) => b.z - a.z);
+                
+                for(let tri of triangles) {
+                    ctx.beginPath();
+                    ctx.moveTo(tri.p0.x, tri.p0.y);
+                    ctx.lineTo(tri.p1.x, tri.p1.y);
+                    ctx.lineTo(tri.p2.x, tri.p2.y);
+                    ctx.closePath();
+                    ctx.fill();
+                    ctx.stroke();
+                }
+            }
+            
+            if(autoRotate && !isDragging) {
+                rotationY += 0.01;
+                rotationX += 0.005;
+            }
+            
+            requestAnimationFrame(render);
+        }
+        
+        render();
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e840f1972643/kimi.png b/data/artifacts/e840f1972643/kimi.png
new file mode 100644
index 0000000..5840d78
Binary files /dev/null and b/data/artifacts/e840f1972643/kimi.png differ
diff --git a/data/artifacts/e840f1972643/qwen25-7b.html b/data/artifacts/e840f1972643/qwen25-7b.html
new file mode 100644
index 0000000..7e1eaa7
--- /dev/null
+++ b/data/artifacts/e840f1972643/qwen25-7b.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>3D Wireframe Scene</title>
+    <style>
+        body { margin: 0; overflow: hidden; }
+        canvas { display: block; width: 100%; height: 100%; background-color: #000; cursor: pointer; }
+    </style>
+</head>
+<body>
+<canvas id="wireframeCanvas"></canvas>
+<script>
+const canvas = document.getElementById('wireframeCanvas');
+const ctx = canvas.getContext('2d');
+
+let angle = 0;
+const shapeSelector = 'torus';
+const wireframeMode = true;
+
+function drawTorus() {
+    const radius = 50, tubeRadius = 15;
+    for (let phi = 0; phi < Math.PI * 2; phi += 0.03) {
+        const x = (radius + tubeRadius * Math.cos(phi)) * Math.cos(angle);
+        const y = (radius + tubeRadius * Math.sin(phi)) * Math.sin(angle);
+        const z = tubeRadius * Math.sin(phi);
+
+        ctx.beginPath();
+        ctx.moveTo(x, y);
+        ctx.lineTo((radius + 2 * tubeRadius * Math.cos(phi)) * Math.cos(angle), (radius + 2 * tubeRadius * Math.sin(phi)) * Math.sin(angle));
+        ctx.stroke();
+    }
+}
+
+function drawIcosahedron() {
+    const vertices = [
+        [1, -0.52573111211913364, -0.850650808362646], [-1, -0.52573111211913364, -0.850650808362646],
+        [1, 0.52573111211913364, -0.850650808362646], [-1, 0.52573111211913364, -0.850650808362646],
+        [0, -1.3090168673400617, 0.850650808362646], [0, 1.3090168673400617, 0.850650808362646],
+        [-0.850650808362646, -1, 0.52573111211913364], [0.850650808362646, -1, 0.52573111211913364],
+        [-0.850650808362646, 1, 0.52573111211913364], [0.850650808362646, 1, 0.52573111211913364],
+        [-0.52573111211913364, -1.3090168673400617, 0], [0.52573111211913364, -1.3090168673400617, 0],
+        [-0.52573111211913364, 1.3090168673400617, 0], [0.52573111211913364, 1.3090168673400617, 0]
+    ];
+
+    for (let i = 0; i < vertices.length; i++) {
+        const v1 = vertices[i];
+        const v2 = vertices[(i + 5) % 12];
+        ctx.beginPath();
+        ctx.moveTo((v1[0] * Math.cos(angle)) - (v1[1] * Math.sin(angle)), (v1[0] * Math.sin(angle)) + (v1[1] * Math.cos(angle)));
+        ctx.lineTo((v2[0] * Math.cos(angle)) - (v2[1] * Math.sin(angle)), (v2[0] * Math.sin(angle)) + (v2[1] * Math.cos(angle)));
+        ctx.stroke();
+    }
+}
+
+function project(point, z) {
+    const scale = 400 / (z + 5);
+    return [point[0] * scale, point[1] * scale];
+}
+
+canvas.addEventListener('mousemove', e => {
+    angle += (e.clientX - canvas.offsetLeft) * 0.02;
+});
+
+function render() {
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+    if (shapeSelector === 'torus') drawTorus();
+    else drawIcosahedron();
+
+    requestAnimationFrame(render);
+}
+
+render();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e840f1972643/qwen25-7b.png b/data/artifacts/e840f1972643/qwen25-7b.png
new file mode 100644
index 0000000..5eda123
Binary files /dev/null and b/data/artifacts/e840f1972643/qwen25-7b.png differ
diff --git a/data/artifacts/e840f1972643/qwen3-14b.html b/data/artifacts/e840f1972643/qwen3-14b.html
new file mode 100644
index 0000000..2b265f3
--- /dev/null
+++ b/data/artifacts/e840f1972643/qwen3-14b.html
@@ -0,0 +1,270 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>3D Rotating Wireframe</title>
+  <style>
+    html, body {
+      margin: 0;
+      overflow: hidden;
+      background: #000;
+      height: 100%;
+    }
+    canvas {
+      display: block;
+    }
+    #controls {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      color: white;
+      background: rgba(0,0,0,0.5);
+      padding: 10px;
+      font-family: sans-serif;
+      z-index: 10;
+    }
+    #controls select, #controls input[type=checkbox] {
+      margin: 5px;
+    }
+  </style>
+</head>
+<body>
+  <div id="controls">
+    <label>Shape:
+      <select id="shapeSelect">
+        <option value="torus">Torus</option>
+        <option value="icosahedron">Icosahedron</option>
+      </select>
+    </label>
+    <label>
+      <input type="checkbox" id="solidToggle"> Solid
+    </label>
+  </div>
+  <canvas id="scene"></canvas>
+  <script>
+    const canvas = document.getElementById('scene');
+    const ctx = canvas.getContext('2d');
+    let width = canvas.width = window.innerWidth;
+    let height = canvas.height = window.innerHeight;
+
+    let shape = 'torus';
+    let solid = false;
+    document.getElementById('shapeSelect').addEventListener('change', (e) => {
+      shape = e.target.value;
+    });
+    document.getElementById('solidToggle').addEventListener('change', (e) => {
+      solid = e.target.checked;
+    });
+
+    window.addEventListener('resize', () => {
+      width = canvas.width = window.innerWidth;
+      height = canvas.height = window.innerHeight;
+    });
+
+    let mouseX = width / 2;
+    let mouseY = height / 2;
+    let lastMouseX = mouseX;
+    let lastMouseY = mouseY;
+    let isDragging = false;
+
+    canvas.addEventListener('mousedown', (e) => {
+      isDragging = true;
+      lastMouseX = e.clientX;
+      lastMouseY = e.clientY;
+    });
+
+    canvas.addEventListener('mouseup', () => {
+      isDragging = false;
+    });
+
+    canvas.addEventListener('mousemove', (e) => {
+      if (isDragging) {
+        const dx = e.clientX - lastMouseX;
+        const dy = e.clientY - lastMouseY;
+        mouseX += dx;
+        mouseY += dy;
+        lastMouseX = e.clientX;
+        lastMouseY = e.clientY;
+      }
+    });
+
+    // Geometry
+    function createTorus(radius = 1, tubeRadius = 0.3, segments = 64, tubeSegments = 16) {
+      const vertices = [];
+      const indices = [];
+      for (let i = 0; i <= segments; i++) {
+        const angle = (i / segments) * Math.PI * 2;
+        const x = radius * Math.cos(angle);
+        const z = radius * Math.sin(angle);
+        for (let j = 0; j <= tubeSegments; j++) {
+          const phi = (j / tubeSegments) * Math.PI * 2;
+          const tx = tubeRadius * Math.cos(phi);
+          const tz = tubeRadius * Math.sin(phi);
+          const vx = x + tx;
+          const vz = z + tz;
+          vertices.push([vx, 0, vz]);
+        }
+      }
+
+      for (let i = 0; i < segments; i++) {
+        for (let j = 0; j < tubeSegments; j++) {
+          const a = i * (tubeSegments + 1) + j;
+          const b = a + tubeSegments + 1;
+          const c = b + 1;
+          const d = a + 1;
+          indices.push(a, b, c, d, a, c);
+        }
+      }
+
+      return { vertices, indices };
+    }
+
+    function createIcosahedron(radius = 1) {
+      const vertices = [];
+      const indices = [];
+
+      // Golden ratio
+      const phi = (1 + Math.sqrt(5)) / 2;
+
+      const v = [
+        [-1, 1, phi], [1, 1, phi], [1, -1, phi], [-1, -1, phi],
+        [-1, 1, -phi], [1, 1, -phi], [1, -1, -phi], [-1, -1, -phi],
+        [phi, -1, 1], [phi, 1, 1], [-phi, -1, 1], [-phi, 1, 1],
+        [phi, 1, -1], [phi, -1, -1], [-phi, 1, -1], [-phi, -1, -1],
+        [1, phi, -1], [-1, phi, -1], [-1, -phi, -1], [1, -phi, -1],
+        [1, phi, 1], [-1, phi, 1], [-1, -phi, 1], [1, -phi, 1]
+      ];
+
+      for (let i = 0; i < v.length; i++) {
+        const [x, y, z] = v[i];
+        const len = Math.sqrt(x*x + y*y + z*z);
+        vertices.push([x / len * radius, y / len * radius, z / len * radius]);
+      }
+
+      const faces = [
+        [0, 11, 5], [0, 5, 1], [0, 1, 7], [0, 7, 10],
+        [0, 10, 11], [1, 5, 9], [5, 11, 4], [11, 10, 2],
+        [10, 7, 6], [7, 1, 8], [3, 9, 4], [3, 4, 2],
+        [3, 2, 6], [3, 6, 8], [3, 8, 9], [4, 9, 5],
+        [2, 4, 11], [6, 2, 10], [8, 6, 7], [9, 8, 1]
+      ];
+
+      for (const face of faces) {
+        indices.push(...face);
+      }
+
+      return { vertices, indices };
+    }
+
+    let geometry;
+    function updateGeometry() {
+      if (shape === 'torus') {
+        geometry = createTorus(1.5, 0.4, 64, 16);
+      } else {
+        geometry = createIcosahedron(1);
+      }
+    }
+
+    updateGeometry();
+
+    // Camera
+    let angleX = 0;
+    let angleY = 0;
+    let angleZ = 0;
+
+    // Projection
+    function project(vertex, cx, cy, cz, fov, aspect) {
+      const [x, y, z] = vertex;
+      const px = (x - cx) * fov / (z - cz);
+      const py = (y - cy) * fov / (z - cz) * aspect;
+      return [px + width / 2, py + height / 2];
+    }
+
+    // Draw
+    function draw() {
+      ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
+      ctx.fillRect(0, 0, width, height);
+
+      const cx = mouseX;
+      const cy = mouseY;
+      const cz = -5;
+      const fov = 200;
+      const aspect = width / height;
+
+      let vertices = geometry.vertices.map(v => v.map((val, i) => {
+        let x = v[0];
+        let y = v[1];
+        let z = v[2];
+
+        // Rotate around X
+        const sinX = Math.sin(angleX);
+        const cosX = Math.cos(angleX);
+        const y1 = y * cosX - z * sinX;
+        const z1 = y * sinX + z * cosX;
+        y = y1;
+        z = z1;
+
+        // Rotate around Y
+        const sinY = Math.sin(angleY);
+        const cosY = Math.cos(angleY);
+        const x1 = x * cosY + z * sinY;
+        const z2 = -x * sinY + z * cosY;
+        x = x1;
+        z = z2;
+
+        // Rotate around Z
+        const sinZ = Math.sin(angleZ);
+        const cosZ = Math.cos(angleZ);
+        const x2 = x * cosZ - y * sinZ;
+        const y1 = x * sinZ + y * cosZ;
+        x = x2;
+        y = y1;
+
+        return [x, y, z];
+      }));
+
+      const indices = geometry.indices;
+
+      ctx.beginPath();
+      ctx.strokeStyle = solid ? '#0ff' : '#fff';
+      ctx.lineWidth = 1;
+
+      for (let i = 0; i < indices.length; i += 3) {
+        const a = indices[i];
+        const b = indices[i + 1];
+        const c = indices[i + 2];
+
+        const pa = project(vertices[a], cx, cy, cz, fov, aspect);
+        const pb = project(vertices[b], cx, cy, cz, fov, aspect);
+        const pc = project(vertices[c], cx, cy, cz, fov, aspect);
+
+        if (solid) {
+          ctx.moveTo(pa[0], pa[1]);
+          ctx.lineTo(pb[0], pb[1]);
+          ctx.lineTo(pc[0], pc[1]);
+          ctx.lineTo(pa[0], pa[1]);
+        } else {
+          ctx.moveTo(pa[0], pa[1]);
+          ctx.lineTo(pb[0], pb[1]);
+          ctx.moveTo(pb[0], pb[1]);
+          ctx.lineTo(pc[0], pc[1]);
+          ctx.moveTo(pc[0], pc[1]);
+          ctx.lineTo(pa[0], pa[1]);
+        }
+      }
+
+      ctx.stroke();
+    }
+
+    function animate() {
+      angleX += 0.01;
+      angleY += 0.005;
+      angleZ += 0.002;
+      draw();
+      requestAnimationFrame(animate);
+    }
+
+    animate();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e840f1972643/qwen3-14b.png b/data/artifacts/e840f1972643/qwen3-14b.png
new file mode 100644
index 0000000..e153405
Binary files /dev/null and b/data/artifacts/e840f1972643/qwen3-14b.png differ
diff --git a/data/artifacts/ebf4f7984098/claude-code.html b/data/artifacts/ebf4f7984098/claude-code.html
new file mode 100644
index 0000000..75a4bf9
--- /dev/null
+++ b/data/artifacts/ebf4f7984098/claude-code.html
@@ -0,0 +1,475 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Serene Aquarium</title>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  html, body { width: 100%; height: 100%; overflow: hidden; background: #04121f; }
+  #tank { display: block; width: 100vw; height: 100vh; cursor: crosshair; }
+  .ui {
+    position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
+    display: flex; gap: 10px; align-items: center;
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+    z-index: 10; user-select: none;
+  }
+  .btn {
+    background: rgba(9, 40, 62, 0.55);
+    color: #cdeafe;
+    border: 1px solid rgba(140, 200, 240, 0.35);
+    backdrop-filter: blur(8px);
+    -webkit-backdrop-filter: blur(8px);
+    padding: 10px 18px;
+    border-radius: 30px;
+    font-size: 14px;
+    letter-spacing: 0.4px;
+    cursor: pointer;
+    transition: background .2s, transform .1s, box-shadow .2s;
+    box-shadow: 0 4px 18px rgba(0,0,0,0.35);
+  }
+  .btn:hover { background: rgba(20, 70, 105, 0.75); box-shadow: 0 6px 24px rgba(60,150,220,0.3); }
+  .btn:active { transform: scale(0.95); }
+  .hint {
+    position: fixed; top: 16px; left: 50%; transform: translateX(-50%);
+    color: rgba(180, 220, 250, 0.55);
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+    font-size: 13px; letter-spacing: 0.5px; z-index: 10; pointer-events: none;
+    text-shadow: 0 1px 4px rgba(0,0,0,0.5);
+    transition: opacity 1.5s;
+  }
+</style>
+</head>
+<body>
+<canvas id="tank"></canvas>
+<div class="hint" id="hint">click the water to drop food · fish will chase it</div>
+<div class="ui">
+  <div class="btn" id="addFish">+ Add Fish</div>
+  <div class="btn" id="feed">🍤 Sprinkle Food</div>
+</div>
+
+<script>
+(function () {
+  "use strict";
+
+  const canvas = document.getElementById("tank");
+  const ctx = canvas.getContext("2d");
+  let W = 0, H = 0, DPR = Math.min(window.devicePixelRatio || 1, 2);
+
+  function resize() {
+    W = window.innerWidth;
+    H = window.innerHeight;
+    canvas.width = W * DPR;
+    canvas.height = H * DPR;
+    canvas.style.width = W + "px";
+    canvas.style.height = H + "px";
+    ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
+    buildPlants();
+  }
+  window.addEventListener("resize", resize);
+
+  // ---------- utility ----------
+  const rand = (a, b) => a + Math.random() * (b - a);
+  const clamp = (v, a, b) => v < a ? a : v > b ? b : v;
+
+  // ---------- fish palette ----------
+  const PALETTES = [
+    ["#ff8a5b", "#ffb98a"], ["#5bc0eb", "#9be0ff"],
+    ["#f6c445", "#ffe08a"], ["#e85d75", "#ffa9bb"],
+    ["#7bd389", "#c3f0c8"], ["#c084fc", "#e5c8ff"],
+    ["#ff6b6b", "#ffb3b3"], ["#4dd0e1", "#a7f0f5"]
+  ];
+
+  // ---------- entities ----------
+  const fishes = [];
+  const food = [];
+  const bubbles = [];
+  const plants = [];
+
+  class Fish {
+    constructor(x, y) {
+      this.x = x; this.y = y;
+      const a = rand(0, Math.PI * 2);
+      this.vx = Math.cos(a); this.vy = Math.sin(a);
+      this.size = rand(9, 18);
+      this.maxSpeed = rand(1.4, 2.3) * (16 / this.size + 0.4);
+      this.maxForce = 0.05;
+      const p = PALETTES[(Math.random() * PALETTES.length) | 0];
+      this.color = p[0]; this.color2 = p[1];
+      this.phase = rand(0, Math.PI * 2);
+      this.wiggle = 0;
+    }
+
+    applyForce(fx, fy) { this.ax += fx; this.ay += fy; }
+
+    steerTo(tx, ty, weight) {
+      let dx = tx - this.x, dy = ty - this.y;
+      const d = Math.hypot(dx, dy) || 1;
+      dx = (dx / d) * this.maxSpeed; dy = (dy / d) * this.maxSpeed;
+      let sx = dx - this.vx, sy = dy - this.vy;
+      const sm = Math.hypot(sx, sy);
+      if (sm > this.maxForce) { sx = (sx / sm) * this.maxForce; sy = (sy / sm) * this.maxForce; }
+      this.applyForce(sx * weight, sy * weight);
+    }
+
+    flock(list) {
+      let sepX = 0, sepY = 0, aliX = 0, aliY = 0, cohX = 0, cohY = 0;
+      let sepN = 0, n = 0;
+      const perc = 70, sepDist = 32;
+      for (let i = 0; i < list.length; i++) {
+        const o = list[i];
+        if (o === this) continue;
+        const dx = this.x - o.x, dy = this.y - o.y;
+        const d = Math.hypot(dx, dy);
+        if (d < perc && d > 0) {
+          aliX += o.vx; aliY += o.vy;
+          cohX += o.x; cohY += o.y;
+          n++;
+          if (d < sepDist) { sepX += dx / d; sepY += dy / d; sepN++; }
+        }
+      }
+      if (sepN > 0) this.steerVec(sepX / sepN, sepY / sepN, 1.5);
+      if (n > 0) {
+        this.steerVec(aliX / n, aliY / n, 0.7);
+        this.steerTo(cohX / n, cohY / n, 0.7);
+      }
+    }
+
+    steerVec(vx, vy, weight) {
+      const m = Math.hypot(vx, vy) || 1;
+      vx = (vx / m) * this.maxSpeed; vy = (vy / m) * this.maxSpeed;
+      let sx = vx - this.vx, sy = vy - this.vy;
+      const sm = Math.hypot(sx, sy);
+      if (sm > this.maxForce) { sx = (sx / sm) * this.maxForce; sy = (sy / sm) * this.maxForce; }
+      this.applyForce(sx * weight, sy * weight);
+    }
+
+    edges() {
+      const m = 60;
+      if (this.x < m) this.applyForce(0.08, 0);
+      if (this.x > W - m) this.applyForce(-0.08, 0);
+      if (this.y < m) this.applyForce(0, 0.08);
+      if (this.y > H - m) this.applyForce(0, -0.08);
+    }
+
+    update() {
+      this.ax = 0; this.ay = 0;
+
+      // chase nearest food
+      let target = null, best = 1e9;
+      for (let i = 0; i < food.length; i++) {
+        const f = food[i];
+        const d = Math.hypot(f.x - this.x, f.y - this.y);
+        if (d < best) { best = d; target = f; }
+      }
+      if (target && best < 320) {
+        this.steerTo(target.x, target.y, 2.4);
+        if (best < this.size + 5) { target.eaten = true; }
+      } else {
+        this.flock(fishes);
+        // gentle wander
+        this.phase += 0.02;
+        this.applyForce(Math.cos(this.phase) * 0.006, Math.sin(this.phase * 0.7) * 0.006);
+      }
+      this.edges();
+
+      this.vx += this.ax; this.vy += this.ay;
+      const sp = Math.hypot(this.vx, this.vy);
+      const max = target && best < 320 ? this.maxSpeed * 1.5 : this.maxSpeed;
+      if (sp > max) { this.vx = (this.vx / sp) * max; this.vy = (this.vy / sp) * max; }
+      // minimum drift so they never freeze
+      if (sp < 0.35) { this.vx += this.vx / (sp || 1) * 0.05 + 0.02; }
+
+      this.x += this.vx; this.y += this.vy;
+      this.x = clamp(this.x, -20, W + 20);
+      this.y = clamp(this.y, -20, H + 20);
+      this.wiggle += sp * 0.35 + 0.05;
+    }
+
+    draw() {
+      const ang = Math.atan2(this.vy, this.vx);
+      ctx.save();
+      ctx.translate(this.x, this.y);
+      ctx.rotate(ang);
+      const s = this.size;
+      const tail = Math.sin(this.wiggle) * 0.5;
+
+      // tail
+      ctx.beginPath();
+      ctx.moveTo(-s * 0.8, 0);
+      ctx.quadraticCurveTo(-s * 1.5, s * 0.6 + tail * s, -s * 1.9, tail * s * 1.4);
+      ctx.quadraticCurveTo(-s * 1.4, 0, -s * 1.9, -tail * s * 1.4);
+      ctx.quadraticCurveTo(-s * 1.5, -s * 0.6 + tail * s, -s * 0.8, 0);
+      ctx.fillStyle = this.color2;
+      ctx.fill();
+
+      // body gradient
+      const g = ctx.createLinearGradient(-s, -s, s, s);
+      g.addColorStop(0, this.color2);
+      g.addColorStop(1, this.color);
+      ctx.beginPath();
+      ctx.moveTo(s * 1.15, 0);
+      ctx.quadraticCurveTo(s * 0.2, -s * 0.72, -s * 0.85, 0);
+      ctx.quadraticCurveTo(s * 0.2, s * 0.72, s * 1.15, 0);
+      ctx.fillStyle = g;
+      ctx.fill();
+
+      // top fin
+      ctx.beginPath();
+      ctx.moveTo(-s * 0.1, -s * 0.55);
+      ctx.quadraticCurveTo(-s * 0.5, -s * 1.0, -s * 0.7, -s * 0.4);
+      ctx.quadraticCurveTo(-s * 0.4, -s * 0.4, -s * 0.1, -s * 0.55);
+      ctx.fillStyle = this.color;
+      ctx.globalAlpha = 0.85;
+      ctx.fill();
+      ctx.globalAlpha = 1;
+
+      // eye
+      ctx.beginPath();
+      ctx.arc(s * 0.65, -s * 0.12, s * 0.11, 0, Math.PI * 2);
+      ctx.fillStyle = "#fff";
+      ctx.fill();
+      ctx.beginPath();
+      ctx.arc(s * 0.68, -s * 0.12, s * 0.055, 0, Math.PI * 2);
+      ctx.fillStyle = "#12232f";
+      ctx.fill();
+
+      ctx.restore();
+    }
+  }
+
+  class Food {
+    constructor(x, y) {
+      this.x = x; this.y = y;
+      this.vx = rand(-0.3, 0.3);
+      this.vy = rand(0.3, 0.8);
+      this.r = rand(2, 3.5);
+      this.eaten = false;
+      this.hue = rand(20, 45);
+    }
+    update() {
+      this.vy += 0.005;
+      this.vy = clamp(this.vy, 0, 1.1);
+      this.x += this.vx; this.y += this.vy;
+      this.vx *= 0.98;
+      if (this.y > H - 6) { this.y = H - 6; this.vy = 0; }
+    }
+    draw() {
+      ctx.beginPath();
+      ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
+      ctx.fillStyle = `hsl(${this.hue}, 75%, 62%)`;
+      ctx.fill();
+    }
+  }
+
+  class Bubble {
+    constructor() {
+      this.x = rand(0, W);
+      this.y = H + rand(0, 40);
+      this.r = rand(1.5, 5);
+      this.sp = rand(0.4, 1.3);
+      this.sway = rand(0, Math.PI * 2);
+    }
+    update() {
+      this.y -= this.sp;
+      this.sway += 0.03;
+      this.x += Math.sin(this.sway) * 0.4;
+      if (this.y < -10) { this.y = H + 10; this.x = rand(0, W); }
+    }
+    draw() {
+      ctx.beginPath();
+      ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
+      ctx.strokeStyle = "rgba(200, 235, 255, 0.35)";
+      ctx.lineWidth = 1;
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.arc(this.x - this.r * 0.3, this.y - this.r * 0.3, this.r * 0.3, 0, Math.PI * 2);
+      ctx.fillStyle = "rgba(255,255,255,0.4)";
+      ctx.fill();
+    }
+  }
+
+  class Plant {
+    constructor(x, base) {
+      this.x = x;
+      this.base = base;
+      this.segs = (rand(5, 9)) | 0;
+      this.len = rand(50, H * 0.34);
+      this.w = rand(6, 13);
+      this.phase = rand(0, Math.PI * 2);
+      this.speed = rand(0.6, 1.2);
+      this.hue = rand(100, 160);
+      this.light = rand(22, 34);
+    }
+    draw(t) {
+      const segLen = this.len / this.segs;
+      ctx.save();
+      ctx.translate(this.x, this.base);
+      ctx.beginPath();
+      ctx.moveTo(0, 0);
+      let x = 0, y = 0, ang = -Math.PI / 2;
+      const pts = [[0, 0]];
+      for (let i = 0; i < this.segs; i++) {
+        const sway = Math.sin(t * 0.001 * this.speed + this.phase + i * 0.5) * 0.18 * (i / this.segs + 0.3);
+        ang += sway;
+        x += Math.cos(ang) * segLen;
+        y += Math.sin(ang) * segLen;
+        pts.push([x, y]);
+      }
+      // draw as tapered ribbon
+      ctx.beginPath();
+      ctx.moveTo(-this.w / 2, 0);
+      for (let i = 0; i < pts.length; i++) {
+        const w = (this.w / 2) * (1 - i / pts.length);
+        ctx.lineTo(pts[i][0] - w, pts[i][1]);
+      }
+      for (let i = pts.length - 1; i >= 0; i--) {
+        const w = (this.w / 2) * (1 - i / pts.length);
+        ctx.lineTo(pts[i][0] + w, pts[i][1]);
+      }
+      ctx.closePath();
+      const g = ctx.createLinearGradient(0, 0, 0, -this.len);
+      g.addColorStop(0, `hsl(${this.hue}, 55%, ${this.light - 8}%)`);
+      g.addColorStop(1, `hsl(${this.hue}, 60%, ${this.light + 6}%)`);
+      ctx.fillStyle = g;
+      ctx.fill();
+      ctx.restore();
+    }
+  }
+
+  function buildPlants() {
+    plants.length = 0;
+    const count = Math.max(6, (W / 130) | 0);
+    for (let i = 0; i < count; i++) {
+      const x = (W / count) * i + rand(-25, 25);
+      plants.push(new Plant(x, H + 4));
+    }
+  }
+
+  // light rays precomputed
+  const rays = [];
+  for (let i = 0; i < 6; i++) {
+    rays.push({ x: rand(0.1, 0.9), w: rand(40, 120), speed: rand(0.00004, 0.0001), phase: rand(0, 10), tilt: rand(-0.25, 0.25) });
+  }
+
+  // ---------- background ----------
+  function drawBackground() {
+    const g = ctx.createLinearGradient(0, 0, 0, H);
+    g.addColorStop(0, "#0a5a8a");
+    g.addColorStop(0.35, "#0a3f66");
+    g.addColorStop(0.7, "#072a48");
+    g.addColorStop(1, "#04121f");
+    ctx.fillStyle = g;
+    ctx.fillRect(0, 0, W, H);
+  }
+
+  function drawRays(t) {
+    ctx.save();
+    ctx.globalCompositeOperation = "lighter";
+    for (const r of rays) {
+      const cx = (r.x + Math.sin(t * r.speed + r.phase) * 0.04) * W;
+      const topW = r.w;
+      const botW = r.w * 3;
+      const grad = ctx.createLinearGradient(cx, 0, cx + r.tilt * H, H);
+      grad.addColorStop(0, "rgba(150, 210, 255, 0.10)");
+      grad.addColorStop(0.6, "rgba(120, 190, 240, 0.04)");
+      grad.addColorStop(1, "rgba(120, 190, 240, 0)");
+      ctx.fillStyle = grad;
+      ctx.beginPath();
+      ctx.moveTo(cx - topW / 2, 0);
+      ctx.lineTo(cx + topW / 2, 0);
+      ctx.lineTo(cx + r.tilt * H + botW / 2, H);
+      ctx.lineTo(cx + r.tilt * H - botW / 2, H);
+      ctx.closePath();
+      ctx.fill();
+    }
+    ctx.restore();
+  }
+
+  function drawFloor() {
+    ctx.fillStyle = "#0a2033";
+    ctx.beginPath();
+    ctx.moveTo(0, H);
+    ctx.lineTo(0, H - 30);
+    for (let x = 0; x <= W; x += 40) {
+      ctx.lineTo(x, H - 26 + Math.sin(x * 0.03) * 8);
+    }
+    ctx.lineTo(W, H);
+    ctx.closePath();
+    ctx.fill();
+  }
+
+  // ---------- interaction ----------
+  function dropFood(x, y, n) {
+    for (let i = 0; i < n; i++) {
+      food.push(new Food(x + rand(-14, 14), y + rand(-10, 10)));
+    }
+    if (food.length > 120) food.splice(0, food.length - 120);
+  }
+
+  canvas.addEventListener("pointerdown", (e) => {
+    dropFood(e.clientX, e.clientY, 4);
+    hideHint();
+  });
+
+  document.getElementById("addFish").addEventListener("click", () => {
+    fishes.push(new Fish(rand(W * 0.3, W * 0.7), rand(H * 0.3, H * 0.7)));
+    if (fishes.length > 60) fishes.splice(0, 1);
+  });
+
+  document.getElementById("feed").addEventListener("click", () => {
+    for (let i = 0; i < 6; i++) dropFood(rand(W * 0.2, W * 0.8), rand(20, 60), 3);
+    hideHint();
+  });
+
+  let hintTimer;
+  const hintEl = document.getElementById("hint");
+  function hideHint() { hintEl.style.opacity = "0"; }
+  hintTimer = setTimeout(hideHint, 7000);
+
+  // ---------- init ----------
+  resize();
+  for (let i = 0; i < 14; i++) fishes.push(new Fish(rand(0, W), rand(0, H)));
+  for (let i = 0; i < 60; i++) { const b = new Bubble(); b.y = rand(0, H); bubbles.push(b); }
+
+  // ---------- loop ----------
+  function frame(t) {
+    drawBackground();
+    drawRays(t);
+
+    // plants (behind fish)
+    for (const p of plants) p.draw(t);
+    drawFloor();
+
+    // food
+    for (let i = food.length - 1; i >= 0; i--) {
+      const f = food[i];
+      f.update(); f.draw();
+      if (f.eaten) {
+        for (let k = 0; k < 2; k++) { const b = new Bubble(); b.x = f.x; b.y = f.y; b.r = rand(1, 2.5); bubbles.push(b); }
+        if (bubbles.length > 140) bubbles.splice(0, 1);
+        food.splice(i, 1);
+      }
+    }
+
+    // fish
+    for (const fish of fishes) fish.update();
+    for (const fish of fishes) fish.draw();
+
+    // bubbles (front)
+    for (const b of bubbles) { b.update(); b.draw(); }
+
+    // subtle vignette
+    const v = ctx.createRadialGradient(W / 2, H / 2, Math.min(W, H) * 0.3, W / 2, H / 2, Math.max(W, H) * 0.75);
+    v.addColorStop(0, "rgba(0,0,0,0)");
+    v.addColorStop(1, "rgba(0,0,0,0.35)");
+    ctx.fillStyle = v;
+    ctx.fillRect(0, 0, W, H);
+
+    requestAnimationFrame(frame);
+  }
+  requestAnimationFrame(frame);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ebf4f7984098/claude-code.png b/data/artifacts/ebf4f7984098/claude-code.png
new file mode 100644
index 0000000..6473a26
Binary files /dev/null and b/data/artifacts/ebf4f7984098/claude-code.png differ
diff --git a/data/artifacts/ebf4f7984098/gemma3-12b.html b/data/artifacts/ebf4f7984098/gemma3-12b.html
new file mode 100644
index 0000000..9f4592e
--- /dev/null
+++ b/data/artifacts/ebf4f7984098/gemma3-12b.html
@@ -0,0 +1,286 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Relaxing Aquarium</title>
+<style>
+body { margin: 0; overflow: hidden; background-color: #3498db; }
+canvas { display: block; }
+#controls { position: absolute; top: 10px; left: 10px; color: white; font-family: sans-serif; }
+</style>
+</head>
+<body>
+
+<div id="controls">
+  Add Fish <button id="addFishButton">Add+</button>
+</div>
+
+<canvas id="aquariumCanvas"></canvas>
+
+<script>
+const canvas = document.getElementById('aquariumCanvas');
+const ctx = canvas.getContext('2d');
+
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+
+let fish = [];
+let food = [];
+let bubbles = [];
+let plants = [];
+let lightRays = [];
+
+// Configuration
+const numFish = 10;
+const bubbleDensity = 50;
+const plantDensity = 5;
+const rayCount = 3;
+
+// Colors (Underwater Gradient)
+const gradientColors = ['#3498db', '#2980b9', '#2673a3'];
+
+
+function init() {
+  // Create Fish
+  for (let i = 0; i < numFish; i++) {
+    fish.push(new Fish(Math.random() * canvas.width, Math.random() * canvas.height, 20 + Math.random()*10));
+  }
+
+  // Create Bubbles
+  for (let i = 0; i < bubbleDensity; i++) {
+    bubbles.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, size: Math.random() * 5, speed: Math.random() * 2 });
+  }
+
+  // Create Plants (Simple swaying)
+  for (let i = 0; i < plantDensity; i++) {
+    plants.push(new Plant(Math.random() * canvas.width, canvas.height - 30 + Math.random()*10, Math.random() * 50));
+  }
+
+  // Create Light Rays (Vertical lines)
+  for (let i = 0; i < rayCount; i++) {
+    lightRays.push({ x: Math.random() * canvas.width, y: 0, length: Math.random() * canvas.height / 2, angle: Math.random()*Math.PI/4 });
+  }
+}
+
+// Fish Class
+class Fish {
+  constructor(x, y, size) {
+    this.x = x;
+    this.y = y;
+    this.size = size;
+    this.speed = 0.5 + Math.random() * 1;
+    this.angle = Math.random() * Math.PI * 2;
+    this.color = `hsl(${Math.random()*360}, 70%, 50%)`; // Random fish colors
+  }
+
+  update(fishArray) {
+    let alignmentX = 0;
+    let alignmentY = 0;
+    let neighborCount = 0;
+
+    for (const otherFish of fishArray) {
+      if (otherFish !== this) {
+        const distance = Math.sqrt((this.x - otherFish.x)**2 + (this.y - otherFish.y)**2);
+        if (distance < 50) {
+          alignmentX += otherFish.x;
+          alignmentY += otherFish.y;
+          neighborCount++;
+        }
+      }
+    }
+
+    if (neighborCount > 0) {
+      alignmentX /= neighborCount;
+      alignmentY /= neighborCount;
+    }
+
+    const steeringForceX = alignmentX - this.x;
+    const steeringForceY = alignmentY - this.y;
+
+
+    this.angle += (Math.atan2(steeringForceY, steeringForceX) - this.angle) * 0.1; // Reduce turning speed.
+    this.x += Math.cos(this.angle) * this.speed;
+    this.y += Math.sin(this.angle) * this.speed;
+
+     // Boundary conditions (wrap around)
+    if (this.x < 0) this.x = canvas.width;
+    if (this.x > canvas.width) this.x = 0;
+    if (this.y < 0) this.y = canvas.height;
+    if (this.y > canvas.height) this.y = 0;
+  }
+
+  draw() {
+    ctx.beginPath();
+    ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
+    ctx.fillStyle = this.color;
+    ctx.fill();
+    ctx.closePath();
+  }
+}
+
+// Plant Class (Simple swaying)
+class Plant {
+    constructor(x, y, height){
+        this.x = x;
+        this.y = y;
+        this.height = height;
+        this.angleOffset = Math.random() * Math.PI * 2; // Random initial angle
+    }
+
+    update(){
+      this.angleOffset += 0.01;
+    }
+
+    draw(){
+        ctx.beginPath();
+        ctx.moveTo(this.x, this.y);
+        ctx.quadraticCurveTo(this.x + 20*Math.cos(this.angleOffset), this.y - this.height, this.x - 20*Math.cos(this.angleOffset), this.y );
+        ctx.lineTo(this.x, this.y); // Close the shape
+        ctx.fillStyle = 'green';
+        ctx.fill();
+        ctx.closePath();
+    }
+}
+
+// Food Class
+class Food {
+  constructor(x, y) {
+    this.x = x;
+    this.y = y;
+    this.size = 5;
+    this.speedX = (Math.random() - 0.5) * 2;
+    this.speedY = (Math.random() - 0.5) * 2;
+  }
+
+  update() {
+    this.x += this.speedX;
+    this.y += this.speedY;
+
+    if(this.x < 0 || this.x > canvas.width){
+       this.speedX *= -1;
+    }
+    if (this.y < 0 || this.y > canvas.height) {
+        this.speedY *= -1;
+    }
+
+  }
+
+  draw() {
+    ctx.beginPath();
+    ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
+    ctx.fillStyle = 'red';
+    ctx.fill();
+    ctx.closePath();
+  }
+}
+
+function drawBackground() {
+  const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
+  gradient.addColorStop(0, gradientColors[0]);
+  gradient.addColorStop(0.5, gradientColors[1]);
+  gradient.addColorStop(1, gradientColors[2]);
+
+  ctx.fillStyle = gradient;
+  ctx.fillRect(0, 0, canvas.width, canvas.height);
+}
+
+function drawBubbles() {
+  for (let i = 0; i < bubbles.length; i++) {
+    bubbles[i].y += bubbles[i].speed;
+    if (bubbles[i].y > canvas.height) {
+      bubbles[i].y = Math.random() * -10; // Reset above screen
+      bubbles[i].x = Math.random() * canvas.width;
+    }
+
+    ctx.beginPath();
+    ctx.arc(bubbles[i].x, bubbles[i].y, bubbles[i].size, 0, Math.PI * 2);
+    ctx.fillStyle = 'rgba(173, 216, 230, 0.7)'; // Light blue, transparent
+    ctx.fill();
+    ctx.closePath();
+  }
+}
+
+function drawLightRays() {
+  for (let i = 0; i < lightRays.length; i++) {
+    ctx.beginPath();
+    ctx.moveTo(lightRays[i].x, lightRays[i].y);
+    ctx.lineTo(lightRays[i].x, lightRays[i].y + lightRays[i].length);
+    ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; // White, transparent
+    ctx.lineWidth = 1;
+    ctx.stroke();
+    ctx.closePath();
+  }
+}
+
+
+function draw() {
+  drawBackground();
+
+  for (let i = 0; i < fish.length; i++) {
+    fish[i].update(fish);
+    fish[i].draw();
+
+     // Chase food if close enough
+    const closestFoodDistance = fish[i].x + fish[i].size > food[0].x - food[0].size &&
+                                fish[i].y + fish[i].size > food[0].y-food[0].size &&
+                                fish[i].x - fish[i].size < food[0].x+food[0].size &&
+                                fish[i].y - fish[i].size < food[0].y + food[0].size;
+
+
+    if(closestFoodDistance){
+      fish[i].angle = Math.atan2(food[0].y-fish[i].y, food[0].x-fish[i].x); // Chase the food directly
+      fish[i].speed = 1.5;
+
+    } else {
+       fish[i].speed = 0.5 + Math.random() * 1;
+    }
+  }
+
+
+   for (let i = 0; i < plants.length; i++){
+        plants[i].update();
+        plants[i].draw();
+   }
+
+  drawBubbles();
+  drawLightRays();
+
+   if(food.length > 0){
+     food[0].update();
+     food[0].draw();
+   }
+}
+
+
+function addFish() {
+    fish.push(new Fish(Math.random() * canvas.width, Math.random() * canvas.height, 20 + Math.random()*10));
+}
+
+// Event Handlers
+canvas.addEventListener('click', function(event) {
+  food.push(new Food(event.clientX, event.clientY));
+});
+
+document.getElementById('addFishButton').addEventListener('click', addFish);
+
+
+
+init();
+
+function animate() {
+  draw();
+  requestAnimationFrame(animate);
+}
+
+animate();
+
+
+window.addEventListener('resize', function(){
+    canvas.width = window.innerWidth;
+    canvas.height = window.innerHeight;
+    init(); // Reinitialize on resize
+});
+
+</script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ebf4f7984098/gemma3-12b.png b/data/artifacts/ebf4f7984098/gemma3-12b.png
new file mode 100644
index 0000000..492b13e
Binary files /dev/null and b/data/artifacts/ebf4f7984098/gemma3-12b.png differ
diff --git a/data/artifacts/ebf4f7984098/gpt.html b/data/artifacts/ebf4f7984098/gpt.html
new file mode 100644
index 0000000..6529118
--- /dev/null
+++ b/data/artifacts/ebf4f7984098/gpt.html
@@ -0,0 +1,581 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>Relaxing Virtual Aquarium</title>
+<style>
+html, body {
+    margin: 0;
+    padding: 0;
+    overflow: hidden;
+    background: radial-gradient(circle at top, #1c4b7a 0%, #021019 55%, #000509 100%);
+    font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+    height: 100%;
+}
+#ui {
+    position: absolute;
+    top: 12px;
+    left: 50%;
+    transform: translateX(-50%);
+    z-index: 10;
+    display: flex;
+    gap: 10px;
+    align-items: center;
+    color: #cfe9ff;
+    text-shadow: 0 1px 2px rgba(0,0,0,0.8);
+    font-size: 14px;
+}
+button {
+    background: rgba(10, 80, 150, 0.8);
+    border: 1px solid rgba(180, 220, 255, 0.8);
+    border-radius: 999px;
+    color: #e8f4ff;
+    padding: 6px 14px;
+    cursor: pointer;
+    font-weight: 500;
+    box-shadow: 0 2px 6px rgba(0,0,0,0.5);
+    backdrop-filter: blur(4px);
+}
+button:hover {
+    background: rgba(25, 110, 190, 0.95);
+}
+button:active {
+    transform: translateY(1px);
+    box-shadow: 0 1px 3px rgba(0,0,0,0.6);
+}
+#hint {
+    opacity: 0.8;
+}
+canvas {
+    display: block;
+}
+</style>
+</head>
+<body>
+<div id="ui">
+    <button id="addFishBtn">Add Fish</button>
+    <span id="hint">Click anywhere to drop food for the fish</span>
+</div>
+<canvas id="aquarium"></canvas>
+<script>
+(function() {
+    const canvas = document.getElementById('aquarium');
+    const ctx = canvas.getContext('2d');
+
+    function resize() {
+        const dpr = window.devicePixelRatio || 1;
+        const rect = canvas.getBoundingClientRect();
+        canvas.width = rect.width * dpr;
+        canvas.height = rect.height * dpr;
+        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+    }
+    function fullSize() {
+        canvas.style.width = window.innerWidth + 'px';
+        canvas.style.height = window.innerHeight + 'px';
+        resize();
+    }
+    fullSize();
+    window.addEventListener('resize', fullSize);
+
+    const fishes = [];
+    const bubbles = [];
+    const plants = [];
+    const foods = [];
+    const lightRays = [];
+    const maxFish = 32;
+
+    function rand(min, max) { return Math.random() * (max - min) + min; }
+
+    // Plant class
+    class Plant {
+        constructor(x, baseY, height, segments, phase) {
+            this.x = x;
+            this.baseY = baseY;
+            this.height = height;
+            this.segments = segments;
+            this.phase = phase;
+        }
+        draw(t) {
+            const sway = Math.sin(t * 0.0006 + this.phase) * 12;
+            ctx.beginPath();
+            ctx.moveTo(this.x, this.baseY);
+            for (let i = 1; i <= this.segments; i++) {
+                const p = i / this.segments;
+                const y = this.baseY - p * this.height;
+                const offset = sway * Math.pow(p, 1.5);
+                const thickness = 8 * (1 - p) + 1.5;
+                ctx.lineWidth = thickness;
+                const g = 100 + p * 80;
+                const b = 60 + p * 40;
+                ctx.strokeStyle = `rgba(0, ${g|0}, ${b|0}, 0.7)`;
+                ctx.lineTo(this.x + offset, y);
+            }
+            ctx.stroke();
+        }
+    }
+
+    // Bubble
+    class Bubble {
+        constructor(x, y, r, speed, wobbleAmp, wobbleFreq) {
+            this.x = x;
+            this.y = y;
+            this.baseX = x;
+            this.r = r;
+            this.speed = speed;
+            this.wobbleAmp = wobbleAmp;
+            this.wobbleFreq = wobbleFreq;
+            this.life = rand(4, 10);
+            this.age = 0;
+        }
+        update(dt) {
+            this.age += dt;
+            this.y -= this.speed * dt;
+            this.x = this.baseX + Math.sin(this.age * this.wobbleFreq) * this.wobbleAmp;
+            if (this.y + this.r < -20 || this.age > this.life) {
+                this.reset();
+            }
+        }
+        reset() {
+            this.y = canvas.height + rand(0, 80);
+            this.baseX = rand(0, canvas.width);
+            this.x = this.baseX;
+            this.r = rand(2, 7);
+            this.speed = rand(20, 60);
+            this.wobbleAmp = rand(2, 8);
+            this.wobbleFreq = rand(1, 3);
+            this.age = 0;
+            this.life = rand(4, 10);
+        }
+        draw() {
+            const alpha = Math.max(0, 1 - this.age / this.life);
+            ctx.beginPath();
+            ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
+            ctx.strokeStyle = `rgba(200, 235, 255, ${0.25 * alpha})`;
+            ctx.lineWidth = 1;
+            ctx.stroke();
+            const grad = ctx.createRadialGradient(this.x - this.r/2, this.y - this.r/2, 0, this.x, this.y, this.r);
+            grad.addColorStop(0, `rgba(255,255,255,${0.35*alpha})`);
+            grad.addColorStop(1, `rgba(180,220,255,0)`);
+            ctx.fillStyle = grad;
+            ctx.fill();
+        }
+    }
+
+    // Food pellet
+    class Food {
+        constructor(x, y) {
+            this.x = x;
+            this.y = y;
+            this.radius = 4;
+            this.vy = 0;
+            this.lifetime = 15;
+            this.age = 0;
+        }
+        update(dt) {
+            this.age += dt;
+            this.vy += 80 * dt;
+            this.y += this.vy * dt;
+            const ground = canvas.height - 40;
+            if (this.y > ground) {
+                this.y = ground;
+                this.vy *= -0.2;
+                if (Math.abs(this.vy) < 5) this.vy = 0;
+            }
+        }
+        get alive() { return this.age < this.lifetime; }
+        draw() {
+            const alpha = Math.max(0, 1 - (this.age / this.lifetime));
+            ctx.beginPath();
+            ctx.fillStyle = `rgba(180,140,80,${0.9*alpha})`;
+            ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
+            ctx.fill();
+            ctx.beginPath();
+            ctx.fillStyle = `rgba(255,235,200,${0.7*alpha})`;
+            ctx.arc(this.x - 1, this.y - 1, this.radius*0.45, 0, Math.PI*2);
+            ctx.fill();
+        }
+    }
+
+    // Fish
+    class Fish {
+        constructor(x, y, size, colorHue) {
+            this.pos = {x, y};
+            const angle = rand(0, Math.PI*2);
+            this.vel = {x: Math.cos(angle)*rand(20,50), y: Math.sin(angle)*rand(10,30)};
+            this.acc = {x:0,y:0};
+            this.size = size;
+            this.maxSpeed = rand(40, 70);
+            this.maxForce = 18;
+            this.colorHue = colorHue;
+            this.foodTarget = null;
+            this.hungryTimer = rand(3,10);
+            this.turnTimer = 0;
+        }
+
+        edges() {
+            const margin = 80;
+            if (this.pos.x < -margin) this.pos.x = canvas.width + margin;
+            if (this.pos.x > canvas.width + margin) this.pos.x = -margin;
+            if (this.pos.y < 40) { this.pos.y = 40; this.vel.y *= -0.5; }
+            if (this.pos.y > canvas.height - 40) { this.pos.y = canvas.height - 40; this.vel.y *= -0.5; }
+        }
+
+        seek(target, strength=1) {
+            const desired = {x: target.x - this.pos.x, y: target.y - this.pos.y};
+            const d = Math.hypot(desired.x, desired.y) || 0.0001;
+            desired.x /= d; desired.y /= d;
+            const speed = this.maxSpeed;
+            desired.x *= speed; desired.y *= speed;
+            let steer = {x: desired.x - this.vel.x, y: desired.y - this.vel.y};
+            const mag = Math.hypot(steer.x, steer.y) || 0.0001;
+            if (mag > this.maxForce) { steer.x = steer.x/mag*this.maxForce; steer.y = steer.y/mag*this.maxForce; }
+            steer.x *= strength; steer.y *= strength;
+            return steer;
+        }
+
+        flock(fishArray) {
+            const neighborDist = 120;
+            const separationDist = 45;
+
+            let sumPos = {x:0,y:0}, sumVel = {x:0,y:0}, steerSep = {x:0,y:0};
+            let count = 0, countSep = 0;
+            for (const other of fishArray) {
+                if (other === this) continue;
+                const dx = other.pos.x - this.pos.x;
+                const dy = other.pos.y - this.pos.y;
+                const d = Math.hypot(dx, dy);
+                if (d > 0 && d < neighborDist) {
+                    sumPos.x += other.pos.x;
+                    sumPos.y += other.pos.y;
+                    sumVel.x += other.vel.x;
+                    sumVel.y += other.vel.y;
+                    count++;
+                }
+                if (d > 0 && d < separationDist) {
+                    steerSep.x -= dx/d;
+                    steerSep.y -= dy/d;
+                    countSep++;
+                }
+            }
+            let align = {x:0,y:0}, cohesion = {x:0,y:0}, separation = {x:0,y:0};
+            if (count > 0) {
+                sumPos.x /= count; sumPos.y /= count;
+                sumVel.x /= count; sumVel.y /= count;
+                const dVel = Math.hypot(sumVel.x, sumVel.y) || 0.0001;
+                sumVel.x = sumVel.x/dVel*this.maxSpeed;
+                sumVel.y = sumVel.y/dVel*this.maxSpeed;
+                align.x = sumVel.x - this.vel.x;
+                align.y = sumVel.y - this.vel.y;
+                const aMag = Math.hypot(align.x, align.y) || 0.0001;
+                if (aMag > this.maxForce) { align.x = align.x/aMag*this.maxForce; align.y = align.y/aMag*this.maxForce; }
+
+                cohesion = this.seek(sumPos, 0.04);
+            }
+            if (countSep > 0) {
+                steerSep.x /= countSep; steerSep.y /= countSep;
+                const sMag = Math.hypot(steerSep.x, steerSep.y) || 0.0001;
+                if (sMag > this.maxForce) { steerSep.x = steerSep.x/sMag*this.maxForce; steerSep.y = steerSep.y/sMag*this.maxForce; }
+                separation = steerSep;
+            }
+            align.x *= 0.4; align.y *= 0.4;
+            cohesion.x *= 0.6; cohesion.y *= 0.6;
+            separation.x *= 1.3; separation.y *= 1.3;
+
+            this.acc.x += align.x + cohesion.x + separation.x;
+            this.acc.y += align.y + cohesion.y + separation.y;
+        }
+
+        findFood() {
+            if (!foods.length) { this.foodTarget = null; return; }
+            let best = null;
+            let bestD = Infinity;
+            for (const f of foods) {
+                const dx = f.x - this.pos.x;
+                const dy = f.y - this.pos.y;
+                const d = Math.hypot(dx, dy);
+                if (d < bestD) {
+                    bestD = d;
+                    best = f;
+                }
+            }
+            if (best && bestD < 260) {
+                this.foodTarget = best;
+            } else {
+                this.foodTarget = null;
+            }
+        }
+
+        update(dt, allFish) {
+            this.hungryTimer -= dt;
+            if (this.hungryTimer <= 0) {
+                this.hungryTimer = rand(4,10);
+                this.findFood();
+            } else if (this.foodTarget && !this.foodTarget.alive) {
+                this.foodTarget = null;
+            }
+
+            if (this.foodTarget) {
+                const steer = this.seek(this.foodTarget, 1.4);
+                this.acc.x += steer.x;
+                this.acc.y += steer.y;
+                const dx = this.foodTarget.x - this.pos.x;
+                const dy = this.foodTarget.y - this.pos.y;
+                const d = Math.hypot(dx, dy);
+                if (d < this.size * 0.9) {
+                    this.foodTarget.age = this.foodTarget.lifetime;
+                    this.foodTarget = null;
+                }
+            } else {
+                this.flock(allFish);
+            }
+
+            this.turnTimer -= dt;
+            if (this.turnTimer <= 0) {
+                this.turnTimer = rand(2,4);
+                this.acc.y += rand(-20,20);
+            }
+
+            this.vel.x += this.acc.x * dt;
+            this.vel.y += this.acc.y * dt;
+
+            const speed = Math.hypot(this.vel.x, this.vel.y) || 0.0001;
+            const max = this.maxSpeed;
+            if (speed > max) {
+                this.vel.x = this.vel.x / speed * max;
+                this.vel.y = this.vel.y / speed * max;
+            }
+
+            this.pos.x += this.vel.x * dt;
+            this.pos.y += this.vel.y * dt;
+            this.edges();
+            this.acc.x = 0; this.acc.y = 0;
+        }
+
+        draw() {
+            const angle = Math.atan2(this.vel.y, this.vel.x);
+            const bodyLength = this.size * 2.2;
+            const bodyHeight = this.size * 1.2;
+            ctx.save();
+            ctx.translate(this.pos.x, this.pos.y);
+            ctx.rotate(angle);
+
+            const gradBody = ctx.createLinearGradient(-this.size*0.4,0, bodyLength*0.7,0);
+            gradBody.addColorStop(0, `hsl(${this.colorHue}, 70%, 40%)`);
+            gradBody.addColorStop(0.5, `hsl(${this.colorHue+25}, 80%, 60%)`);
+            gradBody.addColorStop(1, `hsl(${this.colorHue+10}, 90%, 75%)`);
+
+            // Tail
+            ctx.beginPath();
+            ctx.moveTo(-bodyLength*0.3, 0);
+            ctx.quadraticCurveTo(-bodyLength*0.8, -bodyHeight*0.9, -bodyLength*0.9, 0);
+            ctx.quadraticCurveTo(-bodyLength*0.8, bodyHeight*0.9, -bodyLength*0.3, 0);
+            ctx.fillStyle = `hsla(${this.colorHue+30}, 80%, 65%, 0.8)`;
+            ctx.fill();
+
+            // Body
+            ctx.beginPath();
+            ctx.ellipse(0, 0, bodyLength*0.55, bodyHeight*0.62, 0, 0, Math.PI*2);
+            ctx.fillStyle = gradBody;
+            ctx.fill();
+
+            // Top fin
+            ctx.beginPath();
+            ctx.moveTo(-bodyLength*0.1, -bodyHeight*0.2);
+            ctx.quadraticCurveTo(0, -bodyHeight*0.9, bodyLength*0.3, -bodyHeight*0.4);
+            ctx.quadraticCurveTo(bodyLength*0.05, -bodyHeight*0.4, -bodyLength*0.1, -bodyHeight*0.2);
+            ctx.fillStyle = `hsla(${this.colorHue+10}, 80%, 70%, 0.6)`;
+            ctx.fill();
+
+            // Eye
+            const eyeX = bodyLength*0.35;
+            const eyeY = -bodyHeight*0.18;
+            ctx.beginPath();
+            ctx.fillStyle = 'rgba(255,255,255,0.9)';
+            ctx.arc(eyeX, eyeY, this.size*0.22, 0, Math.PI*2);
+            ctx.fill();
+            ctx.beginPath();
+            ctx.fillStyle = '#000';
+            ctx.arc(eyeX+this.size*0.05, eyeY, this.size*0.12, 0, Math.PI*2);
+            ctx.fill();
+
+            // Gill line
+            ctx.beginPath();
+            ctx.strokeStyle = 'rgba(0,0,0,0.25)';
+            ctx.lineWidth = 1;
+            ctx.arc(-bodyLength*0.02, 0, this.size*0.45, -0.7, 0.7);
+            ctx.stroke();
+
+            ctx.restore();
+        }
+    }
+
+    // Light ray
+    class LightRay {
+        constructor(x, width, angle, phase) {
+            this.x = x;
+            this.width = width;
+            this.angle = angle;
+            this.phase = phase;
+        }
+        draw(t) {
+            const sway = Math.sin(t*0.0002 + this.phase) * 0.12;
+            const finalAngle = this.angle + sway;
+            const len = canvas.height * 1.2;
+            const alpha = 0.12;
+            ctx.save();
+            ctx.translate(this.x, 0);
+            ctx.rotate(finalAngle);
+            const grad = ctx.createLinearGradient(0, 0, 0, len);
+            grad.addColorStop(0, `rgba(220,240,255,${alpha})`);
+            grad.addColorStop(0.3, `rgba(220,240,255,${alpha*0.4})`);
+            grad.addColorStop(1, 'rgba(220,240,255,0)');
+            ctx.fillStyle = grad;
+            ctx.beginPath();
+            ctx.moveTo(-this.width/2, 0);
+            ctx.lineTo(this.width/2, 0);
+            ctx.lineTo(this.width*0.3, len);
+            ctx.lineTo(-this.width*0.3, len);
+            ctx.closePath();
+            ctx.fill();
+            ctx.restore();
+        }
+    }
+
+    function initScene() {
+        plants.length = 0;
+        const numPlants = Math.max(6, Math.floor(canvas.width / 110));
+        const baseY = canvas.height - 20;
+        for (let i = 0; i < numPlants; i++) {
+            const x = (i + Math.random()*0.3) * (canvas.width / numPlants);
+            const h = rand(canvas.height*0.25, canvas.height*0.45);
+            const segments = 12;
+            const ph = rand(0, Math.PI*2);
+            plants.push(new Plant(x, baseY, h, segments, ph));
+        }
+
+        bubbles.length = 0;
+        const numBubbles = 60;
+        for (let i = 0; i < numBubbles; i++) {
+            bubbles.push(new Bubble(rand(0, canvas.width), rand(0, canvas.height+100),
+                rand(2,7), rand(20,60), rand(2,8), rand(1,3)));
+        }
+
+        lightRays.length = 0;
+        const numRays = 10;
+        for (let i = 0; i < numRays; i++) {
+            const x = rand(-canvas.width*0.2, canvas.width*1.2);
+            const w = rand(80, 180);
+            const angle = rand(-0.12, -0.02);
+            const phase = rand(0, Math.PI*2);
+            lightRays.push(new LightRay(x, w, angle, phase));
+        }
+
+        fishes.length = 0;
+        const baseFish = Math.max(8, Math.floor(canvas.width / 180));
+        for (let i = 0; i < baseFish; i++) addFish();
+    }
+
+    function addFish() {
+        if (fishes.length >= maxFish) return;
+        const x = rand(50, canvas.width-50);
+        const y = rand(canvas.height*0.25, canvas.height*0.85);
+        const size = rand(8, 15);
+        const hue = rand(180, 260) + rand(-20, 40);
+        fishes.push(new Fish(x, y, size, hue));
+    }
+
+    document.getElementById('addFishBtn').addEventListener('click', () => {
+        addFish();
+    });
+
+    canvas.addEventListener('click', (e) => {
+        const rect = canvas.getBoundingClientRect();
+        const x = (e.clientX - rect.left);
+        const y = (e.clientY - rect.top);
+        foods.push(new Food(x, y));
+    });
+
+    let lastTime = performance.now();
+    initScene();
+
+    function drawBackground(t) {
+        const w = canvas.width;
+        const h = canvas.height;
+
+        // Depth gradient water
+        const grad = ctx.createLinearGradient(0, 0, 0, h);
+        grad.addColorStop(0, '#143d67');
+        grad.addColorStop(0.4, '#0b2b45');
+        grad.addColorStop(1, '#02131f');
+        ctx.fillStyle = grad;
+        ctx.fillRect(0, 0, w, h);
+
+        // Caustic-ish soft overlay
+        ctx.save();
+        ctx.globalAlpha = 0.18;
+        const caustic = ctx.createRadialGradient(w*0.5, 0, 0, w*0.5, 0, h*0.9);
+        caustic.addColorStop(0, 'rgba(255,255,255,0.4)');
+        caustic.addColorStop(0.25, 'rgba(200,230,255,0.15)');
+        caustic.addColorStop(0.5, 'rgba(80,130,180,0.05)');
+        caustic.addColorStop(1, 'rgba(0,0,0,0)');
+        ctx.fillStyle = caustic;
+        ctx.fillRect(0, 0, w, h);
+        ctx.restore();
+
+        // Light rays
+        ctx.save();
+        ctx.globalCompositeOperation = 'screen';
+        for (const r of lightRays) r.draw(t);
+        ctx.restore();
+
+        // Seabed
+        const bedHeight = 55;
+        const bedGrad = ctx.createLinearGradient(0, h-bedHeight, 0, h);
+        bedGrad.addColorStop(0, 'rgba(60,40,30,0.4)');
+        bedGrad.addColorStop(1, 'rgba(20,10,5,0.9)');
+        ctx.fillStyle = bedGrad;
+        ctx.beginPath();
+        ctx.moveTo(0, h-bedHeight);
+        const steps = 12;
+        for (let i=0; i<=steps; i++) {
+            const x = (i/steps)*w;
+            const y = h - bedHeight + Math.sin(i*0.9 + t*0.0002)*5;
+            ctx.lineTo(x, y);
+        }
+        ctx.lineTo(w, h);
+        ctx.lineTo(0, h);
+        ctx.closePath();
+        ctx.fill();
+
+        // Plants
+        for (const p of plants) p.draw(t);
+    }
+
+    function update(dt, t) {
+        for (const b of bubbles) b.update(dt);
+        for (let i = foods.length-1; i>=0; i--) {
+            foods[i].update(dt);
+            if (!foods[i].alive) foods.splice(i,1);
+        }
+        for (const f of fishes) f.update(dt, fishes);
+    }
+
+    function draw(t) {
+        drawBackground(t);
+        for (const b of bubbles) b.draw();
+        for (const f of fishes) f.draw();
+        for (const fd of foods) fd.draw();
+    }
+
+    function loop(now) {
+        const dt = Math.min(0.05, (now - lastTime)/1000);
+        lastTime = now;
+        update(dt, now);
+        draw(now);
+        requestAnimationFrame(loop);
+    }
+    requestAnimationFrame(loop);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ebf4f7984098/gpt.png b/data/artifacts/ebf4f7984098/gpt.png
new file mode 100644
index 0000000..71c21a6
Binary files /dev/null and b/data/artifacts/ebf4f7984098/gpt.png differ
diff --git a/data/artifacts/ebf4f7984098/grok.html b/data/artifacts/ebf4f7984098/grok.html
new file mode 100644
index 0000000..d6f2dcb
--- /dev/null
+++ b/data/artifacts/ebf4f7984098/grok.html
@@ -0,0 +1,670 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Virtual Aquarium</title>
+<style>
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  html, body {
+    width: 100%; height: 100%;
+    overflow: hidden;
+    background: #001a22;
+    font-family: system-ui, -apple-system, sans-serif;
+  }
+  canvas {
+    display: block;
+    width: 100%; height: 100%;
+    cursor: crosshair;
+  }
+  #ui {
+    position: fixed;
+    bottom: 20px;
+    left: 50%;
+    transform: translateX(-50%);
+    z-index: 10;
+    display: flex;
+    gap: 12px;
+    align-items: center;
+  }
+  button {
+    background: linear-gradient(180deg, rgba(80,180,200,0.85), rgba(30,120,140,0.9));
+    border: 1px solid rgba(150,230,255,0.4);
+    color: #e8ffff;
+    padding: 10px 22px;
+    border-radius: 24px;
+    font-size: 14px;
+    font-weight: 600;
+    cursor: pointer;
+    backdrop-filter: blur(8px);
+    box-shadow: 0 4px 20px rgba(0,40,60,0.4), inset 0 1px 0 rgba(255,255,255,0.2);
+    transition: transform 0.15s, box-shadow 0.15s;
+    letter-spacing: 0.03em;
+  }
+  button:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 6px 28px rgba(0,60,80,0.5), inset 0 1px 0 rgba(255,255,255,0.3);
+  }
+  button:active { transform: translateY(0); }
+  #hint {
+    position: fixed;
+    top: 16px;
+    left: 50%;
+    transform: translateX(-50%);
+    color: rgba(180,230,255,0.55);
+    font-size: 13px;
+    pointer-events: none;
+    letter-spacing: 0.04em;
+    text-shadow: 0 1px 8px rgba(0,0,0,0.5);
+  }
+</style>
+</head>
+<body>
+<canvas id="c"></canvas>
+<div id="hint">Click to drop food · Fish flock &amp; chase</div>
+<div id="ui">
+  <button id="addFish" type="button">+ Add Fish</button>
+  <button id="addSchool" type="button">+ School</button>
+</div>
+<script>
+(function () {
+  const canvas = document.getElementById('c');
+  const ctx = canvas.getContext('2d');
+
+  let W, H, dpr;
+  function resize() {
+    dpr = Math.min(window.devicePixelRatio || 1, 2);
+    W = window.innerWidth;
+    H = window.innerHeight;
+    canvas.width = W * dpr;
+    canvas.height = H * dpr;
+    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+  }
+  resize();
+  window.addEventListener('resize', resize);
+
+  // ─── Utilities ───────────────────────────────────────
+  const TAU = Math.PI * 2;
+  const rand = (a, b) => a + Math.random() * (b - a);
+  const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
+  const lerp = (a, b, t) => a + (b - a) * t;
+
+  function hsl(h, s, l, a) {
+    return a == null ? `hsl(${h},${s}%,${l}%)` : `hsla(${h},${s}%,${l}%,${a})`;
+  }
+
+  // ─── Light rays ──────────────────────────────────────
+  const rays = [];
+  for (let i = 0; i < 7; i++) {
+    rays.push({
+      x: rand(0.05, 0.95),
+      w: rand(0.04, 0.12),
+      speed: rand(0.00015, 0.0004),
+      phase: rand(0, TAU),
+      alpha: rand(0.03, 0.08)
+    });
+  }
+
+  // ─── Bubbles ─────────────────────────────────────────
+  const bubbles = [];
+  function spawnBubble(x, y) {
+    bubbles.push({
+      x: x ?? rand(0, W),
+      y: y ?? H + 10,
+      r: rand(2, 7),
+      speed: rand(0.4, 1.4),
+      wobble: rand(0.5, 2),
+      phase: rand(0, TAU),
+      alpha: rand(0.15, 0.45)
+    });
+  }
+  for (let i = 0; i < 30; i++) spawnBubble(rand(0, W), rand(0, H));
+
+  // ─── Plants ──────────────────────────────────────────
+  const plants = [];
+  function makePlant(x) {
+    const segs = Math.floor(rand(6, 12));
+    const h = rand(H * 0.25, H * 0.55);
+    const cols = [
+      [100, 55, 35], [130, 50, 30], [90, 60, 28],
+      [140, 45, 38], [110, 50, 32]
+    ];
+    const c = cols[Math.floor(rand(0, cols.length))];
+    return {
+      x, segs, h,
+      baseW: rand(8, 18),
+      phase: rand(0, TAU),
+      speed: rand(0.6, 1.4),
+      hue: c[0], sat: c[1], lit: c[2],
+      lean: rand(-0.3, 0.3)
+    };
+  }
+  function initPlants() {
+    plants.length = 0;
+    const n = Math.floor(W / 55);
+    for (let i = 0; i < n; i++) {
+      plants.push(makePlant(rand(10, W - 10)));
+    }
+  }
+  initPlants();
+  window.addEventListener('resize', () => { initPlants(); });
+
+  // ─── Food ────────────────────────────────────────────
+  const foods = [];
+  function dropFood(x, y) {
+    const n = Math.floor(rand(3, 7));
+    for (let i = 0; i < n; i++) {
+      foods.push({
+        x: x + rand(-18, 18),
+        y: y + rand(-8, 8),
+        vx: rand(-0.5, 0.5),
+        vy: rand(0.3, 1.2),
+        r: rand(2.5, 5),
+        life: 1,
+        eaten: false,
+        hue: rand(30, 50)
+      });
+    }
+  }
+
+  canvas.addEventListener('click', (e) => {
+    const rect = canvas.getBoundingClientRect();
+    dropFood(e.clientX - rect.left, e.clientY - rect.top);
+  });
+
+  // ─── Fish ────────────────────────────────────────────
+  const fish = [];
+  const FISH_COLORS = [
+    [10, 80, 55], [200, 70, 55], [40, 90, 55], [320, 65, 60],
+    [170, 60, 45], [280, 55, 55], [0, 75, 50], [50, 85, 50],
+    [190, 50, 60], [25, 90, 58]
+  ];
+
+  function makeFish(x, y, schoolId) {
+    const col = FISH_COLORS[Math.floor(rand(0, FISH_COLORS.length))];
+    const size = rand(14, 28);
+    return {
+      x: x ?? rand(0, W),
+      y: y ?? rand(H * 0.15, H * 0.75),
+      vx: rand(-1.5, 1.5),
+      vy: rand(-0.5, 0.5),
+      size,
+      maxSpeed: rand(1.6, 2.8) * (30 / size),
+      maxForce: rand(0.04, 0.09),
+      hue: col[0], sat: col[1], lit: col[2],
+      tailPhase: rand(0, TAU),
+      tailSpeed: rand(0.15, 0.28),
+      schoolId: schoolId ?? 0,
+      turn: 0,
+      bodyWave: rand(0, TAU)
+    };
+  }
+
+  function addFish(n, schoolId) {
+    const sid = schoolId ?? Math.floor(rand(0, 1000));
+    const cx = rand(W * 0.2, W * 0.8);
+    const cy = rand(H * 0.2, H * 0.6);
+    for (let i = 0; i < n; i++) {
+      fish.push(makeFish(cx + rand(-40, 40), cy + rand(-30, 30), sid));
+    }
+  }
+
+  addFish(8, 1);
+  addFish(6, 2);
+  addFish(5, 3);
+
+  document.getElementById('addFish').addEventListener('click', (e) => {
+    e.stopPropagation();
+    addFish(1, Math.floor(rand(0, 5)));
+  });
+  document.getElementById('addSchool').addEventListener('click', (e) => {
+    e.stopPropagation();
+    addFish(Math.floor(rand(5, 9)), Math.floor(rand(0, 10000)));
+  });
+
+  // ─── Steering ────────────────────────────────────────
+  function limit(vx, vy, max) {
+    const m = Math.hypot(vx, vy);
+    if (m > max && m > 0) {
+      return { x: (vx / m) * max, y: (vy / m) * max };
+    }
+    return { x: vx, y: vy };
+  }
+
+  function steer(f, tx, ty, weight) {
+    let dx = tx - f.x, dy = ty - f.y;
+    const d = Math.hypot(dx, dy) || 1;
+    dx = (dx / d) * f.maxSpeed - f.vx;
+    dy = (dy / d) * f.maxSpeed - f.vy;
+    const lim = limit(dx, dy, f.maxForce);
+    f.vx += lim.x * weight;
+    f.vy += lim.y * weight;
+  }
+
+  function flock(f, idx) {
+    let sepX = 0, sepY = 0, sepN = 0;
+    let aliX = 0, aliY = 0, aliN = 0;
+    let cohX = 0, cohY = 0, cohN = 0;
+    const sepR = f.size * 2.2;
+    const aliR = 80;
+    const cohR = 120;
+
+    for (let j = 0; j < fish.length; j++) {
+      if (j === idx) continue;
+      const o = fish[j];
+      const dx = f.x - o.x, dy = f.y - o.y;
+      const d = Math.hypot(dx, dy);
+      if (d < 0.01) continue;
+
+      if (d < sepR) {
+        sepX += dx / d; sepY += dy / d; sepN++;
+      }
+      if (o.schoolId === f.schoolId) {
+        if (d < aliR) { aliX += o.vx; aliY += o.vy; aliN++; }
+        if (d < cohR) { cohX += o.x; cohY += o.y; cohN++; }
+      }
+    }
+
+    if (sepN > 0) {
+      sepX /= sepN; sepY /= sepN;
+      const m = Math.hypot(sepX, sepY) || 1;
+      sepX = (sepX / m) * f.maxSpeed - f.vx;
+      sepY = (sepY / m) * f.maxSpeed - f.vy;
+      const lim = limit(sepX, sepY, f.maxForce);
+      f.vx += lim.x * 1.6;
+      f.vy += lim.y * 1.6;
+    }
+    if (aliN > 0) {
+      aliX /= aliN; aliY /= aliN;
+      const m = Math.hypot(aliX, aliY) || 1;
+      aliX = (aliX / m) * f.maxSpeed - f.vx;
+      aliY = (aliY / m) * f.maxSpeed - f.vy;
+      const lim = limit(aliX, aliY, f.maxForce);
+      f.vx += lim.x * 0.9;
+      f.vy += lim.y * 0.9;
+    }
+    if (cohN > 0) {
+      cohX = cohX / cohN - f.x;
+      cohY = cohY / cohN - f.y;
+      const m = Math.hypot(cohX, cohY) || 1;
+      cohX = (cohX / m) * f.maxSpeed - f.vx;
+      cohY = (cohY / m) * f.maxSpeed - f.vy;
+      const lim = limit(cohX, cohY, f.maxForce);
+      f.vx += lim.x * 0.7;
+      f.vy += lim.y * 0.7;
+    }
+  }
+
+  function seekFood(f) {
+    let best = null, bestD = 200;
+    for (const food of foods) {
+      if (food.eaten || food.life <= 0) continue;
+      const d = Math.hypot(food.x - f.x, food.y - f.y);
+      if (d < bestD) { bestD = d; best = food; }
+    }
+    if (best) {
+      const urgency = 1 + (1 - bestD / 200) * 1.5;
+      steer(f, best.x, best.y, urgency);
+      if (bestD < f.size * 0.7) {
+        best.eaten = true;
+        f.size = Math.min(f.size + 0.3, 36);
+      }
+    }
+  }
+
+  function boundaries(f) {
+    const m = 60;
+    let fx = 0, fy = 0;
+    if (f.x < m) fx = f.maxSpeed;
+    else if (f.x > W - m) fx = -f.maxSpeed;
+    if (f.y < m) fy = f.maxSpeed;
+    else if (f.y > H - m * 1.5) fy = -f.maxSpeed;
+    if (fx || fy) {
+      const lim = limit(fx - f.vx, fy - f.vy, f.maxForce * 2);
+      f.vx += lim.x;
+      f.vy += lim.y;
+    }
+  }
+
+  function wander(f, t) {
+    f.turn += rand(-0.03, 0.03);
+    f.turn *= 0.95;
+    const angle = Math.atan2(f.vy, f.vx) + f.turn;
+    const wx = Math.cos(angle + Math.sin(t * 0.001 + f.bodyWave) * 0.3);
+    const wy = Math.sin(angle + Math.sin(t * 0.001 + f.bodyWave) * 0.3);
+    f.vx += wx * 0.01;
+    f.vy += wy * 0.01;
+  }
+
+  // ─── Draw fish ───────────────────────────────────────
+  function drawFish(f) {
+    const angle = Math.atan2(f.vy, f.vx);
+    const speed = Math.hypot(f.vx, f.vy);
+    f.tailPhase += f.tailSpeed * (0.8 + speed * 0.4);
+    const tailSwing = Math.sin(f.tailPhase) * 0.45;
+    const s = f.size;
+
+    ctx.save();
+    ctx.translate(f.x, f.y);
+    ctx.rotate(angle);
+
+    // shadow
+    ctx.fillStyle = 'rgba(0,20,40,0.15)';
+    ctx.beginPath();
+    ctx.ellipse(2, 4, s * 0.9, s * 0.38, 0, 0, TAU);
+    ctx.fill();
+
+    // tail
+    ctx.fillStyle = hsl(f.hue, f.sat, f.lit - 8);
+    ctx.beginPath();
+    ctx.moveTo(-s * 0.55, 0);
+    ctx.quadraticCurveTo(-s * 0.7, -s * 0.35 + tailSwing * s * 0.3, -s * 1.15, -s * 0.4 + tailSwing * s);
+    ctx.quadraticCurveTo(-s * 0.85, 0, -s * 1.15, s * 0.4 + tailSwing * s);
+    ctx.quadraticCurveTo(-s * 0.7, s * 0.35 + tailSwing * s * 0.3, -s * 0.55, 0);
+    ctx.fill();
+
+    // body gradient
+    const grad = ctx.createRadialGradient(-s * 0.1, -s * 0.1, 0, 0, 0, s);
+    grad.addColorStop(0, hsl(f.hue, f.sat, f.lit + 12));
+    grad.addColorStop(0.5, hsl(f.hue, f.sat, f.lit));
+    grad.addColorStop(1, hsl(f.hue, f.sat + 5, f.lit - 15));
+    ctx.fillStyle = grad;
+    ctx.beginPath();
+    ctx.ellipse(0, 0, s * 0.75, s * 0.38, 0, 0, TAU);
+    ctx.fill();
+
+    // belly highlight
+    ctx.fillStyle = hsl(f.hue, f.sat - 20, f.lit + 20, 0.35);
+    ctx.beginPath();
+    ctx.ellipse(s * 0.05, s * 0.12, s * 0.5, s * 0.15, 0, 0, TAU);
+    ctx.fill();
+
+    // dorsal fin
+    ctx.fillStyle = hsl(f.hue, f.sat, f.lit - 5, 0.85);
+    ctx.beginPath();
+    ctx.moveTo(-s * 0.1, -s * 0.3);
+    ctx.quadraticCurveTo(s * 0.05, -s * 0.65, s * 0.25, -s * 0.28);
+    ctx.quadraticCurveTo(0, -s * 0.35, -s * 0.1, -s * 0.3);
+    ctx.fill();
+
+    // pectoral fin
+    ctx.fillStyle = hsl(f.hue, f.sat - 10, f.lit, 0.6);
+    ctx.beginPath();
+    ctx.moveTo(s * 0.05, s * 0.1);
+    ctx.quadraticCurveTo(s * 0.1, s * 0.45 + Math.sin(f.tailPhase * 0.7) * 3, -s * 0.15, s * 0.35);
+    ctx.quadraticCurveTo(0, s * 0.2, s * 0.05, s * 0.1);
+    ctx.fill();
+
+    // eye
+    ctx.fillStyle = '#fff';
+    ctx.beginPath();
+    ctx.arc(s * 0.4, -s * 0.08, s * 0.1, 0, TAU);
+    ctx.fill();
+    ctx.fillStyle = '#111';
+    ctx.beginPath();
+    ctx.arc(s * 0.43, -s * 0.08, s * 0.055, 0, TAU);
+    ctx.fill();
+    ctx.fillStyle = 'rgba(255,255,255,0.7)';
+    ctx.beginPath();
+    ctx.arc(s * 0.45, -s * 0.11, s * 0.025, 0, TAU);
+    ctx.fill();
+
+    // gill line
+    ctx.strokeStyle = hsl(f.hue, f.sat, f.lit - 20, 0.4);
+    ctx.lineWidth = 1;
+    ctx.beginPath();
+    ctx.arc(s * 0.15, 0, s * 0.2, -0.8, 0.8);
+    ctx.stroke();
+
+    ctx.restore();
+  }
+
+  // ─── Draw plants ─────────────────────────────────────
+  function drawPlant(p, t) {
+    const sway = Math.sin(t * 0.001 * p.speed + p.phase) * 0.35 + p.lean;
+    ctx.strokeStyle = hsl(p.hue, p.sat, p.lit);
+    ctx.fillStyle = hsl(p.hue, p.sat, p.lit, 0.7);
+    ctx.lineCap = 'round';
+    ctx.lineJoin = 'round';
+
+    let x = p.x, y = H;
+    const segH = p.h / p.segs;
+    const pts = [{ x, y }];
+
+    for (let i = 0; i < p.segs; i++) {
+      const frac = i / p.segs;
+      const sw = sway * (0.3 + frac * 1.2) + Math.sin(t * 0.0015 * p.speed + p.phase + i * 0.6) * 0.15 * frac;
+      x += Math.sin(sw) * segH * 0.9;
+      y -= segH * Math.cos(sw * 0.3);
+      pts.push({ x, y });
+    }
+
+    // stem
+    ctx.beginPath();
+    ctx.moveTo(pts[0].x, pts[0].y);
+    for (let i = 1; i < pts.length; i++) {
+      const prev = pts[i - 1];
+      const cur = pts[i];
+      const mx = (prev.x + cur.x) / 2;
+      const my = (prev.y + cur.y) / 2;
+      ctx.quadraticCurveTo(prev.x, prev.y, mx, my);
+    }
+    ctx.lineWidth = p.baseW * 0.35;
+    ctx.strokeStyle = hsl(p.hue, p.sat, p.lit - 5);
+    ctx.stroke();
+
+    // leaves
+    for (let i = 2; i < pts.length; i++) {
+      const frac = i / pts.length;
+      const pt = pts[i];
+      const prev = pts[i - 1];
+      const dx = pt.x - prev.x, dy = pt.y - prev.y;
+      const len = Math.hypot(dx, dy) || 1;
+      const nx = -dy / len, ny = dx / len;
+      const leafLen = p.baseW * (1.2 - frac * 0.4) * rand(0.9, 1.1);
+      const side = i % 2 === 0 ? 1 : -1;
+
+      ctx.beginPath();
+      ctx.moveTo(pt.x, pt.y);
+      const cx1 = pt.x + nx * leafLen * side * 0.6 + dx * 0.3;
+      const cy1 = pt.y + ny * leafLen * side * 0.6 + dy * 0.3;
+      const tipX = pt.x + nx * leafLen * side + dx * 0.5;
+      const tipY = pt.y + ny * leafLen * side + dy * 0.5;
+      ctx.quadraticCurveTo(cx1, cy1, tipX, tipY);
+      ctx.quadraticCurveTo(
+        pt.x + nx * leafLen * side * 0.3 + dx * 0.4,
+        pt.y + ny * leafLen * side * 0.3 + dy * 0.4,
+        pt.x, pt.y
+      );
+      ctx.fillStyle = hsl(p.hue + (i % 3) * 5, p.sat, p.lit + frac * 8, 0.75);
+      ctx.fill();
+    }
+  }
+
+  // ─── Background ──────────────────────────────────────
+  function drawBackground(t) {
+    // water gradient
+    const bg = ctx.createLinearGradient(0, 0, 0, H);
+    bg.addColorStop(0, '#0a3a4a');
+    bg.addColorStop(0.3, '#0c4a5c');
+    bg.addColorStop(0.6, '#0a3550');
+    bg.addColorStop(1, '#062030');
+    ctx.fillStyle = bg;
+    ctx.fillRect(0, 0, W, H);
+
+    // subtle caustic noise bands
+    for (let i = 0; i < 5; i++) {
+      const y = ((t * 0.01 * (i + 1) * 0.3 + i * H * 0.25) % (H + 100)) - 50;
+      const g = ctx.createRadialGradient(W * (0.2 + i * 0.15), y, 0, W * (0.2 + i * 0.15), y, W * 0.4);
+      g.addColorStop(0, 'rgba(60,180,200,0.03)');
+      g.addColorStop(1, 'rgba(60,180,200,0)');
+      ctx.fillStyle = g;
+      ctx.fillRect(0, 0, W, H);
+    }
+
+    // light rays
+    for (const ray of rays) {
+      const ox = (ray.x + Math.sin(t * ray.speed + ray.phase) * 0.08) * W;
+      const topW = ray.w * W * 0.5;
+      const botW = ray.w * W * 1.8;
+      const g = ctx.createLinearGradient(ox, 0, ox, H);
+      g.addColorStop(0, `rgba(180,230,255,${ray.alpha * 1.4})`);
+      g.addColorStop(0.5, `rgba(140,210,230,${ray.alpha * 0.5})`);
+      g.addColorStop(1, `rgba(100,180,200,0)`);
+      ctx.fillStyle = g;
+      ctx.beginPath();
+      ctx.moveTo(ox - topW, 0);
+      ctx.lineTo(ox + topW, 0);
+      ctx.lineTo(ox + botW + Math.sin(t * 0.0003 + ray.phase) * 30, H);
+      ctx.lineTo(ox - botW + Math.sin(t * 0.0003 + ray.phase) * 30, H);
+      ctx.closePath();
+      ctx.fill();
+    }
+
+    // sandy bottom
+    const sand = ctx.createLinearGradient(0, H * 0.85, 0, H);
+    sand.addColorStop(0, 'rgba(40,70,60,0)');
+    sand.addColorStop(0.4, 'rgba(50,80,55,0.3)');
+    sand.addColorStop(1, 'rgba(60,90,50,0.5)');
+    ctx.fillStyle = sand;
+    ctx.fillRect(0, H * 0.85, W, H * 0.15);
+
+    // floor pebbles
+    ctx.fillStyle = 'rgba(70,100,70,0.25)';
+    for (let i = 0; i < 40; i++) {
+      const px = ((i * 137.5) % W);
+      const py = H - 5 - ((i * 47) % 20);
+      const pr = 2 + (i % 5);
+      ctx.beginPath();
+      ctx.ellipse(px, py, pr, pr * 0.6, 0, 0, TAU);
+      ctx.fill();
+    }
+  }
+
+  // ─── Bubbles draw/update ─────────────────────────────
+  function updateBubbles() {
+    if (Math.random() < 0.08) spawnBubble();
+    if (Math.random() < 0.02) spawnBubble(rand(0, W), H - rand(0, 30));
+
+    for (let i = bubbles.length - 1; i >= 0; i--) {
+      const b = bubbles[i];
+      b.phase += 0.04;
+      b.y -= b.speed;
+      b.x += Math.sin(b.phase) * b.wobble * 0.3;
+      if (b.y < -20) {
+        bubbles.splice(i, 1);
+        continue;
+      }
+      ctx.beginPath();
+      ctx.arc(b.x, b.y, b.r, 0, TAU);
+      ctx.strokeStyle = `rgba(200,240,255,${b.alpha})`;
+      ctx.lineWidth = 1.2;
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.arc(b.x - b.r * 0.3, b.y - b.r * 0.3, b.r * 0.3, 0, TAU);
+      ctx.fillStyle = `rgba(220,250,255,${b.alpha * 0.6})`;
+      ctx.fill();
+    }
+  }
+
+  // ─── Food update ─────────────────────────────────────
+  function updateFood() {
+    for (let i = foods.length - 1; i >= 0; i--) {
+      const f = foods[i];
+      if (f.eaten) { foods.splice(i, 1); continue; }
+      f.vy += 0.015;
+      f.vx *= 0.99;
+      f.x += f.vx;
+      f.y += f.vy;
+      if (f.y > H - 12) {
+        f.y = H - 12;
+        f.vy *= -0.3;
+        f.vx *= 0.8;
+        f.life -= 0.002;
+      }
+      f.life -= 0.0004;
+      if (f.life <= 0) { foods.splice(i, 1); continue; }
+
+      ctx.beginPath();
+      ctx.arc(f.x, f.y, f.r * f.life, 0, TAU);
+      ctx.fillStyle = hsl(f.hue, 80, 55, 0.7 * f.life);
+      ctx.fill();
+      ctx.beginPath();
+      ctx.arc(f.x - 1, f.y - 1, f.r * 0.35 * f.life, 0, TAU);
+      ctx.fillStyle = `rgba(255,255,200,${0.5 * f.life})`;
+      ctx.fill();
+    }
+  }
+
+  // ─── Main loop ───────────────────────────────────────
+  let last = performance.now();
+  function frame(now) {
+    const dt = Math.min(now - last, 50);
+    last = now;
+    const t = now;
+
+    drawBackground(t);
+
+    // plants behind fish
+    for (const p of plants) drawPlant(p, t);
+
+    // food
+    updateFood();
+
+    // fish logic
+    for (let i = 0; i < fish.length; i++) {
+      const f = fish[i];
+      flock(f, i);
+      seekFood(f);
+      boundaries(f);
+      wander(f, t);
+
+      // limit speed
+      const sp = Math.hypot(f.vx, f.vy);
+      const mx = f.maxSpeed * (foods.length > 0 ? 1.4 : 1);
+      if (sp > mx) {
+        f.vx = (f.vx / sp) * mx;
+        f.vy = (f.vy / sp) * mx;
+      }
+      // min speed
+      if (sp < 0.4) {
+        const a = Math.atan2(f.vy, f.vx);
+        f.vx += Math.cos(a) * 0.05;
+        f.vy += Math.sin(a) * 0.05;
+      }
+
+      f.x += f.vx * (dt / 16);
+      f.y += f.vy * (dt / 16);
+    }
+
+    // depth-sort fish roughly by y
+    const sorted = fish.slice().sort((a, b) => a.y - b.y);
+    for (const f of sorted) drawFish(f);
+
+    // bubbles on top
+    updateBubbles();
+
+    // vignette
+    const vig = ctx.createRadialGradient(W / 2, H / 2, H * 0.3, W / 2, H / 2, H * 0.85);
+    vig.addColorStop(0, 'rgba(0,0,0,0)');
+    vig.addColorStop(1, 'rgba(0,10,20,0.35)');
+    ctx.fillStyle = vig;
+    ctx.fillRect(0, 0, W, H);
+
+    // surface shimmer line
+    ctx.strokeStyle = 'rgba(150,220,255,0.06)';
+    ctx.lineWidth = 2;
+    ctx.beginPath();
+    for (let x = 0; x < W; x += 8) {
+      const yy = 8 + Math.sin(x * 0.02 + t * 0.002) * 3 + Math.sin(x * 0.05 + t * 0.003) * 2;
+      if (x === 0) ctx.moveTo(x, yy); else ctx.lineTo(x, yy);
+    }
+    ctx.stroke();
+
+    requestAnimationFrame(frame);
+  }
+  requestAnimationFrame(frame);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ebf4f7984098/grok.png b/data/artifacts/ebf4f7984098/grok.png
new file mode 100644
index 0000000..d85b440
Binary files /dev/null and b/data/artifacts/ebf4f7984098/grok.png differ
diff --git a/data/artifacts/ebf4f7984098/hermes3-8b.html b/data/artifacts/ebf4f7984098/hermes3-8b.html
new file mode 100644
index 0000000..b956775
--- /dev/null
+++ b/data/artifacts/ebf4f7984098/hermes3-8b.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Relaxing Virtual Aquarium</title>
+    <style>
+        body {
+            margin: 0;
+            padding: 0;
+            overflow: hidden;
+        }
+        
+        canvas {
+            display: block;
+        }
+
+        .fish {
+            cursor: pointer;
+            fill: #666;
+            stroke: none;
+        }
+
+        .bubble {
+            cursor: pointer;
+            clip-path: polygon(50% 25%, 75% 0%, 75% 50%, 75% 100%, 50% 75%, 25% 100%, 25% 50%, 25% 0%);
+            fill: rgba(255, 255, 255, 0.7);
+        }
+    </style>
+</head>
+<body>
+    <canvas id="aquarium"></canvas>
+    <button onclick="addFish()">Add Fish</button>
+    <script>
+        const canvas = document.getElementById('aquarium');
+        const ctx = canvas.getContext('2d');
+
+        canvas.width = window.innerWidth * 0.9;
+        canvas.height = window.innerHeight * 0.7;
+
+        let food, fish, bubbles;
+
+        class Food {
+            constructor() {
+                this.x = Math.random() * (canvas.width - 20);
+                this.y = Math.random() * (canvas.height - 20);
+            }
+        }
+
+        class Fish {
+            constructor() {
+                this.x = Math.random() * (canvas.width - 40);
+                this.y = Math.random() * (canvas.height - 40);
+                this.speedX = Math.random() * 2 - 1;
+                this.speedY = Math.random() * 2 - 1;
+                this.size = Math.random() * 0.5 + 0.5;
+            }
+
+            move(fish) {
+                this.x += this.speedX;
+                this.y += this.speedY;
+
+                if (this.x <= 0 || this.x >= canvas.width - this.size) {
+                    this.speedX = -this.speedX;
+                }
+                if (this.y <= 0 || this.y >= canvas.height - this.size) {
+                    this.speedY = -this.speedY;
+                }
+
+                for (let f of fish) {
+                    if (f !== this) {
+                        const dx = f.x - this.x;
+                        const dy = f.y - this.y;
+
+                        if (Math.sqrt(dx * dx + dy * dy) < 20) {
+                            this.speedX = -this.speedX;
+                            this.speedY = -this.speedY;
+                        }
+                    }
+                }
+            }
+
+            draw(ctx, fish) {
+                ctx.beginPath();
+                ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
+                ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
+                ctx.fill();
+
+                if (this === food) {
+                    ctx.fillStyle = 'rgba(255, 165, 0, 0.9)';
+                    ctx.fill();
+                }
+            }
+        }
+
+        class Bubble {
+            constructor() {
+                this.x = Math.random() * canvas.width;
+                this.y = -10;
+                this.speedY = Math.random() * 3 + 1;
+            }
+
+            move() {
+                this.y += this.speedY;
+
+                if (this.y > canvas.height) {
+                    this.y = 0;
+                    this.x = Math.random() * canvas.width;
+                    this.speedY = Math.random() * 3 + 1;
+                }
+            }
+
+            draw(ctx) {
+                ctx.beginPath();
+                this.move();
+                ctx.arc(this.x, this.y, 5, 0, 2 * Math.PI);
+                ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
+                ctx.fill();
+            }
+        }
+
+        function draw() {
+            ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+            food.move(food);
+            fish.forEach((f) => f.move(fish));
+            food.draw(ctx, fish);
+            fish.forEach((f) => f.draw(ctx, fish));
+
+            bubbles.forEach((b) => b.move());
+            bubbles.forEach((b) => b.draw(ctx));
+        }
+
+        function addFood() {
+            if (food) return;
+            food = new Food();
+        }
+
+        canvas.addEventListener('click', addFood);
+
+        const numFish = 10;
+        fish = Array.from({ length: numFish }, () => new Fish());
+        bubbles = Array.from({ length: 20 }, () => new Bubble());
+
+        function gameLoop() {
+            draw();
+            requestAnimationFrame(gameLoop);
+        }
+
+        gameLoop();
+
+        function addFish() {
+            fish.push(new Fish());
+        }
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ebf4f7984098/hermes3-8b.png b/data/artifacts/ebf4f7984098/hermes3-8b.png
new file mode 100644
index 0000000..d931418
Binary files /dev/null and b/data/artifacts/ebf4f7984098/hermes3-8b.png differ
diff --git a/data/artifacts/ebf4f7984098/hf-qwen-coder-32b.html b/data/artifacts/ebf4f7984098/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..51597fe
--- /dev/null
+++ b/data/artifacts/ebf4f7984098/hf-qwen-coder-32b.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Relaxing Virtual Aquarium</title>
+<style>
+body,html{margin:0;padding:0;overflow:hidden;width:100%;height:100%}canvas{display:block}
+button{position:absolute;top:10px;left:10px;z-index:100;background-color:rgba(255,255,255,0.8);border:none;padding:5px 10px;cursor:pointer;font-family:sans-serif}
+</style>
+</head>
+<body>
+<canvas id="aquarium"></canvas>
+<button onclick="addFish()">Add Fish</button>
+<script>
+const canvas=document.getElementById('aquarium');const ctx=canvas.getContext('2d');canvas.width=window.innerWidth;canvas.height=window.innerHeight;let fish=[],bubbles=[],plants=[],food=[];class Vector{constructor(x,y){this.x=x;this.y=y}add(v){return new Vector(this.x+v.x,this.y+v.y)}sub(v){return new Vector(this.x-v.x,this.y-v.y)}mult(s){return new Vector(this.x*s,this.y*s)}mag(){return Math.sqrt(this.x*this.x+this.y*this.y)}limit(max){const mag=this.mag();if(mag>max)this.mult(max/mag);return this}normalize(){const m=this.mag();if(m!==0)this.mult(1/m)}}class Fish{constructor(x,y){this.pos=new Vector(x,y);this.vel=new Vector(Math.random()*2-1,Math.random()*2-1).limit(2);this.acc=new Vector(0,0);this.maxForce=0.1;this.size=5}update(){this.applyBehaviors();this.vel.add(this.acc);this.vel.limit(3);this.pos.add(this.vel);this.acc.mult(0);if(this.pos.x>canvas.width)this.pos.x=0;if(this.pos.x<0)this.pos.x=canvas.width;if(this.pos.y>canvas.height)this.pos.y=0;if(this.pos.y<0)this.pos.y=canvas.height}applyBehaviors(){const seek=this.seek(food);seek.mult(2);this.applyForce(seek)}seek(target){if(target.length===0)return new Vector(0,0);let closest=null;let record=Infinity;for(let i=0;i<target.length;i++){const d=this.pos.dist(target[i].pos);if(d<record){closest=target[i];record=d}}if(closest!==null){const desired=closest.pos.sub(this.pos);desired.normalize();desired.mult(3);const steer=desired.sub(this.vel);steer.limit(this.maxForce);return steer}return new Vector(0,0)}applyForce(force){this.acc.add(force)}render(){ctx.fillStyle='gold';ctx.beginPath();ctx.moveTo(this.pos.x,this.pos.y);ctx.lineTo(this.pos.x-this.size*Math.cos(Math.atan2(this.vel.y,this.vel.x)+Math.PI/4),this.pos.y-this.size*Math.sin(Math.atan2(this.vel.y,this.vel.x)+Math.PI/4));ctx.lineTo(this.pos.x-this.size,0);ctx.lineTo(this.pos.x-this.size*Math.cos(Math.atan2(this.vel.y,this.vel.x)-Math.PI/4),this.pos.y-this.size*Math.sin(Math.atan2(this.vel.y,this.vel.x)-Math.PI/4));ctx.closePath();ctx.fill()}}class Bubble{constructor(x,y){this.pos=new Vector(x,y);this.vel=new Vector(0,-Math.random()*1.5-0.5);this.acc=new Vector(0,0);this.size=Math.random()*2+1}update(){this.vel.add(this.acc);this.pos.add(this.vel);if(this.pos.y<0)this.pos.y=canvas.height;this.acc.mult(0)}render(){ctx.fillStyle='rgba(255,255,255,0.8)';ctx.beginPath();ctx.arc(this.pos.x,this.pos.y,this.size,0,Math.PI*2);ctx.fill()}}class Plant{constructor(x,y){this.pos=new Vector(x,y);this.angle=0;this.swing=Math.random()*0.1+0.05}update(){this.angle+=this.swing;this.angle%=Math.PI*2}render(){const sway=this.size*Math.sin(this.angle);ctx.fillStyle='green';ctx.beginPath();ctx.moveTo(this.pos.x,this.pos.y);ctx.lineTo(this.pos.x+sway,this.pos.y-30);ctx.lineTo(this.pos.x-sway,this.pos.y-40);ctx.lineTo(this.pos.x,this.pos.y-50);ctx.closePath();ctx.fill()}}function setup(){for(let i=0;i<10;i++){fish.push(new Fish(Math.random()*canvas.width,Math.random()*canvas.height))}for(let i=0;i<20;i++){bubbles.push(new Bubble(Math.random()*canvas.width,Math.random()*canvas.height))}for(let i=0;i<5;i++){plants.push(new Plant(canvas.width*(i+1)/6,canvas.height-30))}}function draw(){ctx.clearRect(0,0,canvas.width,canvas.height);ctx.globalAlpha=1;const gradient=ctx.createLinearGradient(0,0,0,canvas.height);gradient.addColorStop(0,'#87CEEB');gradient.addColorStop(1,'#4682B4');ctx.fillStyle=gradient;ctx.fillRect(0,0,canvas.width,canvas.height);for(let i=fish.length-1;i>=0;i--){fish[i].update();fish[i].render()}for(let i=bubbles.length-1;i>=0;i--){bubbles[i].update();bubbles[i].render()}for(let i=plants.length-1;i>=0;i--){plants[i].update();plants[i].render()}ctx.globalAlpha=0.5;food.forEach((f,idx)=>{ctx.fillStyle='orange';ctx.beginPath();ctx.arc(f.pos.x,f.pos.y,3,0,Math.PI*2);ctx.fill();if(f.lifetime<10)food.splice(idx,1);else f.lifetime--});requestAnimationFrame(draw)}function addFish(){fish.push(new Fish(Math.random()*canvas.width,Math.random()*canvas.height))}canvas.addEventListener('click',e=>{const rect=canvas.getBoundingClientRect();const x=e.clientX-rect.left;y=e.clientY-rect.top;food.push({pos:new Vector(x,y),lifetime:50})});setup();draw();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ebf4f7984098/hf-qwen-coder-32b.png b/data/artifacts/ebf4f7984098/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..123bd47
Binary files /dev/null and b/data/artifacts/ebf4f7984098/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/ebf4f7984098/kimi.html b/data/artifacts/ebf4f7984098/kimi.html
new file mode 100644
index 0000000..c777d62
--- /dev/null
+++ b/data/artifacts/ebf4f7984098/kimi.html
@@ -0,0 +1,608 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Virtual Aquarium</title>
+<style>
+body {
+margin: 0;
+padding: 0;
+overflow: hidden;
+background: #001122;
+font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+}
+canvas {
+display: block;
+}
+#controls {
+position: absolute;
+top: 20px;
+left: 20px;
+z-index: 10;
+}
+button {
+background: rgba(255, 255, 255, 0.2);
+backdrop-filter: blur(10px);
+border: 1px solid rgba(255, 255, 255, 0.3);
+color: white;
+padding: 12px 24px;
+font-size: 16px;
+border-radius: 25px;
+cursor: pointer;
+transition: all 0.3s ease;
+box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+button:hover {
+background: rgba(255, 255, 255, 0.3);
+transform: translateY(-2px);
+box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
+}
+#instructions {
+position: absolute;
+bottom: 20px;
+left: 20px;
+color: rgba(255, 255, 255, 0.6);
+font-size: 14px;
+pointer-events: none;
+}
+</style>
+</head>
+<body>
+<div id="controls">
+<button id="addFishBtn">Add Fish</button>
+</div>
+<div id="instructions">Click anywhere to drop food</div>
+<canvas id="aquarium"></canvas>
+
+<script>
+const canvas = document.getElementById('aquarium');
+const ctx = canvas.getContext('2d');
+
+function resize() {
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+}
+resize();
+window.addEventListener('resize', resize);
+
+// Vector utility class
+class Vector {
+constructor(x, y) {
+this.x = x;
+this.y = y;
+}
+
+add(v) {
+this.x += v.x;
+this.y += v.y;
+return this;
+}
+
+sub(v) {
+this.x -= v.x;
+this.y -= v.y;
+return this;
+}
+
+mult(n) {
+this.x *= n;
+this.y *= n;
+return this;
+}
+
+div(n) {
+if (n !== 0) {
+this.x /= n;
+this.y /= n;
+}
+return this;
+}
+
+mag() {
+return Math.sqrt(this.x * this.x + this.y * this.y);
+}
+
+normalize() {
+const m = this.mag();
+if (m > 0) this.div(m);
+return this;
+}
+
+limit(max) {
+if (this.mag() > max) {
+this.normalize();
+this.mult(max);
+}
+return this;
+}
+
+static dist(v1, v2) {
+const dx = v1.x - v2.x;
+const dy = v1.y - v2.y;
+return Math.sqrt(dx * dx + dy * dy);
+}
+
+copy() {
+return new Vector(this.x, this.y);
+}
+}
+
+// Food class
+class Food {
+constructor(x, y) {
+this.pos = new Vector(x, y);
+this.vel = new Vector(0, 0.5 + Math.random() * 0.5);
+this.size = 4 + Math.random() * 3;
+this.color = `hsl(${25 + Math.random() * 20}, 80%, 60%)`;
+this.exists = true;
+}
+
+update() {
+this.pos.add(this.vel);
+if (this.pos.y > canvas.height - 20) {
+this.pos.y = canvas.height - 20;
+this.vel.y = 0;
+}
+// Slight drift
+this.pos.x += Math.sin(Date.now() * 0.001 + this.pos.y * 0.01) * 0.2;
+}
+
+draw() {
+ctx.save();
+ctx.translate(this.pos.x, this.pos.y);
+ctx.fillStyle = this.color;
+ctx.beginPath();
+ctx.arc(0, 0, this.size, 0, Math.PI * 2);
+ctx.fill();
+ctx.fillStyle = 'rgba(255,255,255,0.4)';
+ctx.beginPath();
+ctx.arc(-this.size*0.3, -this.size*0.3, this.size*0.3, 0, Math.PI * 2);
+ctx.fill();
+ctx.restore();
+}
+}
+
+// Bubble class
+class Bubble {
+constructor() {
+this.reset();
+this.y = Math.random() * canvas.height; // Start randomly for initial fill
+}
+
+reset() {
+this.x = Math.random() * canvas.width;
+this.y = canvas.height + 10;
+this.size = 2 + Math.random() * 6;
+this.speed = 1 + Math.random() * 2;
+this.wobble = Math.random() * Math.PI * 2;
+this.wobbleSpeed = 0.02 + Math.random() * 0.03;
+}
+
+update() {
+this.y -= this.speed;
+this.wobble += this.wobbleSpeed;
+this.x += Math.sin(this.wobble) * 0.5;
+
+if (this.y < -10) {
+this.reset();
+}
+}
+
+draw() {
+ctx.save();
+ctx.globalAlpha = 0.3 + Math.sin(this.wobble) * 0.1;
+ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
+ctx.beginPath();
+ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
+ctx.fill();
+ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
+ctx.lineWidth = 1;
+ctx.beginPath();
+ctx.arc(this.x, this.y, this.size * 0.7, 0, Math.PI * 2);
+ctx.stroke();
+ctx.restore();
+}
+}
+
+// Plant class
+class Plant {
+constructor(x) {
+this.x = x;
+this.segments = 8 + Math.floor(Math.random() * 5);
+this.segmentHeight = 15 + Math.random() * 10;
+this.baseWidth = 8 + Math.random() * 6;
+this.color = `hsl(${100 + Math.random() * 40}, ${60 + Math.random() * 20}%, ${20 + Math.random() * 20}%)`;
+this.swayOffset = Math.random() * Math.PI * 2;
+this.swaySpeed = 0.001 + Math.random() * 0.002;
+}
+
+draw(time) {
+ctx.save();
+ctx.strokeStyle = this.color;
+ctx.lineCap = 'round';
+ctx.lineWidth = this.baseWidth;
+
+let currentX = this.x;
+let currentY = canvas.height;
+
+ctx.beginPath();
+ctx.moveTo(currentX, currentY);
+
+const sway = Math.sin(time * this.swaySpeed + this.swayOffset);
+
+for (let i = 0; i < this.segments; i++) {
+const progress = i / this.segments;
+const width = this.baseWidth * (1 - progress * 0.8);
+ctx.lineWidth = width;
+
+const bend = sway * (i * i) * 0.8;
+const nextX = currentX + bend;
+const nextY = currentY - this.segmentHeight;
+
+ctx.lineTo(nextX, nextY);
+currentX = nextX;
+currentY = nextY;
+}
+
+ctx.stroke();
+
+// Draw leaves
+ctx.fillStyle = this.color;
+for (let i = 2; i < this.segments; i += 2) {
+const progress = i / this.segments;
+const leafX = this.x + sway * (i * i) * 0.8;
+const leafY = canvas.height - i * this.segmentHeight;
+const leafSize = this.baseWidth * 2 * (1 - progress * 0.5);
+
+ctx.beginPath();
+ctx.ellipse(leafX + leafSize, leafY, leafSize, leafSize/2, sway * 0.5 + Math.PI/4, 0, Math.PI * 2);
+ctx.fill();
+}
+
+ctx.restore();
+}
+}
+
+// Light Ray class
+class LightRay {
+constructor() {
+this.reset();
+this.x = Math.random() * canvas.width;
+}
+
+reset() {
+this.x = Math.random() * canvas.width;
+this.width = 50 + Math.random() * 100;
+this.angle = Math.PI / 3 + (Math.random() - 0.5) * 0.2;
+this.speed = 0.0002 + Math.random() * 0.0003;
+this.opacity = 0.05 + Math.random() * 0.05;
+this.phase = Math.random() * Math.PI * 2;
+}
+
+update(time) {
+this.phase += this.speed;
+this.currentOpacity = this.opacity + Math.sin(this.phase) * 0.02;
+}
+
+draw() {
+ctx.save();
+ctx.globalAlpha = Math.max(0, this.currentOpacity);
+const gradient = ctx.createLinearGradient(this.x, 0, this.x + Math.cos(this.angle) * canvas.height, canvas.height);
+gradient.addColorStop(0, 'rgba(255, 255, 220, 0.8)');
+gradient.addColorStop(0.5, 'rgba(255, 255, 240, 0.3)');
+gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
+
+ctx.fillStyle = gradient;
+ctx.beginPath();
+ctx.moveTo(this.x - this.width/2, 0);
+ctx.lineTo(this.x + this.width/2, 0);
+ctx.lineTo(this.x + this.width/2 + Math.cos(this.angle) * canvas.height, canvas.height);
+ctx.lineTo(this.x - this.width/2 + Math.cos(this.angle) * canvas.height, canvas.height);
+ctx.closePath();
+ctx.fill();
+ctx.restore();
+}
+}
+
+// Fish class with flocking and steering behaviors
+class Fish {
+constructor(x, y) {
+this.pos = new Vector(x || Math.random() * canvas.width, y || Math.random() * canvas.height);
+this.vel = new Vector(Math.random() * 2 - 1, Math.random() * 2 - 1);
+this.acc = new Vector(0, 0);
+this.maxSpeed = 3 + Math.random() * 2;
+this.maxForce = 0.05 + Math.random() * 0.05;
+this.size = 8 + Math.random() * 8;
+this.hue = Math.random() * 360;
+this.color = `hsl(${this.hue}, 80%, 60%)`;
+this.tailOffset = Math.random() * Math.PI * 2;
+this.perception = 100;
+}
+
+applyForce(force) {
+this.acc.add(force);
+}
+
+seek(target) {
+const desired = target.copy().sub(this.pos);
+desired.normalize();
+desired.mult(this.maxSpeed);
+const steer = desired.sub(this.vel);
+steer.limit(this.maxForce);
+return steer;
+}
+
+separate(fishes) {
+const desiredSeparation = this.size * 2;
+const sum = new Vector(0, 0);
+let count = 0;
+
+for (let other of fishes) {
+const d = Vector.dist(this.pos, other.pos);
+if ((d > 0) && (d < desiredSeparation)) {
+const diff = this.pos.copy().sub(other.pos);
+diff.normalize();
+diff.div(d);
+sum.add(diff);
+count++;
+}
+}
+
+if (count > 0) {
+sum.div(count);
+sum.normalize();
+sum.mult(this.maxSpeed);
+const steer = sum.sub(this.vel);
+steer.limit(this.maxForce);
+return steer;
+}
+return new Vector(0, 0);
+}
+
+align(fishes) {
+const neighborDist = 50;
+const sum = new Vector(0, 0);
+let count = 0;
+
+for (let other of fishes) {
+const d = Vector.dist(this.pos, other.pos);
+if ((d > 0) && (d < neighborDist)) {
+sum.add(other.vel);
+count++;
+}
+}
+
+if (count > 0) {
+sum.div(count);
+sum.normalize();
+sum.mult(this.maxSpeed);
+const steer = sum.sub(this.vel);
+steer.limit(this.maxForce);
+return steer;
+}
+return new Vector(0, 0);
+}
+
+cohesion(fishes) {
+const neighborDist = 50;
+const sum = new Vector(0, 0);
+let count = 0;
+
+for (let other of fishes) {
+const d = Vector.dist(this.pos, other.pos);
+if ((d > 0) && (d < neighborDist)) {
+sum.add(other.pos);
+count++;
+}
+}
+
+if (count > 0) {
+sum.div(count);
+return this.seek(sum);
+}
+return new Vector(0, 0);
+}
+
+findNearestFood(foods) {
+let nearest = null;
+let nearestDist = Infinity;
+
+for (let food of foods) {
+const d = Vector.dist(this.pos, food.pos);
+if (d < nearestDist && d < 200) {
+nearestDist = d;
+nearest = food;
+}
+}
+
+return nearest;
+}
+
+flock(fishes, foods) {
+const sep = this.separate(fishes);
+const ali = this.align(fishes);
+const coh = this.cohesion(fishes);
+const seek = new Vector(0, 0);
+
+const nearestFood = this.findNearestFood(foods);
+if (nearestFood) {
+const foodSeek = this.seek(nearestFood.pos);
+foodSeek.mult(2.0); // Strong priority for food
+seek.add(foodSeek);
+}
+
+sep.mult(1.5);
+ali.mult(1.0);
+coh.mult(1.0);
+
+this.applyForce(sep);
+this.applyForce(ali);
+this.applyForce(coh);
+this.applyForce(seek);
+}
+
+update() {
+this.vel.add(this.acc);
+this.vel.limit(this.maxSpeed);
+this.pos.add(this.vel);
+this.acc.mult(0);
+
+// Wrap around edges with margin
+const margin = 50;
+if (this.pos.x < -margin) this.pos.x = canvas.width + margin;
+if (this.pos.x > canvas.width + margin) this.pos.x = -margin;
+if (this.pos.y < -margin) this.pos.y = canvas.height + margin;
+if (this.pos.y > canvas.height + margin) this.pos.y = -margin;
+}
+
+draw(time) {
+const angle = Math.atan2(this.vel.y, this.vel.x);
+const tailWag = Math.sin(time * 0.01 + this.tailOffset) * 0.3;
+
+ctx.save();
+ctx.translate(this.pos.x, this.pos.y);
+ctx.rotate(angle);
+
+// Body
+ctx.fillStyle = this.color;
+ctx.beginPath();
+ctx.ellipse(0, 0, this.size * 1.5, this.size * 0.8, 0, 0, Math.PI * 2);
+ctx.fill();
+
+// Stripes
+ctx.fillStyle = 'rgba(0,0,0,0.2)';
+for (let i = -1; i <= 1; i++) {
+ctx.beginPath();
+ctx.ellipse(i * this.size * 0.5, 0, this.size * 0.2, this.size * 0.6, 0, 0, Math.PI * 2);
+ctx.fill();
+}
+
+// Tail
+ctx.fillStyle = this.color;
+ctx.beginPath();
+ctx.moveTo(-this.size, 0);
+ctx.lineTo(-this.size * 2, -this.size * 0.8 + tailWag * 5);
+ctx.lineTo(-this.size * 2.5, tailWag * 5);
+ctx.lineTo(-this.size * 2, this.size * 0.8 + tailWag * 5);
+ctx.closePath();
+ctx.fill();
+
+// Fin
+ctx.fillStyle = this.color;
+ctx.beginPath();
+ctx.moveTo(0, -this.size * 0.5);
+ctx.lineTo(-this.size * 0.5, -this.size * 1.2);
+ctx.lineTo(this.size * 0.5, -this.size * 0.8);
+ctx.closePath();
+ctx.fill();
+
+// Eye
+ctx.fillStyle = 'white';
+ctx.beginPath();
+ctx.arc(this.size * 0.8, -this.size * 0.3, this.size * 0.25, 0, Math.PI * 2);
+ctx.fill();
+ctx.fillStyle = 'black';
+ctx.beginPath();
+ctx.arc(this.size * 0.9, -this.size * 0.3, this.size * 0.1, 0, Math.PI * 2);
+ctx.fill();
+
+ctx.restore();
+}
+}
+
+// Initialize entities
+const fishes = [];
+const foods = [];
+const bubbles = [];
+const plants = [];
+const lightRays = [];
+
+// Create initial fish
+for (let i = 0; i < 15; i++) {
+fishes.push(new Fish());
+}
+
+// Create bubbles
+for (let i = 0; i < 20; i++) {
+bubbles.push(new Bubble());
+}
+
+// Create plants
+for (let i = 0; i < canvas.width; i += 40) {
+if (Math.random() > 0.3) {
+plants.push(new Plant(i + Math.random() * 30));
+}
+}
+
+// Create light rays
+for (let i = 0; i < 5; i++) {
+lightRays.push(new LightRay());
+}
+
+// Animation loop
+let time = 0;
+function animate() {
+time++;
+
+// Gradient background
+const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
+gradient.addColorStop(0, '#006994');
+gradient.addColorStop(0.5, '#003d5c');
+gradient.addColorStop(1, '#001a2e');
+ctx.fillStyle = gradient;
+ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+// Light rays (behind everything)
+lightRays.forEach(ray => {
+ray.update(time);
+ray.draw();
+});
+
+// Plants (behind fish)
+plants.forEach(plant => plant.draw(time));
+
+// Update and draw bubbles
+bubbles.forEach(bubble => {
+bubble.update();
+bubble.draw();
+});
+
+// Update and draw food
+for (let i = foods.length - 1; i >= 0; i--) {
+foods[i].update();
+foods[i].draw();
+
+// Remove eaten food
+for (let fish of fishes) {
+if (Vector.dist(fish.pos, foods[i].pos) < fish.size * 2) {
+foods.splice(i, 1);
+break;
+}
+}
+}
+
+// Update and draw fish
+fishes.forEach(fish => {
+fish.flock(fishes, foods);
+fish.update();
+fish.draw(time);
+});
+
+requestAnimationFrame(animate);
+}
+
+// Event listeners
+canvas.addEventListener('click', (e) => {
+const rect = canvas.getBoundingClientRect();
+foods.push(new Food(e.clientX - rect.left, e.clientY - rect.top));
+});
+
+document.getElementById('addFishBtn').addEventListener('click', () => {
+fishes.push(new Fish(canvas.width / 2, canvas.height / 2));
+});
+
+animate();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ebf4f7984098/kimi.png b/data/artifacts/ebf4f7984098/kimi.png
new file mode 100644
index 0000000..95016b0
Binary files /dev/null and b/data/artifacts/ebf4f7984098/kimi.png differ
diff --git a/data/artifacts/ebf4f7984098/qwen25-7b.html b/data/artifacts/ebf4f7984098/qwen25-7b.html
new file mode 100644
index 0000000..c038e4e
--- /dev/null
+++ b/data/artifacts/ebf4f7984098/qwen25-7b.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Relaxing Virtual Aquarium</title>
+    <style>
+        body { margin: 0; overflow: hidden; }
+        canvas { display: block; width: 100%; height: 100%; background: linear-gradient(to right bottom, #2e3440, #3b4252); }
+    </style>
+</head>
+<body>
+<canvas id="aquarium"></canvas>
+<script>
+const canvas = document.getElementById('aquarium');
+const ctx = canvas.getContext('2d');
+
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+
+class Fish {
+    constructor(x, y) {
+        this.x = x;
+        this.y = y;
+        this.speedX = (Math.random() - 0.5) * 4;
+        this.speedY = Math.random() * -2 + -3;
+        this.size = Math.random() * 10 + 5;
+    }
+
+    draw() {
+        ctx.beginPath();
+        ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
+        ctx.fillStyle = 'white';
+        ctx.fill();
+    }
+
+    update() {
+        if (this.y > canvas.height) {
+            this.y = -30;
+            this.x = Math.random() * canvas.width;
+        }
+        this.y += this.speedY;
+        this.x += this.speedX;
+        this.draw();
+    }
+}
+
+class Bubble {
+    constructor(x, y) {
+        this.x = x;
+        this.y = y;
+        this.size = 3 + Math.random() * 2;
+        this.speed = -5 + Math.random() * 10;
+    }
+
+    draw() {
+        ctx.beginPath();
+        ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
+        ctx.fillStyle = 'white';
+        ctx.fill();
+    }
+
+    update() {
+        this.y += this.speed;
+        if (this.y < -15) {
+            this.y = canvas.height + 10;
+            this.x = Math.random() * canvas.width;
+        }
+        this.draw();
+    }
+}
+
+const fishArray = [];
+const bubbleArray = [];
+
+function addFish() {
+    for (let i = 0; i < 2; i++) {
+        const x = Math.random() * canvas.width;
+        const y = -15 - (i * 4);
+        fishArray.push(new Fish(x, y));
+    }
+}
+
+function addBubbles() {
+    for (let i = 0; i < 3; i++) {
+        const x = Math.random() * canvas.width;
+        const y = Math.random() * canvas.height;
+        bubbleArray.push(new Bubble(x, y));
+    }
+}
+
+const food = {
+    x: -10,
+    y: -10
+};
+
+function dropFood(e) {
+    food.x = e.clientX;
+    food.y = e.clientY;
+}
+
+document.addEventListener('click', (e) => {
+    dropFood(e);
+});
+
+function drawBubbles() {
+    for (const bubble of bubbleArray) {
+        bubble.update();
+    }
+}
+
+function drawFish() {
+    fishArray.sort((a, b) => a.x - b.x || a.y - b.y);
+    for (const fish of fishArray) {
+        if (fish.x > food.x && fish.x < food.x + 10 && fish.y > food.y - 5 && fish.y < food.y + 15) {
+            addFish();
+        }
+        fish.update();
+    }
+}
+
+function draw() {
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+    drawBubbles();
+    drawFish();
+    requestAnimationFrame(draw);
+}
+
+function init() {
+    addFish();
+    addBubbles();
+    draw();
+}
+
+init();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ebf4f7984098/qwen25-7b.png b/data/artifacts/ebf4f7984098/qwen25-7b.png
new file mode 100644
index 0000000..6b24b9c
Binary files /dev/null and b/data/artifacts/ebf4f7984098/qwen25-7b.png differ
diff --git a/data/artifacts/ebf4f7984098/qwen3-14b.html b/data/artifacts/ebf4f7984098/qwen3-14b.html
new file mode 100644
index 0000000..9afef84
--- /dev/null
+++ b/data/artifacts/ebf4f7984098/qwen3-14b.html
@@ -0,0 +1,309 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Relaxing Virtual Aquarium</title>
+  <style>
+    html, body {
+      margin: 0;
+      padding: 0;
+      overflow: hidden;
+      background: #000;
+      height: 100%;
+      width: 100%;
+      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+    }
+    canvas {
+      display: block;
+    }
+    #controls {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      color: white;
+      background: rgba(0, 0, 0, 0.5);
+      padding: 10px;
+      border-radius: 8px;
+    }
+    #controls button {
+      margin: 5px 0;
+      padding: 5px 10px;
+      background: #444;
+      color: white;
+      border: none;
+      cursor: pointer;
+      border-radius: 4px;
+    }
+    #controls button:hover {
+      background: #666;
+    }
+  </style>
+</head>
+<body>
+  <div id="controls">
+    <button onclick="addFish()">Add Fish</button>
+  </div>
+  <canvas id="aquarium"></canvas>
+  <script>
+    const canvas = document.getElementById('aquarium');
+    const ctx = canvas.getContext('2d');
+    let width = canvas.width = window.innerWidth;
+    let height = canvas.height = window.innerHeight;
+    window.addEventListener('resize', () => {
+      width = canvas.width = window.innerWidth;
+      height = canvas.height = window.innerHeight;
+    });
+
+    const fishList = [];
+    const plants = [];
+    const bubbles = [];
+
+    class Fish {
+      constructor(x, y) {
+        this.x = x;
+        this.y = y;
+        this.angle = Math.random() * Math.PI * 2;
+        this.speed = 1.2 + Math.random() * 1.5;
+        this.size = 15 + Math.random() * 10;
+        this.maxSpeed = 3;
+        this.maxForce = 0.1;
+        this.alignment = 1;
+        this.cohesion = 1;
+        this.separation = 1;
+        this.color = `hsl(${Math.random() * 360}, 70%, 60%)`;
+      }
+
+      update(fishList) {
+        const alignment = this.flock('alignment', fishList);
+        const cohesion = this.flock('cohesion', fishList);
+        const separation = this.flock('separation', fishList);
+
+        const force = {
+          x: alignment.x * this.alignment + cohesion.x * this.cohesion + separation.x * this.separation,
+          y: alignment.y * this.alignment + cohesion.y * this.cohesion + separation.y * this.separation
+        };
+
+        this.applyForce(force);
+        this.move();
+        this.wrapAround();
+      }
+
+      applyForce(force) {
+        this.angle += Math.atan2(force.y, force.x);
+        this.speed += force.x * 0.2 + force.y * 0.2;
+        this.speed = Math.min(this.maxSpeed, Math.max(-this.maxSpeed, this.speed));
+      }
+
+      move() {
+        this.x += Math.cos(this.angle) * this.speed;
+        this.y += Math.sin(this.angle) * this.speed;
+      }
+
+      wrapAround() {
+        if (this.x < 0) this.x = width;
+        if (this.x > width) this.x = 0;
+        if (this.y < 0) this.y = height;
+        if (this.y > height) this.y = 0;
+      }
+
+      flock(type, fishList) {
+        const neighbors = [];
+        const dist = 100;
+        for (let other of fishList) {
+          if (other === this) continue;
+          const dx = other.x - this.x;
+          const dy = other.y - this.y;
+          const d = Math.sqrt(dx * dx + dy * dy);
+          if (d < dist) {
+            neighbors.push(other);
+          }
+        }
+
+        if (neighbors.length === 0) return { x: 0, y: 0 };
+
+        let sumX = 0, sumY = 0;
+        for (let other of neighbors) {
+          sumX += other.x;
+          sumY += other.y;
+        }
+
+        const averageX = sumX / neighbors.length - this.x;
+        const averageY = sumY / neighbors.length - this.y;
+
+        if (type === 'alignment') {
+          const avgAngle = neighbors.reduce((a, b) => a + b.angle, 0) / neighbors.length;
+          return {
+            x: Math.cos(avgAngle) - Math.cos(this.angle),
+            y: Math.sin(avgAngle) - Math.sin(this.angle)
+          };
+        } else if (type === 'cohesion') {
+          return {
+            x: averageX,
+            y: averageY
+          };
+        } else if (type === 'separation') {
+          const sumX = neighbors.reduce((a, b) => a + (this.x - b.x), 0);
+          const sumY = neighbors.reduce((a, b) => a + (this.y - b.y), 0);
+          return {
+            x: sumX / neighbors.length,
+            y: sumY / neighbors.length
+          };
+        }
+        return { x: 0, y: 0 };
+      }
+
+      draw(ctx) {
+        ctx.save();
+        ctx.translate(this.x, this.y);
+        ctx.rotate(this.angle);
+        ctx.fillStyle = this.color;
+        ctx.beginPath();
+        ctx.moveTo(0, -this.size / 2);
+        ctx.lineTo(-this.size / 4, this.size / 2);
+        ctx.lineTo(this.size / 4, this.size / 2);
+        ctx.closePath();
+        ctx.fill();
+        ctx.restore();
+      }
+    }
+
+    class Plant {
+      constructor(x, y) {
+        this.x = x;
+        this.y = y;
+        this.angle = Math.random() * Math.PI * 2;
+        this.length = 40 + Math.random() * 20;
+        this.width = 4 + Math.random() * 2;
+      }
+
+      draw(ctx) {
+        ctx.save();
+        ctx.translate(this.x, this.y);
+        ctx.rotate(this.angle);
+        ctx.fillStyle = 'green';
+        ctx.beginPath();
+        ctx.moveTo(0, 0);
+        ctx.lineTo(0, this.length);
+        ctx.lineTo(this.width / 2, this.length + 10);
+        ctx.lineTo(-this.width / 2, this.length + 10);
+        ctx.closePath();
+        ctx.fill();
+        ctx.restore();
+      }
+    }
+
+    class Bubble {
+      constructor(x, y) {
+        this.x = x;
+        this.y = y;
+        this.radius = 3 + Math.random() * 3;
+        this.speed = 1 + Math.random() * 2;
+      }
+
+      update() {
+        this.y -= this.speed;
+        if (this.y < -this.radius) {
+          this.y = height + this.radius;
+          this.x = Math.random() * width;
+        }
+      }
+
+      draw(ctx) {
+        ctx.beginPath();
+        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
+        ctx.fillStyle = 'white';
+        ctx.fill();
+      }
+    }
+
+    function addFish() {
+      const x = Math.random() * width;
+      const y = Math.random() * height;
+      fishList.push(new Fish(x, y));
+    }
+
+    function drawGradient(ctx) {
+      const gradient = ctx.createLinearGradient(0, 0, 0, height);
+      gradient.addColorStop(0, 'rgba(0, 40, 80, 1)');
+      gradient.addColorStop(1, 'rgba(0, 0, 20, 1)');
+      ctx.fillStyle = gradient;
+      ctx.fillRect(0, 0, width, height);
+    }
+
+    function drawRays(ctx) {
+      ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
+      ctx.lineWidth = 1;
+      for (let i = 0; i < 20; i++) {
+        ctx.beginPath();
+        ctx.moveTo(Math.random() * width, Math.random() * height);
+        ctx.lineTo(Math.random() * width, Math.random() * height);
+        ctx.stroke();
+      }
+    }
+
+    function drawFood(ctx, foodList) {
+      ctx.fillStyle = 'yellow';
+      for (let food of foodList) {
+        ctx.beginPath();
+        ctx.arc(food.x, food.y, 5, 0, Math.PI * 2);
+        ctx.fill();
+      }
+    }
+
+    function drawFish(ctx) {
+      for (let fish of fishList) {
+        fish.update(fishList);
+        fish.draw(ctx);
+      }
+    }
+
+    function drawPlants(ctx) {
+      for (let plant of plants) {
+        plant.draw(ctx);
+      }
+    }
+
+    function drawBubbles(ctx) {
+      for (let bubble of bubbles) {
+        bubble.update();
+        bubble.draw(ctx);
+      }
+    }
+
+    function drawScene() {
+      ctx.clearRect(0, 0, width, height);
+      drawGradient(ctx);
+      drawRays(ctx);
+      drawPlants(ctx);
+      drawBubbles(ctx);
+      drawFish(ctx);
+    }
+
+    function animate() {
+      drawScene();
+      requestAnimationFrame(animate);
+    }
+
+    // Initialize
+    for (let i = 0; i < 10; i++) {
+      fishList.push(new Fish(Math.random() * width, Math.random() * height));
+    }
+
+    for (let i = 0; i < 20; i++) {
+      plants.push(new Plant(Math.random() * width, Math.random() * height));
+    }
+
+    for (let i = 0; i < 50; i++) {
+      bubbles.push(new Bubble(Math.random() * width, height + Math.random() * 50));
+    }
+
+    canvas.addEventListener('click', (e) => {
+      const x = e.clientX;
+      const y = e.clientY;
+      foodList.push({ x, y });
+    });
+
+    animate();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ebf4f7984098/qwen3-14b.png b/data/artifacts/ebf4f7984098/qwen3-14b.png
new file mode 100644
index 0000000..766a35f
Binary files /dev/null and b/data/artifacts/ebf4f7984098/qwen3-14b.png differ
diff --git a/data/challenges.json b/data/challenges.json
index f00deb7..d09db99 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -8657,7 +8657,7 @@
     "category": "Games",
     "designTools": false,
     "created_at": "2026-07-23T18:18:30.757Z",
-    "winner": null,
+    "winner": "gpt",
     "runs": [
       {
         "model": "qwen3-14b",
@@ -8669,7 +8669,14 @@
         "finished_at": "2026-07-23T18:19:30.414Z",
         "queued_at": "2026-07-23T18:18:30.761Z",
         "bytes": 8872,
-        "thumb": true
+        "thumb": true,
+        "aiScore": 7.5,
+        "aiReason": "The game is visually appealing and fulfills all the requirements of a polished Breakout/Arkanoid game.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 6
+        },
+        "aiSpread": 3
       },
       {
         "model": "gemma3-12b",
@@ -8681,7 +8688,14 @@
         "finished_at": "2026-07-23T18:20:21.096Z",
         "queued_at": "2026-07-23T18:18:30.766Z",
         "bytes": 7809,
-        "thumb": true
+        "thumb": true,
+        "aiScore": 4.8,
+        "aiReason": "The image is completely black, indicating no content or rendering of the game.",
+        "aiScores": {
+          "qwen2.5vl:7b": 3,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 3.5
       },
       {
         "model": "hermes3-8b",
@@ -8693,7 +8707,14 @@
         "finished_at": "2026-07-23T18:20:38.308Z",
         "queued_at": "2026-07-23T18:18:30.771Z",
         "bytes": 4799,
-        "thumb": true
+        "thumb": true,
+        "aiScore": 5,
+        "aiReason": "The game is functional but lacks the neon aesthetic and power-ups.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 3
+        },
+        "aiSpread": 4
       },
       {
         "model": "qwen25-7b",
@@ -8705,17 +8726,33 @@
         "finished_at": "2026-07-23T18:19:04.212Z",
         "queued_at": "2026-07-23T18:18:30.775Z",
         "bytes": 4240,
-        "thumb": true
+        "thumb": true,
+        "aiScore": 7.3,
+        "aiReason": "The game is functional but lacks some visual polish and power-ups.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 0.5
       },
       {
         "model": "hf-qwen-coder-32b",
-        "status": "running",
+        "status": "done",
         "error": null,
-        "seconds": null,
-        "cost": null,
+        "seconds": 80,
+        "cost": 0,
         "started_at": "2026-07-23T18:20:38.313Z",
-        "finished_at": null,
-        "queued_at": "2026-07-23T18:18:30.779Z"
+        "finished_at": "2026-07-23T18:21:58.509Z",
+        "queued_at": "2026-07-23T18:18:30.779Z",
+        "bytes": 5863,
+        "thumb": true,
+        "aiScore": 7.8,
+        "aiReason": "The game is visually appealing and fulfills all the requirements of the challenge.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 2.5
       },
       {
         "model": "claude-code",
@@ -8729,13 +8766,22 @@
       },
       {
         "model": "kimi",
-        "status": "running",
+        "status": "done",
         "error": null,
-        "seconds": null,
-        "cost": null,
+        "seconds": 153,
+        "cost": 0.0219,
         "started_at": "2026-07-23T18:18:30.813Z",
-        "finished_at": null,
-        "queued_at": "2026-07-23T18:18:30.786Z"
+        "finished_at": "2026-07-23T18:21:03.833Z",
+        "queued_at": "2026-07-23T18:18:30.786Z",
+        "bytes": 17483,
+        "thumb": true,
+        "aiScore": 8.3,
+        "aiReason": "The game is visually appealing and fulfills all the requirements of the challenge.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 1.5
       },
       {
         "model": "gpt",
@@ -8747,7 +8793,14 @@
         "finished_at": "2026-07-23T18:20:35.161Z",
         "queued_at": "2026-07-23T18:18:30.788Z",
         "bytes": 40937,
-        "thumb": true
+        "thumb": true,
+        "aiScore": 9.3,
+        "aiReason": "The game is visually appealing and fully functional according to the challenge criteria.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 9.5
+        },
+        "aiSpread": 0.5
       },
       {
         "model": "grok",
@@ -8759,6 +8812,1091 @@
         "finished_at": "2026-07-23T18:20:03.926Z",
         "queued_at": "2026-07-23T18:18:30.790Z",
         "bytes": 20675,
+        "thumb": true,
+        "aiScore": 8.3,
+        "aiReason": "The game fulfills the challenge requirements and has an appealing neon aesthetic.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 1.5
+      }
+    ],
+    "judging": false,
+    "judged_at": "2026-07-23T18:22:40.486Z",
+    "aiPick": "gpt",
+    "voted_at": "2026-07-23T18:22:41.612Z"
+  },
+  {
+    "id": "9cbc4e3c60a6",
+    "title": "All-Models — Markdown Previewer",
+    "prompt": "Build a single-file HTML page: a live markdown editor + preview. Left textarea, right rendered pane updating as you type; support headings, bold/italic, lists, links, code blocks, blockquotes; a copy-HTML button. Implement the parser yourself, self-contained, no external libraries.",
+    "category": "Custom",
+    "designTools": false,
+    "created_at": "2026-07-23T18:23:04.228Z",
+    "winner": "kimi",
+    "runs": [
+      {
+        "model": "qwen3-14b",
+        "status": "done",
+        "error": null,
+        "seconds": 26,
+        "cost": 0,
+        "started_at": "2026-07-23T18:23:04.263Z",
+        "finished_at": "2026-07-23T18:23:30.076Z",
+        "queued_at": "2026-07-23T18:23:04.233Z",
+        "bytes": 2431,
+        "thumb": true,
+        "aiScore": 6.8,
+        "aiReason": "The editor is functional but lacks visual appeal and some features like code blocks are not fully supported.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "gemma3-12b",
+        "status": "done",
+        "error": null,
+        "seconds": 31,
+        "cost": 0,
+        "started_at": "2026-07-23T18:23:30.082Z",
+        "finished_at": "2026-07-23T18:24:01.019Z",
+        "queued_at": "2026-07-23T18:23:04.237Z",
+        "bytes": 2132,
+        "thumb": true,
+        "aiScore": 6.8,
+        "aiReason": "The page is functional but lacks visual appeal and does not fully utilize the available space.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "hermes3-8b",
+        "status": "done",
+        "error": null,
+        "seconds": 11,
+        "cost": 0,
+        "started_at": "2026-07-23T18:24:01.023Z",
+        "finished_at": "2026-07-23T18:24:11.581Z",
+        "queued_at": "2026-07-23T18:23:04.239Z",
+        "bytes": 2263,
+        "thumb": true,
+        "aiScore": 5.8,
+        "aiReason": "The page is empty and lacks the expected content or functionality.",
+        "aiScores": {
+          "qwen2.5vl:7b": 5,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 1.5
+      },
+      {
+        "model": "qwen25-7b",
+        "status": "done",
+        "error": null,
+        "seconds": 13,
+        "cost": 0,
+        "started_at": "2026-07-23T18:23:04.266Z",
+        "finished_at": "2026-07-23T18:23:17.534Z",
+        "queued_at": "2026-07-23T18:23:04.243Z",
+        "bytes": 2116,
+        "thumb": true,
+        "aiScore": 7.3,
+        "aiReason": "The editor is functional but lacks visual polish and some features like bold/italic are not immediately apparent.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "hf-qwen-coder-32b",
+        "status": "done",
+        "error": null,
+        "seconds": 46,
+        "cost": 0,
+        "started_at": "2026-07-23T18:24:11.586Z",
+        "finished_at": "2026-07-23T18:24:57.208Z",
+        "queued_at": "2026-07-23T18:23:04.248Z",
+        "bytes": 3040,
+        "thumb": true,
+        "aiScore": 7.3,
+        "aiReason": "The editor fulfills the requirements but lacks some polish in terms of visual quality and user interface design.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "claude-code",
+        "status": "error",
+        "error": "no HTML document in model output (140 chars)",
+        "seconds": 2,
+        "cost": null,
+        "started_at": "2026-07-23T18:23:04.268Z",
+        "finished_at": "2026-07-23T18:23:06.740Z",
+        "queued_at": "2026-07-23T18:23:04.251Z"
+      },
+      {
+        "model": "kimi",
+        "status": "done",
+        "error": null,
+        "seconds": 150,
+        "cost": 0.0252,
+        "started_at": "2026-07-23T18:23:04.284Z",
+        "finished_at": "2026-07-23T18:25:34.446Z",
+        "queued_at": "2026-07-23T18:23:04.254Z",
+        "bytes": 9522,
+        "thumb": true,
+        "aiScore": 8.3,
+        "aiReason": "The editor fulfills the challenge requirements and has good visual quality.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 1.5
+      },
+      {
+        "model": "gpt",
+        "status": "done",
+        "error": null,
+        "seconds": 26,
+        "cost": 0.041,
+        "started_at": "2026-07-23T18:23:04.287Z",
+        "finished_at": "2026-07-23T18:23:30.125Z",
+        "queued_at": "2026-07-23T18:23:04.258Z",
+        "bytes": 9799,
+        "thumb": true,
+        "aiScore": 8.3,
+        "aiReason": "The editor fulfills all requirements and has a clean layout.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 1.5
+      },
+      {
+        "model": "grok",
+        "status": "done",
+        "error": null,
+        "seconds": 60,
+        "cost": 0.0773,
+        "started_at": "2026-07-23T18:23:04.289Z",
+        "finished_at": "2026-07-23T18:24:04.435Z",
+        "queued_at": "2026-07-23T18:23:04.260Z",
+        "bytes": 15129,
+        "thumb": true,
+        "aiScore": 8.3,
+        "aiReason": "The editor fulfills all requirements and has good visual quality.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 1.5
+      }
+    ],
+    "judging": false,
+    "judged_at": "2026-07-23T18:26:31.240Z",
+    "aiPick": "kimi",
+    "voted_at": "2026-07-23T18:26:34.949Z"
+  },
+  {
+    "id": "04692f72ef1d",
+    "title": "All-Models — Tetris",
+    "prompt": "Build a single-file HTML page: a playable Tetris. All 7 tetrominoes, rotation, line clears with scoring, next-piece preview, increasing speed, pause, game-over + restart. Keyboard controls, self-contained, no external assets or libraries.",
+    "category": "Games",
+    "designTools": false,
+    "created_at": "2026-07-23T18:26:54.453Z",
+    "winner": "claude-code",
+    "runs": [
+      {
+        "model": "qwen3-14b",
+        "status": "done",
+        "error": null,
+        "seconds": 44,
+        "cost": 0,
+        "started_at": "2026-07-23T18:26:54.488Z",
+        "finished_at": "2026-07-23T18:27:38.354Z",
+        "queued_at": "2026-07-23T18:26:54.456Z",
+        "bytes": 6841,
+        "thumb": true,
+        "aiScore": 6,
+        "aiReason": "The game is functional and meets the challenge criteria but lacks visual polish.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 5
+        },
+        "aiSpread": 2
+      },
+      {
+        "model": "gemma3-12b",
+        "status": "done",
+        "error": null,
+        "seconds": 51,
+        "cost": 0,
+        "started_at": "2026-07-23T18:27:38.358Z",
+        "finished_at": "2026-07-23T18:28:29.696Z",
+        "queued_at": "2026-07-23T18:26:54.460Z",
+        "bytes": 6872,
+        "thumb": true,
+        "aiScore": 5.5,
+        "aiReason": "Basic Tetris functionality is present but the visual quality and additional features are lacking.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 4
+        },
+        "aiSpread": 3
+      },
+      {
+        "model": "hermes3-8b",
+        "status": "done",
+        "error": null,
+        "seconds": 42,
+        "cost": 0,
+        "started_at": "2026-07-23T18:28:29.701Z",
+        "finished_at": "2026-07-23T18:29:11.429Z",
+        "queued_at": "2026-07-23T18:26:54.463Z",
+        "bytes": 8297,
+        "thumb": true,
+        "aiScore": 6.3,
+        "aiReason": "Basic Tetris functionality is present but lacks visual appeal and some features like increasing speed are not implemented.",
+        "aiScores": {
+          "qwen2.5vl:7b": 6,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "qwen25-7b",
+        "status": "done",
+        "error": null,
+        "seconds": 31,
+        "cost": 0,
+        "started_at": "2026-07-23T18:26:54.491Z",
+        "finished_at": "2026-07-23T18:27:25.532Z",
+        "queued_at": "2026-07-23T18:26:54.466Z",
+        "bytes": 2841,
+        "thumb": true,
+        "aiScore": 6.3,
+        "aiReason": "The page is functional but lacks visual appeal and some features are missing.",
+        "aiScores": {
+          "qwen2.5vl:7b": 6,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "hf-qwen-coder-32b",
+        "status": "done",
+        "error": null,
+        "seconds": 72,
+        "cost": 0,
+        "started_at": "2026-07-23T18:29:11.432Z",
+        "finished_at": "2026-07-23T18:30:23.638Z",
+        "queued_at": "2026-07-23T18:26:54.470Z",
+        "bytes": 5458,
+        "thumb": true,
+        "aiScore": 5,
+        "aiReason": "Basic Tetris functionality but lacks visual polish and some features like increasing speed.",
+        "aiScores": {
+          "qwen2.5vl:7b": 6,
+          "minicpm-v:latest": 4
+        },
+        "aiSpread": 2
+      },
+      {
+        "model": "claude-code",
+        "status": "done",
+        "error": null,
+        "seconds": 121,
+        "cost": 0,
+        "started_at": "2026-07-23T18:26:54.493Z",
+        "finished_at": "2026-07-23T18:28:55.281Z",
+        "queued_at": "2026-07-23T18:26:54.474Z",
+        "bytes": 11981,
+        "thumb": true,
+        "aiScore": 7.8,
+        "aiReason": "The Tetris game is fully functional and meets all the requirements of the challenge. The visual quality is clean and professional.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 2.5
+      },
+      {
+        "model": "kimi",
+        "status": "done",
+        "error": null,
+        "seconds": 127,
+        "cost": 0.0213,
+        "started_at": "2026-07-23T18:26:54.496Z",
+        "finished_at": "2026-07-23T18:29:01.607Z",
+        "queued_at": "2026-07-23T18:26:54.478Z",
+        "bytes": 16004,
+        "thumb": true,
+        "aiScore": 6.8,
+        "aiReason": "The game is functional and includes all required features but lacks visual polish.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "gpt",
+        "status": "done",
+        "error": null,
+        "seconds": 35,
+        "cost": 0.0597,
+        "started_at": "2026-07-23T18:26:54.501Z",
+        "finished_at": "2026-07-23T18:27:29.601Z",
+        "queued_at": "2026-07-23T18:26:54.481Z",
+        "bytes": 12915,
+        "thumb": true,
+        "aiScore": 6.8,
+        "aiReason": "The game is functional but lacks some visual polish and detail.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "grok",
+        "status": "done",
+        "error": null,
+        "seconds": 46,
+        "cost": 0.062,
+        "started_at": "2026-07-23T18:26:54.502Z",
+        "finished_at": "2026-07-23T18:27:40.402Z",
+        "queued_at": "2026-07-23T18:26:54.485Z",
+        "bytes": 10532,
+        "thumb": true,
+        "aiScore": 6.8,
+        "aiReason": "The Tetris game is functional and meets the challenge criteria but lacks some visual polish.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      }
+    ],
+    "judging": false,
+    "judged_at": "2026-07-23T18:31:17.318Z",
+    "aiPick": "claude-code",
+    "voted_at": "2026-07-23T18:31:25.340Z"
+  },
+  {
+    "id": "e840f1972643",
+    "title": "All-Models — 3D Wireframe Spin",
+    "prompt": "Build a single-file HTML page: a rotating 3D wireframe scene. Render a spinning wireframe torus or icosahedron with perspective projection computed by hand; drag to orbit; a shape selector and a wireframe/solid toggle. Canvas-based, self-contained, no external libraries.",
+    "category": "Custom",
+    "designTools": false,
+    "created_at": "2026-07-23T18:31:42.757Z",
+    "winner": "claude-code",
+    "runs": [
+      {
+        "model": "qwen3-14b",
+        "status": "done",
+        "error": null,
+        "seconds": 48,
+        "cost": 0,
+        "started_at": "2026-07-23T18:31:48.458Z",
+        "finished_at": "2026-07-23T18:32:36.046Z",
+        "queued_at": "2026-07-23T18:31:48.444Z",
+        "resumes": 1,
+        "bytes": 7176,
+        "thumb": true,
+        "aiScore": 5.8,
+        "aiReason": "The page is mostly blank and does not display the required rotating wireframe scene.",
+        "aiScores": {
+          "qwen2.5vl:7b": 5,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 1.5
+      },
+      {
+        "model": "gemma3-12b",
+        "status": "done",
+        "error": null,
+        "seconds": 51,
+        "cost": 0,
+        "started_at": "2026-07-23T18:32:36.051Z",
+        "finished_at": "2026-07-23T18:33:26.816Z",
+        "queued_at": "2026-07-23T18:31:48.446Z",
+        "resumes": 1,
+        "bytes": 5726,
+        "thumb": true,
+        "aiScore": 5.8,
+        "aiReason": "The page is empty and does not display any 3D scene or shapes.",
+        "aiScores": {
+          "qwen2.5vl:7b": 5,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 1.5
+      },
+      {
+        "model": "hermes3-8b",
+        "status": "done",
+        "error": null,
+        "seconds": 27,
+        "cost": 0,
+        "started_at": "2026-07-23T18:33:26.819Z",
+        "finished_at": "2026-07-23T18:33:54.210Z",
+        "queued_at": "2026-07-23T18:31:48.448Z",
+        "resumes": 1,
+        "bytes": 6222,
+        "thumb": true,
+        "aiScore": 7,
+        "aiReason": "The model successfully renders a rotating wireframe torus and includes interactive elements like drag to orbit and shape selector. However, the visual quality is somewhat basic with limited color and ",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 7
+        },
+        "aiSpread": 0
+      },
+      {
+        "model": "qwen25-7b",
+        "status": "done",
+        "error": null,
+        "seconds": 30,
+        "cost": 0,
+        "started_at": "2026-07-23T18:31:48.465Z",
+        "finished_at": "2026-07-23T18:32:18.361Z",
+        "queued_at": "2026-07-23T18:31:48.449Z",
+        "resumes": 1,
+        "bytes": 2773,
+        "thumb": true,
+        "aiScore": 6.8,
+        "aiReason": "The model fulfills the challenge by rendering a wireframe torus and includes basic interactivity but lacks visual quality and complexity.",
+        "aiScores": {
+          "qwen2.5vl:7b": 6,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 1.5
+      },
+      {
+        "model": "hf-qwen-coder-32b",
+        "status": "done",
+        "error": null,
+        "seconds": 77,
+        "cost": 0,
+        "started_at": "2026-07-23T18:33:54.212Z",
+        "finished_at": "2026-07-23T18:35:11.161Z",
+        "queued_at": "2026-07-23T18:31:48.450Z",
+        "resumes": 1,
+        "bytes": 4978,
+        "thumb": true,
+        "aiScore": 5.3,
+        "aiReason": "The page is empty and does not display any of the required elements or content.",
+        "aiScores": {
+          "qwen2.5vl:7b": 4,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 2.5
+      },
+      {
+        "model": "claude-code",
+        "status": "done",
+        "error": null,
+        "seconds": 83,
+        "cost": 0,
+        "started_at": "2026-07-23T18:31:48.467Z",
+        "finished_at": "2026-07-23T18:33:11.783Z",
+        "queued_at": "2026-07-23T18:31:48.452Z",
+        "resumes": 1,
+        "bytes": 16545,
+        "thumb": true,
+        "aiScore": 9.3,
+        "aiReason": "The model fulfills the challenge requirements and has good visual quality.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 9.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "kimi",
+        "status": "done",
+        "error": null,
+        "seconds": 177,
+        "cost": 0.0281,
+        "started_at": "2026-07-23T18:31:48.470Z",
+        "finished_at": "2026-07-23T18:34:45.019Z",
+        "queued_at": "2026-07-23T18:31:48.453Z",
+        "resumes": 1,
+        "bytes": 11775,
+        "thumb": true,
+        "aiScore": 9.3,
+        "aiReason": "The model fulfills the challenge requirements and has good visual quality.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 9.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "gpt",
+        "status": "done",
+        "error": null,
+        "seconds": 31,
+        "cost": 0.0439,
+        "started_at": "2026-07-23T18:31:48.478Z",
+        "finished_at": "2026-07-23T18:32:19.336Z",
+        "queued_at": "2026-07-23T18:31:48.455Z",
+        "resumes": 1,
+        "bytes": 11219,
+        "thumb": true,
+        "aiScore": 9.3,
+        "aiReason": "The model meets the challenge criteria and has good visual quality.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 9.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "grok",
+        "status": "done",
+        "error": null,
+        "seconds": 60,
+        "cost": 0.0816,
+        "started_at": "2026-07-23T18:31:48.480Z",
+        "finished_at": "2026-07-23T18:32:48.250Z",
+        "queued_at": "2026-07-23T18:31:48.456Z",
+        "resumes": 1,
+        "bytes": 14658,
+        "thumb": true,
+        "aiScore": 9.3,
+        "aiReason": "The model fulfills the challenge requirements and has good visual quality.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 9.5
+        },
+        "aiSpread": 0.5
+      }
+    ],
+    "aiPick": "claude-code",
+    "judging": false,
+    "judged_at": "2026-07-23T18:35:58.602Z",
+    "voted_at": "2026-07-23T18:36:03.512Z"
+  },
+  {
+    "id": "ebf4f7984098",
+    "title": "All-Models — Aquarium Sim",
+    "prompt": "Build a single-file HTML page: a relaxing virtual aquarium. Fish with steering/flocking behavior swim across a gradient underwater scene with bubbles, swaying plants, and light rays; click to drop food that fish chase; add-fish button. Canvas-based, self-contained, no external assets.",
+    "category": "Custom",
+    "designTools": false,
+    "created_at": "2026-07-23T18:36:31.877Z",
+    "winner": "claude-code",
+    "runs": [
+      {
+        "model": "qwen3-14b",
+        "status": "done",
+        "error": null,
+        "seconds": 50,
+        "cost": 0,
+        "started_at": "2026-07-23T18:36:31.916Z",
+        "finished_at": "2026-07-23T18:37:21.967Z",
+        "queued_at": "2026-07-23T18:36:31.887Z",
+        "bytes": 8140,
+        "thumb": true,
+        "aiScore": 7.3,
+        "aiReason": "The model demonstrates basic fish behavior and visual elements but lacks the complexity and polish expected for a relaxing aquarium simulation.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "gemma3-12b",
+        "status": "done",
+        "error": null,
+        "seconds": 60,
+        "cost": 0,
+        "started_at": "2026-07-23T18:37:21.972Z",
+        "finished_at": "2026-07-23T18:38:21.816Z",
+        "queued_at": "2026-07-23T18:36:31.894Z",
+        "bytes": 7151,
+        "thumb": true,
+        "aiScore": 6.8,
+        "aiReason": "The page has basic functionality but lacks visual elements like bubbles and swaying plants.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "hermes3-8b",
+        "status": "done",
+        "error": null,
+        "seconds": 15,
+        "cost": 0,
+        "started_at": "2026-07-23T18:38:21.819Z",
+        "finished_at": "2026-07-23T18:38:36.805Z",
+        "queued_at": "2026-07-23T18:36:31.898Z",
+        "bytes": 4381,
+        "thumb": true,
+        "aiScore": 4.8,
+        "aiReason": "The page is empty and does not contain any visual elements or functionality related to the challenge.",
+        "aiScores": {
+          "qwen2.5vl:7b": 3,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 3.5
+      },
+      {
+        "model": "qwen25-7b",
+        "status": "done",
+        "error": null,
+        "seconds": 21,
+        "cost": 0,
+        "started_at": "2026-07-23T18:36:31.918Z",
+        "finished_at": "2026-07-23T18:36:52.992Z",
+        "queued_at": "2026-07-23T18:36:31.903Z",
+        "bytes": 2995,
+        "thumb": true,
+        "aiScore": 6.3,
+        "aiReason": "The page has basic elements but lacks detailed aquatic life and interactive features.",
+        "aiScores": {
+          "qwen2.5vl:7b": 6,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "hf-qwen-coder-32b",
+        "status": "done",
+        "error": null,
+        "seconds": 70,
+        "cost": 0,
+        "started_at": "2026-07-23T18:38:36.809Z",
+        "finished_at": "2026-07-23T18:39:46.631Z",
+        "queued_at": "2026-07-23T18:36:31.906Z",
+        "bytes": 4683,
+        "thumb": true
+      },
+      {
+        "model": "claude-code",
+        "status": "done",
+        "error": null,
+        "seconds": 81,
+        "cost": 0,
+        "started_at": "2026-07-23T18:36:31.921Z",
+        "finished_at": "2026-07-23T18:37:52.470Z",
+        "queued_at": "2026-07-23T18:36:31.910Z",
+        "bytes": 14948,
+        "thumb": true,
+        "aiScore": 9.3,
+        "aiReason": "The aquarium is visually appealing and fulfills all the requirements of the challenge.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 9.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "kimi",
+        "status": "done",
+        "error": null,
+        "seconds": 67,
+        "cost": 0.0126,
+        "started_at": "2026-07-23T18:36:31.937Z",
+        "finished_at": "2026-07-23T18:37:39.186Z",
+        "queued_at": "2026-07-23T18:36:31.912Z",
+        "bytes": 12722,
+        "thumb": true,
+        "aiScore": 8.3,
+        "aiReason": "The aquarium is visually appealing and fulfills all the requirements of the challenge.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 1.5
+      },
+      {
+        "model": "gpt",
+        "status": "done",
+        "error": null,
+        "seconds": 71,
+        "cost": 0.0778,
+        "started_at": "2026-07-23T18:36:31.939Z",
+        "finished_at": "2026-07-23T18:37:42.727Z",
+        "queued_at": "2026-07-23T18:36:31.913Z",
+        "bytes": 19897,
+        "thumb": true,
+        "aiScore": 8.3,
+        "aiReason": "The aquarium is visually appealing and fulfills all the requirements of the challenge.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 1.5
+      },
+      {
+        "model": "grok",
+        "status": "done",
+        "error": null,
+        "seconds": 86,
+        "cost": 0.1201,
+        "started_at": "2026-07-23T18:36:31.942Z",
+        "finished_at": "2026-07-23T18:37:57.912Z",
+        "queued_at": "2026-07-23T18:36:31.915Z",
+        "bytes": 20857,
+        "thumb": true,
+        "aiScore": 9.3,
+        "aiReason": "The aquarium is visually appealing and fulfills all the requirements of the challenge.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 9.5
+        },
+        "aiSpread": 0.5
+      }
+    ],
+    "judged_at": "2026-07-23T18:40:40.253Z",
+    "judging": false,
+    "aiPick": "claude-code",
+    "voted_at": "2026-07-23T18:40:48.567Z"
+  },
+  {
+    "id": "0cb3fc06b069",
+    "title": "Free-Roster — Particle Text",
+    "prompt": "Build a single-file HTML page: text that forms from particles. Type a word; thousands of particles fly in to spell it, and scatter on mouse hover then reform. Canvas-based, self-contained, no external libraries.",
+    "category": "Games",
+    "designTools": false,
+    "created_at": "2026-07-23T18:40:48.585Z",
+    "winner": "qwen3-14b",
+    "runs": [
+      {
+        "model": "qwen3-14b",
+        "status": "done",
+        "error": null,
+        "seconds": 27,
+        "cost": 0,
+        "started_at": "2026-07-23T18:40:48.615Z",
+        "finished_at": "2026-07-23T18:41:15.191Z",
+        "queued_at": "2026-07-23T18:40:48.589Z",
+        "bytes": 3692,
+        "thumb": true,
+        "aiScore": 9.3,
+        "aiReason": "The page is self-contained and fulfills the challenge requirements effectively.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 9.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "gemma3-12b",
+        "status": "done",
+        "error": null,
+        "seconds": 24,
+        "cost": 0,
+        "started_at": "2026-07-23T18:41:15.197Z",
+        "finished_at": "2026-07-23T18:41:38.958Z",
+        "queued_at": "2026-07-23T18:40:48.594Z",
+        "bytes": 2827,
+        "thumb": true,
+        "aiScore": 7.3,
+        "aiReason": "The particle effect is visible and the word appears correctly, but it lacks interactivity upon mouse hover.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "hermes3-8b",
+        "status": "done",
+        "error": null,
+        "seconds": 11,
+        "cost": 0,
+        "started_at": "2026-07-23T18:41:38.960Z",
+        "finished_at": "2026-07-23T18:41:49.707Z",
+        "queued_at": "2026-07-23T18:40:48.600Z",
+        "bytes": 2831,
+        "thumb": true,
+        "aiScore": 5.5,
+        "aiReason": "The page is blank and does not display any particles or text.",
+        "aiScores": {
+          "qwen2.5vl:7b": 5,
+          "minicpm-v:latest": 6
+        },
+        "aiSpread": 1
+      },
+      {
+        "model": "qwen25-7b",
+        "status": "done",
+        "error": null,
+        "seconds": 17,
+        "cost": 0,
+        "started_at": "2026-07-23T18:40:48.618Z",
+        "finished_at": "2026-07-23T18:41:05.990Z",
+        "queued_at": "2026-07-23T18:40:48.604Z",
+        "bytes": 2968,
+        "thumb": true,
+        "aiScore": 5.3,
+        "aiReason": "The page is blank and does not display any particles or text.",
+        "aiScores": {
+          "qwen2.5vl:7b": 3,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 4.5
+      },
+      {
+        "model": "hf-qwen-coder-32b",
+        "status": "done",
+        "error": null,
+        "seconds": 40,
+        "cost": 0,
+        "started_at": "2026-07-23T18:41:49.711Z",
+        "finished_at": "2026-07-23T18:42:29.664Z",
+        "queued_at": "2026-07-23T18:40:48.608Z",
+        "bytes": 3047,
+        "thumb": true,
+        "aiScore": 7.3,
+        "aiReason": "The word is displayed correctly but lacks the particle animation and mouse hover effect.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "claude-code",
+        "status": "done",
+        "error": null,
+        "seconds": 48,
+        "cost": 0,
+        "started_at": "2026-07-23T18:40:48.620Z",
+        "finished_at": "2026-07-23T18:41:37.089Z",
+        "queued_at": "2026-07-23T18:40:48.611Z",
+        "bytes": 7503,
+        "thumb": true,
+        "aiScore": 9.3,
+        "aiReason": "The page successfully forms text from particles and allows for scattering and reforming upon mouse hover.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 9.5
+        },
+        "aiSpread": 0.5
+      }
+    ],
+    "judging": false,
+    "judged_at": "2026-07-23T18:43:45.849Z",
+    "aiPick": "qwen3-14b",
+    "voted_at": "2026-07-23T18:43:49.240Z"
+  },
+  {
+    "id": "1d4944290382",
+    "title": "Free-Roster — Isometric City",
+    "prompt": "Build a single-file HTML page: a tiny isometric city builder. Click grid tiles to cycle through building types (house/tree/road/shop) drawn as isometric blocks; a rotate-view button; auto-count of each type. Canvas-based, self-contained, no external libraries.",
+    "category": "Custom",
+    "designTools": false,
+    "created_at": "2026-07-23T18:44:09.861Z",
+    "winner": "claude-code",
+    "runs": [
+      {
+        "model": "qwen3-14b",
+        "status": "done",
+        "error": null,
+        "seconds": 37,
+        "cost": 0,
+        "started_at": "2026-07-23T18:44:09.896Z",
+        "finished_at": "2026-07-23T18:44:47.078Z",
+        "queued_at": "2026-07-23T18:44:09.869Z",
+        "bytes": 5437,
+        "thumb": true,
+        "aiScore": 6.8,
+        "aiReason": "The page is functional and meets the challenge criteria but lacks visual appeal.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "gemma3-12b",
+        "status": "done",
+        "error": null,
+        "seconds": 70,
+        "cost": 0,
+        "started_at": "2026-07-23T18:44:47.084Z",
+        "finished_at": "2026-07-23T18:45:57.447Z",
+        "queued_at": "2026-07-23T18:44:09.873Z",
+        "bytes": 4816,
+        "thumb": true,
+        "aiScore": 6.8,
+        "aiReason": "The page is functional but lacks visual appeal and detail.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "hermes3-8b",
+        "status": "done",
+        "error": null,
+        "seconds": 28,
+        "cost": 0,
+        "started_at": "2026-07-23T18:45:57.451Z",
+        "finished_at": "2026-07-23T18:46:25.061Z",
+        "queued_at": "2026-07-23T18:44:09.878Z",
+        "bytes": 2896,
+        "thumb": true,
+        "aiScore": 6.8,
+        "aiReason": "The page is functional but lacks visual appeal and detail.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 6.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "qwen25-7b",
+        "status": "done",
+        "error": null,
+        "seconds": 28,
+        "cost": 0,
+        "started_at": "2026-07-23T18:44:09.900Z",
+        "finished_at": "2026-07-23T18:44:37.421Z",
+        "queued_at": "2026-07-23T18:44:09.884Z",
+        "bytes": 2988,
+        "thumb": true,
+        "aiScore": 7.3,
+        "aiReason": "The model fulfills the challenge requirements but lacks aesthetic appeal and interactivity.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "hf-qwen-coder-32b",
+        "status": "done",
+        "error": null,
+        "seconds": 89,
+        "cost": 0,
+        "started_at": "2026-07-23T18:46:25.065Z",
+        "finished_at": "2026-07-23T18:47:54.434Z",
+        "queued_at": "2026-07-23T18:44:09.888Z",
+        "bytes": 3462,
+        "thumb": true,
+        "aiScore": 7.3,
+        "aiReason": "The model fulfills the challenge requirements but lacks visual appeal and detail.",
+        "aiScores": {
+          "qwen2.5vl:7b": 7,
+          "minicpm-v:latest": 7.5
+        },
+        "aiSpread": 0.5
+      },
+      {
+        "model": "claude-code",
+        "status": "done",
+        "error": null,
+        "seconds": 109,
+        "cost": 0,
+        "started_at": "2026-07-23T18:44:09.902Z",
+        "finished_at": "2026-07-23T18:45:58.539Z",
+        "queued_at": "2026-07-23T18:44:09.892Z",
+        "bytes": 12389,
+        "thumb": true,
+        "aiScore": 9.3,
+        "aiReason": "The model fulfills the challenge by providing an isometric city builder with grid tiles and building types, and it has a polished visual quality.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 9.5
+        },
+        "aiSpread": 0.5
+      }
+    ],
+    "judging": false,
+    "judged_at": "2026-07-23T18:48:38.998Z",
+    "aiPick": "claude-code",
+    "voted_at": "2026-07-23T18:48:40.763Z"
+  },
+  {
+    "id": "68b40458a368",
+    "title": "Free-Roster — Rope Physics",
+    "prompt": "Build a single-file HTML page: an interactive rope/cloth physics toy. A rope of verlet-integrated points hangs and swings; drag any point with the mouse; a cut tool severs segments; reset button. Canvas-based, self-contained, no external libraries.",
+    "category": "Games",
+    "designTools": false,
+    "created_at": "2026-07-23T18:49:00.915Z",
+    "winner": null,
+    "runs": [
+      {
+        "model": "qwen3-14b",
+        "status": "done",
+        "error": null,
+        "seconds": 35,
+        "cost": 0,
+        "started_at": "2026-07-23T18:49:00.943Z",
+        "finished_at": "2026-07-23T18:49:35.697Z",
+        "queued_at": "2026-07-23T18:49:00.920Z",
+        "bytes": 5085,
+        "thumb": true
+      },
+      {
+        "model": "gemma3-12b",
+        "status": "done",
+        "error": null,
+        "seconds": 56,
+        "cost": 0,
+        "started_at": "2026-07-23T18:49:35.703Z",
+        "finished_at": "2026-07-23T18:50:31.909Z",
+        "queued_at": "2026-07-23T18:49:00.925Z",
+        "bytes": 4201,
+        "thumb": true
+      },
+      {
+        "model": "hermes3-8b",
+        "status": "running",
+        "error": null,
+        "seconds": null,
+        "cost": null,
+        "started_at": "2026-07-23T18:50:31.916Z",
+        "finished_at": null,
+        "queued_at": "2026-07-23T18:49:00.929Z"
+      },
+      {
+        "model": "qwen25-7b",
+        "status": "done",
+        "error": null,
+        "seconds": 17,
+        "cost": 0,
+        "started_at": "2026-07-23T18:49:00.946Z",
+        "finished_at": "2026-07-23T18:49:18.251Z",
+        "queued_at": "2026-07-23T18:49:00.933Z",
+        "bytes": 2448,
+        "thumb": true
+      },
+      {
+        "model": "hf-qwen-coder-32b",
+        "status": "queued",
+        "error": null,
+        "seconds": null,
+        "cost": null,
+        "started_at": null,
+        "finished_at": null,
+        "queued_at": "2026-07-23T18:49:00.936Z"
+      },
+      {
+        "model": "claude-code",
+        "status": "done",
+        "error": null,
+        "seconds": 69,
+        "cost": 0,
+        "started_at": "2026-07-23T18:49:00.948Z",
+        "finished_at": "2026-07-23T18:50:10.357Z",
+        "queued_at": "2026-07-23T18:49:00.940Z",
+        "bytes": 13057,
         "thumb": true
       }
     ]
diff --git a/data/costlog.jsonl b/data/costlog.jsonl
index e7c3fa7..45461b0 100644
--- a/data/costlog.jsonl
+++ b/data/costlog.jsonl
@@ -141,3 +141,16 @@
 {"ts":"2026-07-23T18:16:38.382Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":142,"output_tokens":9885,"cost_usd":0.024798}
 {"ts":"2026-07-23T18:20:03.921Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":354,"output_tokens":7503,"cost_usd":0.113607}
 {"ts":"2026-07-23T18:20:35.157Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":144,"output_tokens":10279,"cost_usd":0.144158}
+{"ts":"2026-07-23T18:21:03.830Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":146,"output_tokens":8728,"cost_usd":0.021908}
+{"ts":"2026-07-23T18:23:30.124Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":141,"output_tokens":2912,"cost_usd":0.041015}
+{"ts":"2026-07-23T18:24:04.432Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":347,"output_tokens":5087,"cost_usd":0.077346}
+{"ts":"2026-07-23T18:25:34.441Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":142,"output_tokens":10061,"cost_usd":0.025238}
+{"ts":"2026-07-23T18:27:29.598Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":134,"output_tokens":4245,"cost_usd":0.059665}
+{"ts":"2026-07-23T18:27:40.399Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":343,"output_tokens":4067,"cost_usd":0.062034}
+{"ts":"2026-07-23T18:29:01.604Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":135,"output_tokens":8506,"cost_usd":0.021346}
+{"ts":"2026-07-23T18:32:19.334Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":141,"output_tokens":3118,"cost_usd":0.043899}
+{"ts":"2026-07-23T18:32:48.247Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":346,"output_tokens":5372,"cost_usd":0.081618}
+{"ts":"2026-07-23T18:34:45.015Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":141,"output_tokens":11202,"cost_usd":0.02809}
+{"ts":"2026-07-23T18:37:39.182Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":140,"output_tokens":5016,"cost_usd":0.012624}
+{"ts":"2026-07-23T18:37:42.724Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":139,"output_tokens":5539,"cost_usd":0.077789}
+{"ts":"2026-07-23T18:37:57.910Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":347,"output_tokens":7939,"cost_usd":0.120126}
diff --git a/data/votes.jsonl b/data/votes.jsonl
index e2bd172..2751341 100644
--- a/data/votes.jsonl
+++ b/data/votes.jsonl
@@ -34,3 +34,11 @@
 {"ts":"2026-07-23T18:09:35.064Z","challenge":"556ee195503a","title":"All-Models — Flappy Clone","winner":"gpt","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
 {"ts":"2026-07-23T18:13:25.122Z","challenge":"963a77fd0031","title":"All-Models — Color Palette Tool","winner":"gpt","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
 {"ts":"2026-07-23T18:18:11.985Z","challenge":"db2cfc877c3d","title":"All-Models — Sorting Visualizer","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T18:22:41.612Z","challenge":"7bf57916e775","title":"All-Models — Breakout Game","winner":"gpt","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","kimi","gpt","grok"]}
+{"ts":"2026-07-23T18:26:34.949Z","challenge":"9cbc4e3c60a6","title":"All-Models — Markdown Previewer","winner":"kimi","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","kimi","gpt","grok"]}
+{"ts":"2026-07-23T18:31:25.340Z","challenge":"04692f72ef1d","title":"All-Models — Tetris","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T18:36:03.512Z","challenge":"e840f1972643","title":"All-Models — 3D Wireframe Spin","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T18:40:42.656Z","challenge":"ebf4f7984098","title":"All-Models — Aquarium Sim","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T18:40:48.567Z","challenge":"ebf4f7984098","title":"All-Models — Aquarium Sim","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T18:43:49.240Z","challenge":"0cb3fc06b069","title":"Free-Roster — Particle Text","winner":"qwen3-14b","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code"]}
+{"ts":"2026-07-23T18:48:40.763Z","challenge":"1d4944290382","title":"Free-Roster — Isometric City","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code"]}
diff --git a/tetris.html b/tetris.html
new file mode 100644
index 0000000..c81d31e
--- /dev/null
+++ b/tetris.html
@@ -0,0 +1,372 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Tetris</title>
+<style>
+  :root{
+    --bg:#0d1117; --panel:#161b22; --grid:#1c2330; --line:#2a3345;
+    --text:#e6edf3; --muted:#8b949e; --accent:#58a6ff;
+  }
+  *{box-sizing:border-box; margin:0; padding:0;}
+  html,body{height:100%;}
+  body{
+    background:radial-gradient(circle at 50% -10%, #1a2233, var(--bg));
+    color:var(--text);
+    font-family:"Segoe UI",system-ui,-apple-system,sans-serif;
+    display:flex; align-items:center; justify-content:center;
+    min-height:100vh; padding:16px; user-select:none;
+  }
+  .wrap{
+    display:flex; gap:20px; align-items:flex-start;
+    flex-wrap:wrap; justify-content:center;
+  }
+  .board-shell{position:relative;}
+  canvas{
+    background:var(--grid);
+    border:2px solid var(--line);
+    border-radius:8px;
+    display:block;
+    box-shadow:0 10px 40px rgba(0,0,0,.5);
+  }
+  .side{
+    display:flex; flex-direction:column; gap:16px; width:180px;
+  }
+  .card{
+    background:var(--panel); border:1px solid var(--line);
+    border-radius:10px; padding:14px;
+  }
+  .card h2{
+    font-size:11px; letter-spacing:2px; text-transform:uppercase;
+    color:var(--muted); margin-bottom:8px; font-weight:600;
+  }
+  .stat{font-size:28px; font-weight:700; font-variant-numeric:tabular-nums;}
+  #next{display:block; margin:0 auto; background:var(--grid); border-radius:6px;}
+  .keys{font-size:12px; line-height:1.9; color:var(--muted);}
+  .keys b{color:var(--text); font-weight:600;}
+  kbd{
+    background:#21262d; border:1px solid #30363d; border-bottom-width:2px;
+    border-radius:4px; padding:1px 6px; font-size:11px; color:var(--text);
+  }
+  .title{
+    font-size:26px; font-weight:800; letter-spacing:4px;
+    background:linear-gradient(90deg,#58a6ff,#bc8cff,#ff7b72);
+    -webkit-background-clip:text; background-clip:text; color:transparent;
+    text-align:center; margin-bottom:4px;
+  }
+  .overlay{
+    position:absolute; inset:0; display:flex; flex-direction:column;
+    align-items:center; justify-content:center; gap:14px;
+    background:rgba(6,9,15,.82); border-radius:8px; backdrop-filter:blur(3px);
+    text-align:center; padding:20px;
+  }
+  .overlay.hidden{display:none;}
+  .overlay h3{font-size:30px; font-weight:800; letter-spacing:1px;}
+  .overlay p{color:var(--muted); font-size:14px; max-width:220px;}
+  button{
+    background:var(--accent); color:#04101f; border:none;
+    padding:11px 22px; font-size:15px; font-weight:700; border-radius:8px;
+    cursor:pointer; letter-spacing:.5px; transition:transform .08s, filter .15s;
+  }
+  button:hover{filter:brightness(1.1);}
+  button:active{transform:scale(.96);}
+</style>
+</head>
+<body>
+  <div class="wrap">
+    <div class="board-shell">
+      <canvas id="board" width="300" height="600"></canvas>
+      <div id="overlay" class="overlay">
+        <h3 id="ovTitle">TETRIS</h3>
+        <p id="ovText">Stack the blocks, clear the lines.</p>
+        <button id="startBtn">Start Game</button>
+      </div>
+    </div>
+    <div class="side">
+      <div class="title">TETRIS</div>
+      <div class="card">
+        <h2>Score</h2>
+        <div class="stat" id="score">0</div>
+      </div>
+      <div class="card">
+        <h2>Next</h2>
+        <canvas id="next" width="120" height="120"></canvas>
+      </div>
+      <div class="card" style="display:flex; gap:12px;">
+        <div><h2>Lines</h2><div class="stat" id="lines" style="font-size:20px;">0</div></div>
+        <div><h2>Level</h2><div class="stat" id="level" style="font-size:20px;">1</div></div>
+      </div>
+      <div class="card keys">
+        <h2>Controls</h2>
+        <div><kbd>←</kbd> <kbd>→</kbd> <b>Move</b></div>
+        <div><kbd>↑</kbd> <b>Rotate</b></div>
+        <div><kbd>↓</kbd> <b>Soft drop</b></div>
+        <div><kbd>Space</kbd> <b>Hard drop</b></div>
+        <div><kbd>P</kbd> <b>Pause</b></div>
+      </div>
+    </div>
+  </div>
+
+<script>
+(function(){
+  "use strict";
+  const COLS = 10, ROWS = 20, CELL = 30;
+  const board   = document.getElementById("board");
+  const ctx     = board.getContext("2d");
+  const nextCv  = document.getElementById("next");
+  const nctx    = nextCv.getContext("2d");
+  const scoreEl = document.getElementById("score");
+  const linesEl = document.getElementById("lines");
+  const levelEl = document.getElementById("level");
+  const overlay = document.getElementById("overlay");
+  const ovTitle = document.getElementById("ovTitle");
+  const ovText  = document.getElementById("ovText");
+  const startBtn= document.getElementById("startBtn");
+
+  // Tetromino shapes (rotation states derived by matrix rotation from base)
+  const SHAPES = {
+    I: {color:"#4ad9d9", cells:[[0,1],[1,1],[2,1],[3,1]], size:4},
+    O: {color:"#f2d94e", cells:[[1,0],[2,0],[1,1],[2,1]], size:4},
+    T: {color:"#b06cf0", cells:[[1,0],[0,1],[1,1],[2,1]], size:3},
+    S: {color:"#5ad863", cells:[[1,0],[2,0],[0,1],[1,1]], size:3},
+    Z: {color:"#f2604e", cells:[[0,0],[1,0],[1,1],[2,1]], size:3},
+    J: {color:"#5a86f0", cells:[[0,0],[0,1],[1,1],[2,1]], size:3},
+    L: {color:"#f0a03c", cells:[[2,0],[0,1],[1,1],[2,1]], size:3}
+  };
+  const TYPES = Object.keys(SHAPES);
+
+  let grid, cur, nextType, score, lines, level, dropInterval, dropTimer;
+  let running=false, paused=false, over=false;
+
+  function emptyGrid(){
+    return Array.from({length:ROWS}, ()=>Array(COLS).fill(null));
+  }
+
+  function makePiece(type){
+    const s = SHAPES[type];
+    // build a size x size matrix
+    const m = Array.from({length:s.size},()=>Array(s.size).fill(0));
+    s.cells.forEach(([x,y])=> m[y][x]=1);
+    return {
+      type, color:s.color, matrix:m,
+      x: Math.floor((COLS - s.size)/2), y:-topOffset(m)
+    };
+  }
+  function topOffset(m){
+    for(let y=0;y<m.length;y++) if(m[y].some(v=>v)) return y;
+    return 0;
+  }
+
+  function rotate(matrix){
+    const n = matrix.length;
+    const r = Array.from({length:n},()=>Array(n).fill(0));
+    for(let y=0;y<n;y++) for(let x=0;x<n;x++) r[x][n-1-y]=matrix[y][x];
+    return r;
+  }
+
+  function collide(piece, offX, offY, matrix){
+    const m = matrix || piece.matrix;
+    for(let y=0;y<m.length;y++){
+      for(let x=0;x<m[y].length;x++){
+        if(!m[y][x]) continue;
+        const nx = piece.x + x + offX;
+        const ny = piece.y + y + offY;
+        if(nx<0 || nx>=COLS || ny>=ROWS) return true;
+        if(ny>=0 && grid[ny][nx]) return true;
+      }
+    }
+    return false;
+  }
+
+  function merge(piece){
+    piece.matrix.forEach((row,y)=>row.forEach((v,x)=>{
+      if(v){
+        const ny = piece.y+y, nx = piece.x+x;
+        if(ny>=0) grid[ny][nx]=piece.color;
+      }
+    }));
+  }
+
+  function clearLines(){
+    let cleared=0;
+    for(let y=ROWS-1;y>=0;y--){
+      if(grid[y].every(c=>c)){
+        grid.splice(y,1);
+        grid.unshift(Array(COLS).fill(null));
+        cleared++; y++;
+      }
+    }
+    if(cleared){
+      const pts=[0,100,300,500,800];
+      score += pts[cleared]*level;
+      lines += cleared;
+      const newLevel = Math.floor(lines/10)+1;
+      if(newLevel!==level){ level=newLevel; dropInterval=Math.max(80, 1000-(level-1)*80); }
+      updateStats();
+    }
+  }
+
+  function spawn(){
+    cur = makePiece(nextType);
+    nextType = TYPES[Math.floor(Math.random()*TYPES.length)];
+    drawNext();
+    if(collide(cur,0,0)){ gameOver(); }
+  }
+
+  function drop(){
+    if(!collide(cur,0,1)){
+      cur.y++;
+    } else {
+      merge(cur);
+      clearLines();
+      spawn();
+    }
+    draw();
+  }
+
+  function hardDrop(){
+    let d=0;
+    while(!collide(cur,0,d+1)) d++;
+    cur.y+=d;
+    score += d*2;
+    merge(cur); clearLines(); spawn(); updateStats(); draw();
+  }
+
+  function move(dir){
+    if(!collide(cur,dir,0)) cur.x+=dir, draw();
+  }
+
+  function tryRotate(){
+    const r = rotate(cur.matrix);
+    // wall kicks
+    for(const k of [0,-1,1,-2,2]){
+      if(!collide(cur,k,0,r)){ cur.matrix=r; cur.x+=k; draw(); return; }
+    }
+  }
+
+  // ---------- rendering ----------
+  function cellRect(cx,x,y,color,ctx2){
+    ctx2.fillStyle=color;
+    ctx2.fillRect(x*cx, y*cx, cx, cx);
+    ctx2.fillStyle="rgba(255,255,255,.18)";
+    ctx2.fillRect(x*cx, y*cx, cx, cx*0.18);
+    ctx2.fillStyle="rgba(0,0,0,.22)";
+    ctx2.fillRect(x*cx, y*cx+cx*0.82, cx, cx*0.18);
+    ctx2.strokeStyle="rgba(0,0,0,.35)";
+    ctx2.lineWidth=1;
+    ctx2.strokeRect(x*cx+0.5, y*cx+0.5, cx-1, cx-1);
+  }
+
+  function draw(){
+    ctx.clearRect(0,0,board.width,board.height);
+    // grid lines
+    ctx.strokeStyle="rgba(255,255,255,.04)";
+    ctx.lineWidth=1;
+    for(let x=1;x<COLS;x++){ ctx.beginPath(); ctx.moveTo(x*CELL,0); ctx.lineTo(x*CELL,ROWS*CELL); ctx.stroke(); }
+    for(let y=1;y<ROWS;y++){ ctx.beginPath(); ctx.moveTo(0,y*CELL); ctx.lineTo(COLS*CELL,y*CELL); ctx.stroke(); }
+    // settled
+    for(let y=0;y<ROWS;y++) for(let x=0;x<COLS;x++)
+      if(grid[y][x]) cellRect(CELL,x,y,grid[y][x],ctx);
+    if(!cur) return;
+    // ghost
+    let gd=0; while(!collide(cur,0,gd+1)) gd++;
+    ctx.globalAlpha=0.22;
+    cur.matrix.forEach((row,y)=>row.forEach((v,x)=>{
+      if(v && cur.y+y+gd>=0) cellRect(CELL,cur.x+x,cur.y+y+gd,cur.color,ctx);
+    }));
+    ctx.globalAlpha=1;
+    // current
+    cur.matrix.forEach((row,y)=>row.forEach((v,x)=>{
+      if(v && cur.y+y>=0) cellRect(CELL,cur.x+x,cur.y+y,cur.color,ctx);
+    }));
+  }
+
+  function drawNext(){
+    nctx.clearRect(0,0,nextCv.width,nextCv.height);
+    const s = SHAPES[nextType];
+    const cx = 24;
+    // center it
+    let minx=99,maxx=-9,miny=99,maxy=-9;
+    s.cells.forEach(([x,y])=>{minx=Math.min(minx,x);maxx=Math.max(maxx,x);miny=Math.min(miny,y);maxy=Math.max(maxy,y);});
+    const w=(maxx-minx+1), h=(maxy-miny+1);
+    const ox=(nextCv.width/cx - w)/2 - minx;
+    const oy=(nextCv.height/cx - h)/2 - miny;
+    s.cells.forEach(([x,y])=> cellRect(cx, x+ox, y+oy, s.color, nctx));
+  }
+
+  function updateStats(){
+    scoreEl.textContent=score;
+    linesEl.textContent=lines;
+    levelEl.textContent=level;
+  }
+
+  // ---------- loop ----------
+  function tick(){
+    if(!running||paused||over) return;
+    drop();
+    dropTimer=setTimeout(tick, dropInterval);
+  }
+  function restartTimer(){
+    clearTimeout(dropTimer);
+    if(running&&!paused&&!over) dropTimer=setTimeout(tick, dropInterval);
+  }
+
+  function startGame(){
+    grid=emptyGrid();
+    score=0; lines=0; level=1; dropInterval=1000;
+    running=true; paused=false; over=false;
+    nextType=TYPES[Math.floor(Math.random()*TYPES.length)];
+    updateStats();
+    spawn(); draw();
+    overlay.classList.add("hidden");
+    clearTimeout(dropTimer);
+    dropTimer=setTimeout(tick, dropInterval);
+  }
+
+  function togglePause(){
+    if(!running||over) return;
+    paused=!paused;
+    if(paused){
+      clearTimeout(dropTimer);
+      showOverlay("PAUSED","Press P or the button to resume.","Resume");
+    } else {
+      overlay.classList.add("hidden");
+      restartTimer();
+    }
+  }
+
+  function gameOver(){
+    over=true; running=false;
+    clearTimeout(dropTimer);
+    showOverlay("GAME OVER","Score: "+score+" • Lines: "+lines,"Play Again");
+  }
+
+  function showOverlay(t,txt,btn){
+    ovTitle.textContent=t; ovText.textContent=txt; startBtn.textContent=btn;
+    overlay.classList.remove("hidden");
+  }
+
+  startBtn.addEventListener("click", ()=>{
+    if(paused && running && !over){ togglePause(); }
+    else startGame();
+  });
+
+  document.addEventListener("keydown",(e)=>{
+    if(e.key==="p"||e.key==="P"){ e.preventDefault(); togglePause(); return; }
+    if(!running||paused||over) return;
+    switch(e.key){
+      case "ArrowLeft":  e.preventDefault(); move(-1); break;
+      case "ArrowRight": e.preventDefault(); move(1); break;
+      case "ArrowUp":    e.preventDefault(); tryRotate(); break;
+      case "ArrowDown":  e.preventDefault(); drop(); score+=1; updateStats(); restartTimer(); break;
+      case " ":          e.preventDefault(); hardDrop(); restartTimer(); break;
+    }
+  });
+
+  // initial idle board
+  grid=emptyGrid(); draw(); nextType=TYPES[0]; drawNext();
+})();
+</script>
+</body>
+</html>

← 92a5f7d arena: show $ spend on every model chip (card grid)  ·  back to Model Arena  ·  arena: cost dashboard across top (today/week/month/year/all- 595106f →