← 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
A data/artifacts/04692f72ef1d/claude-code.htmlA data/artifacts/04692f72ef1d/claude-code.pngA data/artifacts/04692f72ef1d/gemma3-12b.htmlA data/artifacts/04692f72ef1d/gemma3-12b.pngA data/artifacts/04692f72ef1d/gpt.htmlA data/artifacts/04692f72ef1d/gpt.pngA data/artifacts/04692f72ef1d/grok.htmlA data/artifacts/04692f72ef1d/grok.pngA data/artifacts/04692f72ef1d/hermes3-8b.htmlA data/artifacts/04692f72ef1d/hermes3-8b.pngA data/artifacts/04692f72ef1d/hf-qwen-coder-32b.htmlA data/artifacts/04692f72ef1d/hf-qwen-coder-32b.pngA data/artifacts/04692f72ef1d/kimi.htmlA data/artifacts/04692f72ef1d/kimi.pngA data/artifacts/04692f72ef1d/qwen25-7b.htmlA data/artifacts/04692f72ef1d/qwen25-7b.pngA data/artifacts/04692f72ef1d/qwen3-14b.htmlA data/artifacts/04692f72ef1d/qwen3-14b.pngA data/artifacts/0cb3fc06b069/claude-code.htmlA data/artifacts/0cb3fc06b069/claude-code.pngA data/artifacts/0cb3fc06b069/gemma3-12b.htmlA data/artifacts/0cb3fc06b069/gemma3-12b.pngA data/artifacts/0cb3fc06b069/hermes3-8b.htmlA data/artifacts/0cb3fc06b069/hermes3-8b.pngA data/artifacts/0cb3fc06b069/hf-qwen-coder-32b.htmlA data/artifacts/0cb3fc06b069/hf-qwen-coder-32b.pngA data/artifacts/0cb3fc06b069/qwen25-7b.htmlA data/artifacts/0cb3fc06b069/qwen25-7b.pngA data/artifacts/0cb3fc06b069/qwen3-14b.htmlA data/artifacts/0cb3fc06b069/qwen3-14b.pngA data/artifacts/1d4944290382/claude-code.htmlA data/artifacts/1d4944290382/claude-code.pngA data/artifacts/1d4944290382/gemma3-12b.htmlA data/artifacts/1d4944290382/gemma3-12b.pngA data/artifacts/1d4944290382/hermes3-8b.htmlA data/artifacts/1d4944290382/hermes3-8b.pngA data/artifacts/1d4944290382/hf-qwen-coder-32b.htmlA data/artifacts/1d4944290382/hf-qwen-coder-32b.pngA data/artifacts/1d4944290382/qwen25-7b.htmlA data/artifacts/1d4944290382/qwen25-7b.pngA data/artifacts/1d4944290382/qwen3-14b.htmlA data/artifacts/1d4944290382/qwen3-14b.pngA data/artifacts/68b40458a368/claude-code.htmlA data/artifacts/68b40458a368/claude-code.pngA data/artifacts/68b40458a368/gemma3-12b.htmlA data/artifacts/68b40458a368/gemma3-12b.pngA data/artifacts/68b40458a368/qwen25-7b.htmlA data/artifacts/68b40458a368/qwen25-7b.pngA data/artifacts/68b40458a368/qwen3-14b.htmlA data/artifacts/68b40458a368/qwen3-14b.pngA data/artifacts/7bf57916e775/hf-qwen-coder-32b.htmlA data/artifacts/7bf57916e775/hf-qwen-coder-32b.pngA data/artifacts/7bf57916e775/kimi.htmlA data/artifacts/7bf57916e775/kimi.pngA data/artifacts/9cbc4e3c60a6/gemma3-12b.htmlA data/artifacts/9cbc4e3c60a6/gemma3-12b.pngA data/artifacts/9cbc4e3c60a6/gpt.htmlA data/artifacts/9cbc4e3c60a6/gpt.pngA data/artifacts/9cbc4e3c60a6/grok.htmlA data/artifacts/9cbc4e3c60a6/grok.pngA data/artifacts/9cbc4e3c60a6/hermes3-8b.htmlA data/artifacts/9cbc4e3c60a6/hermes3-8b.pngA data/artifacts/9cbc4e3c60a6/hf-qwen-coder-32b.htmlA data/artifacts/9cbc4e3c60a6/hf-qwen-coder-32b.pngA data/artifacts/9cbc4e3c60a6/kimi.htmlA data/artifacts/9cbc4e3c60a6/kimi.pngA data/artifacts/9cbc4e3c60a6/qwen25-7b.htmlA data/artifacts/9cbc4e3c60a6/qwen25-7b.pngA data/artifacts/9cbc4e3c60a6/qwen3-14b.htmlA data/artifacts/9cbc4e3c60a6/qwen3-14b.pngA data/artifacts/e840f1972643/claude-code.htmlA data/artifacts/e840f1972643/claude-code.pngA data/artifacts/e840f1972643/gemma3-12b.htmlA data/artifacts/e840f1972643/gemma3-12b.pngA data/artifacts/e840f1972643/gpt.htmlA data/artifacts/e840f1972643/gpt.pngA data/artifacts/e840f1972643/grok.htmlA data/artifacts/e840f1972643/grok.pngA data/artifacts/e840f1972643/hermes3-8b.htmlA data/artifacts/e840f1972643/hermes3-8b.pngA data/artifacts/e840f1972643/hf-qwen-coder-32b.htmlA data/artifacts/e840f1972643/hf-qwen-coder-32b.pngA data/artifacts/e840f1972643/kimi.htmlA data/artifacts/e840f1972643/kimi.pngA data/artifacts/e840f1972643/qwen25-7b.htmlA data/artifacts/e840f1972643/qwen25-7b.pngA data/artifacts/e840f1972643/qwen3-14b.htmlA data/artifacts/e840f1972643/qwen3-14b.pngA data/artifacts/ebf4f7984098/claude-code.htmlA data/artifacts/ebf4f7984098/claude-code.pngA data/artifacts/ebf4f7984098/gemma3-12b.htmlA data/artifacts/ebf4f7984098/gemma3-12b.pngA data/artifacts/ebf4f7984098/gpt.htmlA data/artifacts/ebf4f7984098/gpt.pngA data/artifacts/ebf4f7984098/grok.htmlA data/artifacts/ebf4f7984098/grok.pngA data/artifacts/ebf4f7984098/hermes3-8b.htmlA data/artifacts/ebf4f7984098/hermes3-8b.pngA data/artifacts/ebf4f7984098/hf-qwen-coder-32b.htmlA data/artifacts/ebf4f7984098/hf-qwen-coder-32b.pngA data/artifacts/ebf4f7984098/kimi.htmlA data/artifacts/ebf4f7984098/kimi.pngA data/artifacts/ebf4f7984098/qwen25-7b.htmlA data/artifacts/ebf4f7984098/qwen25-7b.pngA data/artifacts/ebf4f7984098/qwen3-14b.htmlA data/artifacts/ebf4f7984098/qwen3-14b.pngM data/challenges.jsonM data/costlog.jsonlM data/votes.jsonlA tetris.html
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 · 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 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 '&';
+ case '<': return '<';
+ case '>': return '>';
+ case '"': return '"';
+ case "'": return ''';
+ }
+ });
+ }
+
+ 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, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+ }
+
+ 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(/<(https?:\/\/[^&]+)>/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, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
+ }
+
+ 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('>')) {
+ const lines = block.split('\n');
+ const content = lines.map(function(line) {
+ return line.replace(/^>\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 • Scroll: zoom • 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 & 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 →