← back to Model Arena
auto-save: 2026-07-23T10:20:16 (23 files) — data/challenges.json data/costlog.jsonl data/votes.jsonl data/artifacts/0e969e848172/ data/artifacts/1fd68f0d37b7/claude-code.html
543fc511c5144a53626255836b7b8af3abc195ee · 2026-07-23 10:20:22 -0700 · Steve Abrams
Files touched
A data/artifacts/0e969e848172/claude-code.htmlA data/artifacts/0e969e848172/claude-code.pngA data/artifacts/0e969e848172/gemma3-12b.htmlA data/artifacts/0e969e848172/gemma3-12b.pngA data/artifacts/0e969e848172/gpt.htmlA data/artifacts/0e969e848172/gpt.pngA data/artifacts/0e969e848172/grok.htmlA data/artifacts/0e969e848172/grok.pngA data/artifacts/0e969e848172/hermes3-8b.htmlA data/artifacts/0e969e848172/hermes3-8b.pngA data/artifacts/0e969e848172/hf-qwen-coder-32b.htmlA data/artifacts/0e969e848172/hf-qwen-coder-32b.pngA data/artifacts/0e969e848172/kimi.htmlA data/artifacts/0e969e848172/kimi.pngA data/artifacts/0e969e848172/qwen25-7b.htmlA data/artifacts/0e969e848172/qwen25-7b.pngA data/artifacts/0e969e848172/qwen3-14b.htmlA data/artifacts/0e969e848172/qwen3-14b.pngA data/artifacts/1fd68f0d37b7/claude-code.htmlA data/artifacts/1fd68f0d37b7/claude-code.pngA data/artifacts/1fd68f0d37b7/gemma3-12b.htmlA data/artifacts/1fd68f0d37b7/gemma3-12b.pngA data/artifacts/1fd68f0d37b7/gpt.htmlA data/artifacts/1fd68f0d37b7/gpt.pngA data/artifacts/1fd68f0d37b7/grok.htmlA data/artifacts/1fd68f0d37b7/grok.pngA data/artifacts/1fd68f0d37b7/hermes3-8b.htmlA data/artifacts/1fd68f0d37b7/hermes3-8b.pngA data/artifacts/1fd68f0d37b7/hf-qwen-coder-32b.htmlA data/artifacts/1fd68f0d37b7/hf-qwen-coder-32b.pngA data/artifacts/1fd68f0d37b7/kimi.htmlA data/artifacts/1fd68f0d37b7/kimi.pngA data/artifacts/48026228790d/claude-code.htmlA data/artifacts/48026228790d/claude-code.pngA data/artifacts/48026228790d/gemma3-12b.htmlA data/artifacts/48026228790d/gpt.htmlA data/artifacts/48026228790d/gpt.pngA data/artifacts/48026228790d/grok.htmlA data/artifacts/48026228790d/grok.pngA data/artifacts/48026228790d/hermes3-8b.htmlA data/artifacts/48026228790d/hermes3-8b.pngA data/artifacts/48026228790d/qwen25-7b.htmlA data/artifacts/48026228790d/qwen25-7b.pngA data/artifacts/48026228790d/qwen3-14b.htmlA data/artifacts/48026228790d/qwen3-14b.pngA data/artifacts/6c13ff8455cc/claude-code.htmlA data/artifacts/6c13ff8455cc/claude-code.pngA data/artifacts/6c13ff8455cc/gemma3-12b.htmlA data/artifacts/6c13ff8455cc/gemma3-12b.pngA data/artifacts/6c13ff8455cc/gpt.htmlA data/artifacts/6c13ff8455cc/gpt.pngA data/artifacts/6c13ff8455cc/grok.htmlA data/artifacts/6c13ff8455cc/grok.pngA data/artifacts/6c13ff8455cc/hermes3-8b.htmlA data/artifacts/6c13ff8455cc/hermes3-8b.pngA data/artifacts/6c13ff8455cc/hf-qwen-coder-32b.htmlA data/artifacts/6c13ff8455cc/hf-qwen-coder-32b.pngA data/artifacts/6c13ff8455cc/kimi.htmlA data/artifacts/6c13ff8455cc/kimi.pngA data/artifacts/6c13ff8455cc/qwen25-7b.htmlA data/artifacts/6c13ff8455cc/qwen25-7b.pngA data/artifacts/6c13ff8455cc/qwen3-14b.htmlA data/artifacts/6c13ff8455cc/qwen3-14b.pngA data/artifacts/905cc3052af2/gemma3-12b.htmlA data/artifacts/905cc3052af2/gemma3-12b.pngA data/artifacts/905cc3052af2/gpt.htmlA data/artifacts/905cc3052af2/gpt.pngA data/artifacts/905cc3052af2/grok.htmlA data/artifacts/905cc3052af2/grok.pngA data/artifacts/905cc3052af2/hermes3-8b.htmlA data/artifacts/905cc3052af2/hermes3-8b.pngA data/artifacts/905cc3052af2/hf-qwen-coder-32b.htmlA data/artifacts/905cc3052af2/hf-qwen-coder-32b.pngA data/artifacts/905cc3052af2/kimi.htmlA data/artifacts/905cc3052af2/kimi.pngA data/artifacts/905cc3052af2/qwen25-7b.htmlA data/artifacts/905cc3052af2/qwen25-7b.pngA data/artifacts/905cc3052af2/qwen3-14b.htmlA data/artifacts/905cc3052af2/qwen3-14b.pngA data/artifacts/e3c185541c0d/claude-code.htmlA data/artifacts/e3c185541c0d/claude-code.pngA data/artifacts/e3c185541c0d/gemma3-12b.htmlA data/artifacts/e3c185541c0d/gemma3-12b.pngA data/artifacts/e3c185541c0d/gpt.htmlA data/artifacts/e3c185541c0d/gpt.pngA data/artifacts/e3c185541c0d/grok.htmlA data/artifacts/e3c185541c0d/grok.pngA data/artifacts/e3c185541c0d/hermes3-8b.htmlA data/artifacts/e3c185541c0d/hermes3-8b.pngA data/artifacts/e3c185541c0d/hf-qwen-coder-32b.htmlA data/artifacts/e3c185541c0d/hf-qwen-coder-32b.pngA data/artifacts/e3c185541c0d/kimi.htmlA data/artifacts/e3c185541c0d/kimi.pngA data/artifacts/e3c185541c0d/qwen25-7b.htmlA data/artifacts/e3c185541c0d/qwen25-7b.pngA data/artifacts/e3c185541c0d/qwen3-14b.htmlA data/artifacts/e3c185541c0d/qwen3-14b.pngA data/artifacts/f7fa0a2c495b/claude-code.htmlA data/artifacts/f7fa0a2c495b/claude-code.pngA data/artifacts/f7fa0a2c495b/gemma3-12b.htmlA data/artifacts/f7fa0a2c495b/gemma3-12b.pngA data/artifacts/f7fa0a2c495b/gpt.htmlA data/artifacts/f7fa0a2c495b/gpt.pngA data/artifacts/f7fa0a2c495b/grok.htmlA data/artifacts/f7fa0a2c495b/grok.pngA data/artifacts/f7fa0a2c495b/hermes3-8b.htmlA data/artifacts/f7fa0a2c495b/hermes3-8b.pngA data/artifacts/f7fa0a2c495b/hf-qwen-coder-32b.htmlA data/artifacts/f7fa0a2c495b/hf-qwen-coder-32b.pngA data/artifacts/f7fa0a2c495b/kimi.htmlA data/artifacts/f7fa0a2c495b/kimi.pngA data/artifacts/f7fa0a2c495b/qwen25-7b.htmlA data/artifacts/f7fa0a2c495b/qwen25-7b.pngA data/artifacts/f7fa0a2c495b/qwen3-14b.htmlA data/artifacts/f7fa0a2c495b/qwen3-14b.pngM data/challenges.jsonM data/costlog.jsonlM data/votes.jsonl
Diff
commit 543fc511c5144a53626255836b7b8af3abc195ee
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 23 10:20:22 2026 -0700
auto-save: 2026-07-23T10:20:16 (23 files) — data/challenges.json data/costlog.jsonl data/votes.jsonl data/artifacts/0e969e848172/ data/artifacts/1fd68f0d37b7/claude-code.html
---
data/artifacts/0e969e848172/claude-code.html | 440 +++++++
data/artifacts/0e969e848172/claude-code.png | Bin 0 -> 35896 bytes
data/artifacts/0e969e848172/gemma3-12b.html | 137 ++
data/artifacts/0e969e848172/gemma3-12b.png | Bin 0 -> 9706 bytes
data/artifacts/0e969e848172/gpt.html | 375 ++++++
data/artifacts/0e969e848172/gpt.png | Bin 0 -> 16296 bytes
data/artifacts/0e969e848172/grok.html | 339 +++++
data/artifacts/0e969e848172/grok.png | Bin 0 -> 41951 bytes
data/artifacts/0e969e848172/hermes3-8b.html | 138 +++
data/artifacts/0e969e848172/hermes3-8b.png | Bin 0 -> 4658 bytes
data/artifacts/0e969e848172/hf-qwen-coder-32b.html | 101 ++
data/artifacts/0e969e848172/hf-qwen-coder-32b.png | Bin 0 -> 4345 bytes
data/artifacts/0e969e848172/kimi.html | 261 ++++
data/artifacts/0e969e848172/kimi.png | Bin 0 -> 21120 bytes
data/artifacts/0e969e848172/qwen25-7b.html | 101 ++
data/artifacts/0e969e848172/qwen25-7b.png | Bin 0 -> 4988 bytes
data/artifacts/0e969e848172/qwen3-14b.html | 142 +++
data/artifacts/0e969e848172/qwen3-14b.png | Bin 0 -> 5058 bytes
data/artifacts/1fd68f0d37b7/claude-code.html | 330 +++++
data/artifacts/1fd68f0d37b7/claude-code.png | Bin 0 -> 29523 bytes
data/artifacts/1fd68f0d37b7/gemma3-12b.html | 130 ++
data/artifacts/1fd68f0d37b7/gemma3-12b.png | Bin 0 -> 4225 bytes
data/artifacts/1fd68f0d37b7/gpt.html | 541 ++++++++
data/artifacts/1fd68f0d37b7/gpt.png | Bin 0 -> 173916 bytes
data/artifacts/1fd68f0d37b7/grok.html | 521 ++++++++
data/artifacts/1fd68f0d37b7/grok.png | Bin 0 -> 28306 bytes
data/artifacts/1fd68f0d37b7/hermes3-8b.html | 112 ++
data/artifacts/1fd68f0d37b7/hermes3-8b.png | Bin 0 -> 4890 bytes
data/artifacts/1fd68f0d37b7/hf-qwen-coder-32b.html | 88 ++
data/artifacts/1fd68f0d37b7/hf-qwen-coder-32b.png | Bin 0 -> 4832 bytes
data/artifacts/1fd68f0d37b7/kimi.html | 314 +++++
data/artifacts/1fd68f0d37b7/kimi.png | Bin 0 -> 13656 bytes
data/artifacts/48026228790d/claude-code.html | 318 +++++
data/artifacts/48026228790d/claude-code.png | Bin 0 -> 336955 bytes
data/artifacts/48026228790d/gemma3-12b.html | 156 +++
data/artifacts/48026228790d/gpt.html | 314 +++++
data/artifacts/48026228790d/gpt.png | Bin 0 -> 272102 bytes
data/artifacts/48026228790d/grok.html | 554 +++++++++
data/artifacts/48026228790d/grok.png | Bin 0 -> 289283 bytes
data/artifacts/48026228790d/hermes3-8b.html | 309 +++++
data/artifacts/48026228790d/hermes3-8b.png | Bin 0 -> 6319 bytes
data/artifacts/48026228790d/qwen25-7b.html | 83 ++
data/artifacts/48026228790d/qwen25-7b.png | Bin 0 -> 2889 bytes
data/artifacts/48026228790d/qwen3-14b.html | 142 +++
data/artifacts/48026228790d/qwen3-14b.png | Bin 0 -> 5053 bytes
data/artifacts/6c13ff8455cc/claude-code.html | 388 ++++++
data/artifacts/6c13ff8455cc/claude-code.png | Bin 0 -> 26138 bytes
data/artifacts/6c13ff8455cc/gemma3-12b.html | 324 +++++
data/artifacts/6c13ff8455cc/gemma3-12b.png | Bin 0 -> 9003 bytes
data/artifacts/6c13ff8455cc/gpt.html | 602 +++++++++
data/artifacts/6c13ff8455cc/gpt.png | Bin 0 -> 48393 bytes
data/artifacts/6c13ff8455cc/grok.html | 596 +++++++++
data/artifacts/6c13ff8455cc/grok.png | Bin 0 -> 25691 bytes
data/artifacts/6c13ff8455cc/hermes3-8b.html | 211 ++++
data/artifacts/6c13ff8455cc/hermes3-8b.png | Bin 0 -> 6758 bytes
data/artifacts/6c13ff8455cc/hf-qwen-coder-32b.html | 183 +++
data/artifacts/6c13ff8455cc/hf-qwen-coder-32b.png | Bin 0 -> 7321 bytes
data/artifacts/6c13ff8455cc/kimi.html | 515 ++++++++
data/artifacts/6c13ff8455cc/kimi.png | Bin 0 -> 19297 bytes
data/artifacts/6c13ff8455cc/qwen25-7b.html | 146 +++
data/artifacts/6c13ff8455cc/qwen25-7b.png | Bin 0 -> 6720 bytes
data/artifacts/6c13ff8455cc/qwen3-14b.html | 263 ++++
data/artifacts/6c13ff8455cc/qwen3-14b.png | Bin 0 -> 6998 bytes
data/artifacts/905cc3052af2/gemma3-12b.html | 197 +++
data/artifacts/905cc3052af2/gemma3-12b.png | Bin 0 -> 5594 bytes
data/artifacts/905cc3052af2/gpt.html | 397 ++++++
data/artifacts/905cc3052af2/gpt.png | Bin 0 -> 10495 bytes
data/artifacts/905cc3052af2/grok.html | 840 +++++++++++++
data/artifacts/905cc3052af2/grok.png | Bin 0 -> 184835 bytes
data/artifacts/905cc3052af2/hermes3-8b.html | 165 +++
data/artifacts/905cc3052af2/hermes3-8b.png | Bin 0 -> 11517 bytes
data/artifacts/905cc3052af2/hf-qwen-coder-32b.html | 149 +++
data/artifacts/905cc3052af2/hf-qwen-coder-32b.png | Bin 0 -> 4083 bytes
data/artifacts/905cc3052af2/kimi.html | 347 ++++++
data/artifacts/905cc3052af2/kimi.png | Bin 0 -> 29702 bytes
data/artifacts/905cc3052af2/qwen25-7b.html | 107 ++
data/artifacts/905cc3052af2/qwen25-7b.png | Bin 0 -> 5856 bytes
data/artifacts/905cc3052af2/qwen3-14b.html | 362 ++++++
data/artifacts/905cc3052af2/qwen3-14b.png | Bin 0 -> 6378 bytes
data/artifacts/e3c185541c0d/claude-code.html | 210 ++++
data/artifacts/e3c185541c0d/claude-code.png | Bin 0 -> 212107 bytes
data/artifacts/e3c185541c0d/gemma3-12b.html | 132 ++
data/artifacts/e3c185541c0d/gemma3-12b.png | Bin 0 -> 38442 bytes
data/artifacts/e3c185541c0d/gpt.html | 184 +++
data/artifacts/e3c185541c0d/gpt.png | Bin 0 -> 108398 bytes
data/artifacts/e3c185541c0d/grok.html | 121 ++
data/artifacts/e3c185541c0d/grok.png | Bin 0 -> 296665 bytes
data/artifacts/e3c185541c0d/hermes3-8b.html | 100 ++
data/artifacts/e3c185541c0d/hermes3-8b.png | Bin 0 -> 2724 bytes
data/artifacts/e3c185541c0d/hf-qwen-coder-32b.html | 75 ++
data/artifacts/e3c185541c0d/hf-qwen-coder-32b.png | Bin 0 -> 114836 bytes
data/artifacts/e3c185541c0d/kimi.html | 184 +++
data/artifacts/e3c185541c0d/kimi.png | Bin 0 -> 509656 bytes
data/artifacts/e3c185541c0d/qwen25-7b.html | 92 ++
data/artifacts/e3c185541c0d/qwen25-7b.png | Bin 0 -> 2731 bytes
data/artifacts/e3c185541c0d/qwen3-14b.html | 99 ++
data/artifacts/e3c185541c0d/qwen3-14b.png | Bin 0 -> 49890 bytes
data/artifacts/f7fa0a2c495b/claude-code.html | 303 +++++
data/artifacts/f7fa0a2c495b/claude-code.png | Bin 0 -> 21348 bytes
data/artifacts/f7fa0a2c495b/gemma3-12b.html | 109 ++
data/artifacts/f7fa0a2c495b/gemma3-12b.png | Bin 0 -> 6120 bytes
data/artifacts/f7fa0a2c495b/gpt.html | 226 ++++
data/artifacts/f7fa0a2c495b/gpt.png | Bin 0 -> 7184 bytes
data/artifacts/f7fa0a2c495b/grok.html | 314 +++++
data/artifacts/f7fa0a2c495b/grok.png | Bin 0 -> 22679 bytes
data/artifacts/f7fa0a2c495b/hermes3-8b.html | 76 ++
data/artifacts/f7fa0a2c495b/hermes3-8b.png | Bin 0 -> 4461 bytes
data/artifacts/f7fa0a2c495b/hf-qwen-coder-32b.html | 544 ++++++++
data/artifacts/f7fa0a2c495b/hf-qwen-coder-32b.png | Bin 0 -> 4277 bytes
data/artifacts/f7fa0a2c495b/kimi.html | 276 +++++
data/artifacts/f7fa0a2c495b/kimi.png | Bin 0 -> 9493 bytes
data/artifacts/f7fa0a2c495b/qwen25-7b.html | 80 ++
data/artifacts/f7fa0a2c495b/qwen25-7b.png | Bin 0 -> 14013 bytes
data/artifacts/f7fa0a2c495b/qwen3-14b.html | 201 +++
data/artifacts/f7fa0a2c495b/qwen3-14b.png | Bin 0 -> 7491 bytes
data/challenges.json | 1308 +++++++++++++++++++-
data/costlog.jsonl | 20 +
data/votes.jsonl | 11 +
118 files changed, 16342 insertions(+), 61 deletions(-)
diff --git a/data/artifacts/0e969e848172/claude-code.html b/data/artifacts/0e969e848172/claude-code.html
new file mode 100644
index 0000000..15df4d3
--- /dev/null
+++ b/data/artifacts/0e969e848172/claude-code.html
@@ -0,0 +1,440 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Gravity Sandbox</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ overflow: hidden;
+ background: #05060a;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ user-select: none;
+ -webkit-user-select: none;
+ }
+ #space {
+ display: block;
+ position: absolute;
+ inset: 0;
+ cursor: crosshair;
+ }
+ #hud {
+ position: absolute;
+ top: 12px;
+ left: 12px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ align-items: center;
+ background: rgba(10, 14, 24, 0.72);
+ border: 1px solid rgba(120, 160, 255, 0.25);
+ border-radius: 10px;
+ padding: 10px 12px;
+ color: #cfe0ff;
+ backdrop-filter: blur(6px);
+ z-index: 10;
+ max-width: calc(100vw - 40px);
+ }
+ #hud .title {
+ font-weight: 700;
+ letter-spacing: 0.5px;
+ font-size: 13px;
+ color: #ffffff;
+ margin-right: 4px;
+ }
+ button {
+ background: #16223c;
+ color: #cfe0ff;
+ border: 1px solid rgba(120, 160, 255, 0.35);
+ border-radius: 7px;
+ padding: 6px 12px;
+ font-size: 12px;
+ cursor: pointer;
+ transition: background 0.15s, transform 0.05s;
+ }
+ button:hover { background: #1f3157; }
+ button:active { transform: scale(0.96); }
+ button.active {
+ background: #2f5fd0;
+ color: #fff;
+ border-color: #6f9bff;
+ }
+ label.ctl {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ color: #9fb6e6;
+ }
+ input[type=range] {
+ width: 90px;
+ accent-color: #5b8cff;
+ }
+ #hint {
+ position: absolute;
+ bottom: 12px;
+ left: 50%;
+ transform: translateX(-50%);
+ color: rgba(180, 200, 240, 0.55);
+ font-size: 12px;
+ background: rgba(10, 14, 24, 0.55);
+ padding: 6px 14px;
+ border-radius: 999px;
+ pointer-events: none;
+ z-index: 10;
+ white-space: nowrap;
+ }
+ #stats {
+ font-size: 11px;
+ color: #7e94c4;
+ margin-left: 4px;
+ }
+</style>
+</head>
+<body>
+<canvas id="space"></canvas>
+
+<div id="hud">
+ <span class="title">GRAVITY SANDBOX</span>
+ <label class="ctl">Mass
+ <input type="range" id="mass" min="1" max="100" value="25">
+ <span id="massVal">25</span>
+ </label>
+ <button id="trailBtn" class="active" title="Toggle motion trails">Trails: ON</button>
+ <button id="pauseBtn" title="Pause / resume simulation">Pause</button>
+ <button id="clearBtn" title="Remove all bodies">Clear</button>
+ <span id="stats">0 bodies</span>
+</div>
+
+<div id="hint">Click to drop a body · Click & drag to fling it · Big mass = a star that anchors orbits</div>
+
+<script>
+(function () {
+ "use strict";
+
+ var canvas = document.getElementById("space");
+ var ctx = canvas.getContext("2d");
+ var DPR = Math.min(window.devicePixelRatio || 1, 2);
+ var W = 0, H = 0;
+
+ function resize() {
+ W = window.innerWidth;
+ H = window.innerHeight;
+ canvas.width = Math.floor(W * DPR);
+ canvas.height = Math.floor(H * DPR);
+ canvas.style.width = W + "px";
+ canvas.style.height = H + "px";
+ ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
+ paintBackground(1); // full repaint after resize clears the canvas
+ }
+
+ // ---------- simulation state ----------
+ var G = 900; // gravitational constant (tuned for pixels)
+ var SOFTEN = 12; // softening length to avoid singular slingshots
+ var bodies = [];
+ var trailsOn = true;
+ var paused = false;
+ var nextHue = 0;
+
+ function radiusFor(mass) {
+ return 2.5 + 2.2 * Math.cbrt(mass);
+ }
+
+ function makeBody(x, y, vx, vy, mass) {
+ nextHue = (nextHue + 47) % 360;
+ return {
+ x: x, y: y,
+ vx: vx, vy: vy,
+ ax: 0, ay: 0,
+ mass: mass,
+ r: radiusFor(mass),
+ hue: nextHue,
+ big: mass >= 60
+ };
+ }
+
+ // ---------- physics: velocity Verlet N-body ----------
+ function computeAccel() {
+ var i, j, n = bodies.length;
+ for (i = 0; i < n; i++) { bodies[i].ax = 0; bodies[i].ay = 0; }
+ for (i = 0; i < n; i++) {
+ var a = bodies[i];
+ for (j = i + 1; j < n; j++) {
+ var b = bodies[j];
+ var dx = b.x - a.x;
+ var dy = b.y - a.y;
+ var d2 = dx * dx + dy * dy + SOFTEN * SOFTEN;
+ var invD = 1 / Math.sqrt(d2);
+ var f = G * invD / d2; // G / d^2 * (unit vector handled below)
+ a.ax += f * b.mass * dx;
+ a.ay += f * b.mass * dy;
+ b.ax -= f * a.mass * dx;
+ b.ay -= f * a.mass * dy;
+ }
+ }
+ }
+
+ function mergeCollisions() {
+ for (var i = 0; i < bodies.length; i++) {
+ for (var j = i + 1; j < bodies.length; j++) {
+ var a = bodies[i], b = bodies[j];
+ var dx = b.x - a.x, dy = b.y - a.y;
+ var rr = (a.r + b.r) * 0.6;
+ if (dx * dx + dy * dy < rr * rr) {
+ // inelastic merge, conserve momentum
+ var m = a.mass + b.mass;
+ a.x = (a.x * a.mass + b.x * b.mass) / m;
+ a.y = (a.y * a.mass + b.y * b.mass) / m;
+ a.vx = (a.vx * a.mass + b.vx * b.mass) / m;
+ a.vy = (a.vy * a.mass + b.vy * b.mass) / m;
+ if (b.mass > a.mass) { a.hue = b.hue; }
+ a.mass = m;
+ a.r = radiusFor(m);
+ a.big = m >= 60;
+ bodies.splice(j, 1);
+ j--;
+ updateStats();
+ }
+ }
+ }
+ }
+
+ function step(dt) {
+ var i, n = bodies.length;
+ for (i = 0; i < n; i++) {
+ var b = bodies[i];
+ b.vx += b.ax * dt * 0.5;
+ b.vy += b.ay * dt * 0.5;
+ b.x += b.vx * dt;
+ b.y += b.vy * dt;
+ }
+ computeAccel();
+ for (i = 0; i < n; i++) {
+ var c = bodies[i];
+ c.vx += c.ax * dt * 0.5;
+ c.vy += c.ay * dt * 0.5;
+ }
+ mergeCollisions();
+ // cull bodies flung far off-screen
+ var margin = 3000;
+ for (i = bodies.length - 1; i >= 0; i--) {
+ var d = bodies[i];
+ if (d.x < -margin || d.x > W + margin || d.y < -margin || d.y > H + margin) {
+ bodies.splice(i, 1);
+ updateStats();
+ }
+ }
+ }
+
+ // ---------- starfield background ----------
+ var stars = [];
+ function seedStars() {
+ stars = [];
+ var count = Math.floor((W * H) / 6000);
+ for (var i = 0; i < count; i++) {
+ stars.push({
+ x: Math.random() * W,
+ y: Math.random() * H,
+ s: Math.random() * 1.3 + 0.3,
+ a: Math.random() * 0.5 + 0.15
+ });
+ }
+ }
+
+ function paintBackground(alpha) {
+ // trails work by painting a translucent dark layer each frame;
+ // alpha=1 gives a hard clear
+ ctx.fillStyle = "rgba(5, 6, 10," + alpha + ")";
+ ctx.fillRect(0, 0, W, H);
+ if (alpha === 1) {
+ for (var i = 0; i < stars.length; i++) {
+ var s = stars[i];
+ ctx.fillStyle = "rgba(200,215,255," + s.a + ")";
+ ctx.fillRect(s.x, s.y, s.s, s.s);
+ }
+ }
+ }
+
+ // ---------- rendering ----------
+ function draw() {
+ if (trailsOn) {
+ paintBackground(0.08); // fade — old positions linger as colored trails
+ } else {
+ paintBackground(1);
+ }
+
+ for (var i = 0; i < bodies.length; i++) {
+ var b = bodies[i];
+ var col = "hsl(" + b.hue + ", 90%, 65%)";
+
+ if (b.big) {
+ // star-like glow for heavy bodies
+ var g = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, b.r * 4);
+ g.addColorStop(0, "hsla(" + b.hue + ", 95%, 75%, 0.55)");
+ g.addColorStop(1, "hsla(" + b.hue + ", 95%, 75%, 0)");
+ ctx.fillStyle = g;
+ ctx.beginPath();
+ ctx.arc(b.x, b.y, b.r * 4, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ ctx.beginPath();
+ ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
+ ctx.fillStyle = col;
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(b.x - b.r * 0.3, b.y - b.r * 0.3, b.r * 0.45, 0, Math.PI * 2);
+ ctx.fillStyle = "rgba(255,255,255,0.5)";
+ ctx.fill();
+ }
+
+ // drag preview: launch vector
+ if (dragging && dragStart) {
+ ctx.beginPath();
+ ctx.moveTo(dragStart.x, dragStart.y);
+ ctx.lineTo(dragNow.x, dragNow.y);
+ ctx.strokeStyle = "rgba(140,180,255,0.8)";
+ ctx.lineWidth = 1.5;
+ ctx.setLineDash([5, 5]);
+ ctx.stroke();
+ ctx.setLineDash([]);
+ var pm = +massSlider.value;
+ ctx.beginPath();
+ ctx.arc(dragStart.x, dragStart.y, radiusFor(pm), 0, Math.PI * 2);
+ ctx.strokeStyle = "rgba(200,220,255,0.9)";
+ ctx.stroke();
+ }
+ }
+
+ // ---------- main loop ----------
+ var last = performance.now();
+ function frame(now) {
+ var dt = Math.min((now - last) / 1000, 0.033); // clamp big tab-switch gaps
+ last = now;
+ if (!paused) {
+ // substep for stability at close encounters
+ var sub = 4;
+ for (var s = 0; s < sub; s++) step(dt / sub);
+ }
+ draw();
+ requestAnimationFrame(frame);
+ }
+
+ // ---------- input ----------
+ var massSlider = document.getElementById("mass");
+ var massVal = document.getElementById("massVal");
+ var dragging = false;
+ var dragStart = null;
+ var dragNow = { x: 0, y: 0 };
+
+ massSlider.addEventListener("input", function () {
+ massVal.textContent = massSlider.value;
+ });
+
+ function pos(e) {
+ var r = canvas.getBoundingClientRect();
+ var t = e.touches ? e.touches[0] : e;
+ return { x: t.clientX - r.left, y: t.clientY - r.top };
+ }
+
+ function down(e) {
+ if (e.target !== canvas) return;
+ dragging = true;
+ dragStart = pos(e);
+ dragNow = { x: dragStart.x, y: dragStart.y };
+ e.preventDefault();
+ }
+ function move(e) {
+ if (!dragging) return;
+ dragNow = pos(e.changedTouches ? { touches: e.changedTouches } : e);
+ }
+ function up(e) {
+ if (!dragging) return;
+ dragging = false;
+ var end = e.changedTouches ? { x: e.changedTouches[0].clientX - canvas.getBoundingClientRect().left,
+ y: e.changedTouches[0].clientY - canvas.getBoundingClientRect().top }
+ : pos(e);
+ var vx = (end.x - dragStart.x) * 1.6;
+ var vy = (end.y - dragStart.y) * 1.6;
+ bodies.push(makeBody(dragStart.x, dragStart.y, vx, vy, +massSlider.value));
+ dragStart = null;
+ updateStats();
+ }
+
+ canvas.addEventListener("mousedown", down);
+ window.addEventListener("mousemove", move);
+ window.addEventListener("mouseup", up);
+ canvas.addEventListener("touchstart", down, { passive: false });
+ window.addEventListener("touchmove", move, { passive: true });
+ window.addEventListener("touchend", up);
+
+ // ---------- HUD buttons ----------
+ var trailBtn = document.getElementById("trailBtn");
+ var pauseBtn = document.getElementById("pauseBtn");
+ var clearBtn = document.getElementById("clearBtn");
+ var stats = document.getElementById("stats");
+
+ trailBtn.addEventListener("click", function () {
+ trailsOn = !trailsOn;
+ trailBtn.textContent = "Trails: " + (trailsOn ? "ON" : "OFF");
+ trailBtn.classList.toggle("active", trailsOn);
+ if (!trailsOn) paintBackground(1);
+ });
+
+ pauseBtn.addEventListener("click", function () {
+ paused = !paused;
+ pauseBtn.textContent = paused ? "Resume" : "Pause";
+ pauseBtn.classList.toggle("active", paused);
+ });
+
+ clearBtn.addEventListener("click", function () {
+ bodies = [];
+ paintBackground(1);
+ updateStats();
+ });
+
+ function updateStats() {
+ stats.textContent = bodies.length + (bodies.length === 1 ? " body" : " bodies");
+ }
+
+ // ---------- boot: seed a small demo system ----------
+ function seedDemo() {
+ var cx = W / 2, cy = H / 2;
+ var star = makeBody(cx, cy, 0, 0, 90);
+ star.hue = 48; // warm star
+ bodies.push(star);
+ var dists = [110, 170, 240];
+ for (var i = 0; i < dists.length; i++) {
+ var d = dists[i];
+ var ang = Math.random() * Math.PI * 2;
+ var v = Math.sqrt(G * star.mass / d); // circular orbital speed
+ bodies.push(makeBody(
+ cx + Math.cos(ang) * d,
+ cy + Math.sin(ang) * d,
+ -Math.sin(ang) * v,
+ Math.cos(ang) * v,
+ 3 + i * 4
+ ));
+ }
+ computeAccel();
+ updateStats();
+ }
+
+ window.addEventListener("resize", function () {
+ resize();
+ seedStars();
+ paintBackground(1);
+ });
+
+ resize();
+ seedStars();
+ paintBackground(1);
+ seedDemo();
+ requestAnimationFrame(frame);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0e969e848172/claude-code.png b/data/artifacts/0e969e848172/claude-code.png
new file mode 100644
index 0000000..62c491c
Binary files /dev/null and b/data/artifacts/0e969e848172/claude-code.png differ
diff --git a/data/artifacts/0e969e848172/gemma3-12b.html b/data/artifacts/0e969e848172/gemma3-12b.html
new file mode 100644
index 0000000..fa6eb52
--- /dev/null
+++ b/data/artifacts/0e969e848172/gemma3-12b.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Gravity Sandbox</title>
+<style>
+body { margin: 0; overflow: hidden; background-color: #111; }
+canvas { display: block; }
+#controls { position: absolute; top: 10px; left: 10px; font-family: sans-serif; color: white; }
+</style>
+</head>
+<body>
+
+<div id="controls">
+ Click to place planets. Clear button below. Trail toggle on/off.
+ <button id="clearButton">Clear</button>
+ <label for="trailToggle">Trails:</label>
+ <input type="checkbox" id="trailToggle" checked>
+</div>
+
+<canvas id="sandboxCanvas"></canvas>
+
+<script>
+const canvas = document.getElementById('sandboxCanvas');
+const ctx = canvas.getContext('2d');
+const clearButton = document.getElementById('clearButton');
+const trailToggle = document.getElementById('trailToggle');
+
+let bodies = [];
+let trails = [];
+
+const gravityConstant = 6.674e-11; // Reduced for performance
+const pixelRatio = window.devicePixelRatio || 1;
+canvas.width = canvas.getContext('2d').canvas.clientWidth * pixelRatio;
+canvas.height = canvas.getContext('2d').canvas.clientHeight * pixelRatio;
+ctx.scale(pixelRatio, pixelRatio);
+
+function Body(x, y, mass, color) {
+ this.x = x;
+ this.y = y;
+ this.mass = mass;
+ this.color = color || '#fff';
+ this.vx = 0;
+ this.vy = 0;
+ this.trail = [];
+}
+
+function distance(x1, y1, x2, y2) {
+ const dx = x2 - x1;
+ const dy = y2 - y1;
+ return Math.sqrt(dx * dx + dy * dy);
+}
+
+function calculateAttraction(body1, body2) {
+ const dist = distance(body1.x, body1.y, body2.x, body2.y);
+ const force = gravityConstant * (body1.mass * body2.mass) / (dist * dist);
+
+ const dx = (body2.x - body1.x) / dist;
+ const dy = (body2.y - body1.y) / dist;
+
+ body1.vx += force * dx;
+ body1.vy += force * dy;
+ body2.vx -= force * dx;
+ body2.vy -= force * dy;
+}
+
+function update() {
+ for (let i = 0; i < bodies.length; i++) {
+ for (let j = i + 1; j < bodies.length; j++) {
+ calculateAttraction(bodies[i], bodies[j]);
+ }
+
+ // Apply velocity and update position
+ bodies[i].vx *= 0.98; // Damping to prevent runaway speed
+ bodies[i].vy *= 0.98;
+ bodies[i].x += bodies[i].vx;
+ bodies[i].y += bodies[i].vy;
+
+ // Trail recording
+ if (trailToggle.checked) {
+ bodies[i].trail.push({ x: bodies[i].x, y: bodies[i].y });
+ if(bodies[i].trail.length > 100){
+ bodies[i].trail.shift();
+ }
+ }
+ }
+}
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ for (let i = 0; i < bodies.length; i++) {
+ ctx.beginPath();
+ ctx.arc(bodies[i].x, bodies[i].y, Math.max(5, bodies[i].mass / 10), 0, 2 * Math.PI); // Size proportional to mass
+ ctx.fillStyle = bodies[i].color;
+ ctx.fill();
+
+ if (trailToggle.checked && bodies[i].trail.length > 0) {
+ ctx.beginPath();
+ ctx.moveTo(bodies[i].trail[0].x, bodies[i].trail[0].y);
+ for (let j = 1; j < bodies[i].trail.length; j++) {
+ ctx.lineTo(bodies[i].trail[j].x, bodies[i].trail[j].y);
+ }
+ ctx.strokeStyle = bodies[i].color;
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ }
+ }
+}
+
+canvas.addEventListener('click', (event) => {
+ const x = event.clientX * pixelRatio;
+ const y = event.clientY * pixelRatio;
+ const mass = Math.random() * 10 + 1; // Random mass between 1 and 10
+ const color = '#' + Math.floor(Math.random()*16777215).toString(16);
+ bodies.push(new Body(x, y, mass, color));
+});
+
+clearButton.addEventListener('click', () => {
+ bodies = [];
+ trails = [];
+});
+
+trailToggle.addEventListener('change', () =>{
+ //No action needed here as trails are already being added or not based on checkbox state
+})
+
+function animate() {
+ update();
+ draw();
+ requestAnimationFrame(animate);
+}
+
+animate();
+</script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0e969e848172/gemma3-12b.png b/data/artifacts/0e969e848172/gemma3-12b.png
new file mode 100644
index 0000000..2a22e4c
Binary files /dev/null and b/data/artifacts/0e969e848172/gemma3-12b.png differ
diff --git a/data/artifacts/0e969e848172/gpt.html b/data/artifacts/0e969e848172/gpt.html
new file mode 100644
index 0000000..1a5bf5a
--- /dev/null
+++ b/data/artifacts/0e969e848172/gpt.html
@@ -0,0 +1,375 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>2D Gravity Sandbox</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ background: #050711;
+ color: #ddd;
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ }
+ #ui {
+ position: fixed;
+ top: 8px;
+ left: 8px;
+ right: 8px;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+ z-index: 10;
+ pointer-events: none;
+ }
+ #ui > * {
+ pointer-events: auto;
+ }
+ button, input, label {
+ font-size: 12px;
+ }
+ button {
+ background: #222;
+ color: #eee;
+ border: 1px solid #555;
+ padding: 4px 8px;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ button:hover {
+ background: #333;
+ }
+ button:active {
+ background: #444;
+ }
+ #massLabel, #velLabel {
+ margin-right: 4px;
+ }
+ #canvas {
+ display: block;
+ }
+ .panel {
+ background: rgba(0,0,0,0.5);
+ border-radius: 6px;
+ padding: 4px 6px;
+ border: 1px solid rgba(255,255,255,0.08);
+ }
+</style>
+</head>
+<body>
+<div id="ui">
+ <div class="panel">
+ <button id="clearBtn">Clear</button>
+ <button id="pauseBtn">Pause</button>
+ <label style="margin-left:6px;">
+ <input type="checkbox" id="trailToggle" checked>
+ Trails
+ </label>
+ <label style="margin-left:6px;">
+ <input type="checkbox" id="showVectors">
+ Vel. vectors
+ </label>
+ </div>
+ <div class="panel">
+ <span id="massLabel">Mass:</span>
+ <input id="massSlider" type="range" min="1" max="200" value="40">
+ <span id="massValue">40</span>
+ </div>
+ <div class="panel">
+ <span id="velLabel">Initial speed:</span>
+ <input id="velSlider" type="range" min="0" max="400" value="120">
+ <span id="velValue">120</span>
+ </div>
+ <div class="panel" style="font-size:11px;">
+ Click & drag to set direction and speed. Release to spawn a body.
+ </div>
+</div>
+<canvas id="canvas"></canvas>
+<script>
+(function() {
+ const canvas = document.getElementById('canvas');
+ const ctx = canvas.getContext('2d');
+
+ const clearBtn = document.getElementById('clearBtn');
+ const pauseBtn = document.getElementById('pauseBtn');
+ const trailToggle = document.getElementById('trailToggle');
+ const showVectors = document.getElementById('showVectors');
+ const massSlider = document.getElementById('massSlider');
+ const massValue = document.getElementById('massValue');
+ const velSlider = document.getElementById('velSlider');
+ const velValue = document.getElementById('velValue');
+
+ let width = window.innerWidth;
+ let height = window.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+
+ window.addEventListener('resize', () => {
+ width = window.innerWidth;
+ height = window.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+ // Do not clear bodies; just redraw
+ });
+
+ massSlider.addEventListener('input', () => {
+ massValue.textContent = massSlider.value;
+ });
+ velSlider.addEventListener('input', () => {
+ velValue.textContent = velSlider.value;
+ });
+
+ let bodies = [];
+ let lastTime = performance.now();
+ let paused = false;
+
+ // Physics constants
+ const G = 80; // gravitational constant (tuned for visual behavior)
+ const softening = 4; // softening length to avoid huge forces
+ const maxTrailLength = 200;
+ const maxBodies = 200; // simple cap to avoid runaway performance
+
+ function randomColor() {
+ const h = Math.floor(Math.random() * 360);
+ const s = 60 + Math.random() * 30;
+ const l = 50 + Math.random() * 10;
+ return `hsl(${h},${s}%,${l}%)`;
+ }
+
+ function addBody(x, y, vx, vy, mass) {
+ if (bodies.length >= maxBodies) bodies.shift();
+ const radius = Math.sqrt(mass) * 0.6 + 1.5;
+ bodies.push({
+ x, y, vx, vy, mass,
+ radius,
+ color: randomColor(),
+ trail: []
+ });
+ }
+
+ clearBtn.onclick = () => {
+ bodies = [];
+ clearCanvas(true);
+ };
+
+ pauseBtn.onclick = () => {
+ paused = !paused;
+ pauseBtn.textContent = paused ? "Resume" : "Pause";
+ };
+
+ function clearCanvas(full) {
+ if (trailToggle.checked && !full) {
+ ctx.fillStyle = "rgba(5,7,17,0.28)";
+ ctx.fillRect(0, 0, width, height);
+ } else {
+ ctx.fillStyle = "#050711";
+ ctx.fillRect(0, 0, width, height);
+ }
+ }
+
+ function update(dt) {
+ // semi-implicit Euler
+ const n = bodies.length;
+ for (let i = 0; i < n; i++) {
+ let ax = 0, ay = 0;
+ const bi = bodies[i];
+ for (let j = 0; j < n; j++) {
+ if (i === j) continue;
+ const bj = bodies[j];
+ let dx = bj.x - bi.x;
+ let dy = bj.y - bi.y;
+ const distSq = dx*dx + dy*dy + softening*softening;
+ const invDist = 1 / Math.sqrt(distSq);
+ const invDist3 = invDist * invDist * invDist;
+ const f = G * bj.mass * invDist3;
+ ax += dx * f;
+ ay += dy * f;
+ }
+ bi.vx += ax * dt;
+ bi.vy += ay * dt;
+ }
+
+ for (let i = 0; i < n; i++) {
+ const b = bodies[i];
+ b.x += b.vx * dt;
+ b.y += b.vy * dt;
+ if (trailToggle.checked) {
+ b.trail.push({x: b.x, y: b.y});
+ if (b.trail.length > maxTrailLength) b.trail.shift();
+ } else {
+ b.trail.length = 0;
+ }
+ }
+ }
+
+ function draw() {
+ clearCanvas(false);
+
+ // trails
+ for (const b of bodies) {
+ if (!trailToggle.checked || b.trail.length < 2) continue;
+ const len = b.trail.length;
+ ctx.lineWidth = 1;
+ for (let i = 1; i < len; i++) {
+ const p0 = b.trail[i - 1];
+ const p1 = b.trail[i];
+ const alpha = i / len;
+ ctx.strokeStyle = b.color.replace('hsl', 'hsla').replace('%)', `%,${alpha * 0.7})`);
+ ctx.beginPath();
+ ctx.moveTo(p0.x, p0.y);
+ ctx.lineTo(p1.x, p1.y);
+ ctx.stroke();
+ }
+ }
+
+ // bodies
+ for (const b of bodies) {
+ const grad = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, b.radius*1.4);
+ grad.addColorStop(0, "#ffffff");
+ grad.addColorStop(0.3, b.color);
+ grad.addColorStop(1, "rgba(0,0,0,0)");
+ ctx.fillStyle = grad;
+ ctx.beginPath();
+ ctx.arc(b.x, b.y, b.radius*1.4, 0, Math.PI*2);
+ ctx.fill();
+
+ ctx.fillStyle = b.color;
+ ctx.beginPath();
+ ctx.arc(b.x, b.y, Math.max(1, b.radius*0.5), 0, Math.PI*2);
+ ctx.fill();
+
+ if (showVectors.checked) {
+ const scale = 0.15;
+ const vx = b.vx * scale;
+ const vy = b.vy * scale;
+ ctx.strokeStyle = "#88f";
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ ctx.moveTo(b.x, b.y);
+ ctx.lineTo(b.x + vx, b.y + vy);
+ ctx.stroke();
+ }
+ }
+ }
+
+ // Input for spawning bodies
+ let isDragging = false;
+ let dragStart = {x: 0, y: 0};
+ let dragEnd = {x: 0, y: 0};
+
+ function getMousePos(evt) {
+ const rect = canvas.getBoundingClientRect();
+ return {
+ x: (evt.clientX - rect.left),
+ y: (evt.clientY - rect.top)
+ };
+ }
+
+ canvas.addEventListener('mousedown', (e) => {
+ isDragging = true;
+ dragStart = getMousePos(e);
+ dragEnd = {...dragStart};
+ });
+
+ canvas.addEventListener('mousemove', (e) => {
+ if (!isDragging) return;
+ dragEnd = getMousePos(e);
+ });
+
+ canvas.addEventListener('mouseup', (e) => {
+ if (!isDragging) return;
+ isDragging = false;
+ dragEnd = getMousePos(e);
+ const dx = dragEnd.x - dragStart.x;
+ const dy = dragEnd.y - dragStart.y;
+ const speedFactor = parseFloat(velSlider.value) / 100;
+ const vx = dx * 0.01 * speedFactor;
+ const vy = dy * 0.01 * speedFactor;
+ const mass = parseFloat(massSlider.value);
+ addBody(dragStart.x, dragStart.y, vx, vy, mass);
+ });
+
+ canvas.addEventListener('mouseleave', () => {
+ isDragging = false;
+ });
+
+ // Touch support
+ canvas.addEventListener('touchstart', (e) => {
+ const t = e.touches[0];
+ isDragging = true;
+ dragStart = getMousePos(t);
+ dragEnd = {...dragStart};
+ e.preventDefault();
+ }, {passive:false});
+
+ canvas.addEventListener('touchmove', (e) => {
+ if (!isDragging) return;
+ const t = e.touches[0];
+ dragEnd = getMousePos(t);
+ e.preventDefault();
+ }, {passive:false});
+
+ canvas.addEventListener('touchend', (e) => {
+ if (!isDragging) return;
+ isDragging = false;
+ const dx = dragEnd.x - dragStart.x;
+ const dy = dragEnd.y - dragStart.y;
+ const speedFactor = parseFloat(velSlider.value) / 100;
+ const vx = dx * 0.01 * speedFactor;
+ const vy = dy * 0.01 * speedFactor;
+ const mass = parseFloat(massSlider.value);
+ addBody(dragStart.x, dragStart.y, vx, vy, mass);
+ e.preventDefault();
+ }, {passive:false});
+
+ function drawDragVector() {
+ if (!isDragging) return;
+ ctx.save();
+ ctx.setLineDash([4, 3]);
+ ctx.strokeStyle = "#0f8";
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ ctx.moveTo(dragStart.x, dragStart.y);
+ ctx.lineTo(dragEnd.x, dragEnd.y);
+ ctx.stroke();
+
+ const dx = dragEnd.x - dragStart.x;
+ const dy = dragEnd.y - dragStart.y;
+ const len = Math.hypot(dx, dy);
+ if (len > 4) {
+ const nx = dx / len;
+ const ny = dy / len;
+ const arrowSize = 8;
+ const ax = dragEnd.x;
+ const ay = dragEnd.y;
+ ctx.beginPath();
+ ctx.moveTo(ax, ay);
+ ctx.lineTo(ax - nx * arrowSize + ny * arrowSize * 0.5,
+ ay - ny * arrowSize - nx * arrowSize * 0.5);
+ ctx.moveTo(ax, ay);
+ ctx.lineTo(ax - nx * arrowSize - ny * arrowSize * 0.5,
+ ay - ny * arrowSize + nx * arrowSize * 0.5);
+ ctx.stroke();
+ }
+ ctx.restore();
+ }
+
+ function loop(now) {
+ const dt = Math.min(0.05, (now - lastTime) / 1000);
+ lastTime = now;
+
+ if (!paused) update(dt);
+ draw();
+ drawDragVector();
+
+ requestAnimationFrame(loop);
+ }
+
+ clearCanvas(true);
+ requestAnimationFrame(loop);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0e969e848172/gpt.png b/data/artifacts/0e969e848172/gpt.png
new file mode 100644
index 0000000..5a6fc27
Binary files /dev/null and b/data/artifacts/0e969e848172/gpt.png differ
diff --git a/data/artifacts/0e969e848172/grok.html b/data/artifacts/0e969e848172/grok.html
new file mode 100644
index 0000000..6d0c7fc
--- /dev/null
+++ b/data/artifacts/0e969e848172/grok.html
@@ -0,0 +1,339 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>2D Gravity Sandbox</title>
+<style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a12; font-family: system-ui, sans-serif; }
+ canvas { display: block; width: 100%; height: 100%; cursor: crosshair; }
+ #ui {
+ position: fixed; top: 12px; left: 12px; z-index: 10;
+ display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
+ }
+ #ui button, #ui label {
+ background: rgba(20, 24, 40, 0.85);
+ border: 1px solid rgba(120, 140, 200, 0.35);
+ color: #c8d0e8;
+ padding: 8px 14px;
+ border-radius: 6px;
+ font-size: 13px;
+ cursor: pointer;
+ backdrop-filter: blur(6px);
+ user-select: none;
+ }
+ #ui button:hover { background: rgba(40, 50, 80, 0.9); border-color: rgba(150, 170, 230, 0.5); }
+ #ui label { display: flex; align-items: center; gap: 6px; cursor: pointer; }
+ #ui input[type="checkbox"] { accent-color: #6af; cursor: pointer; }
+ #hint {
+ position: fixed; bottom: 12px; left: 50%; transform: translateX(-50%);
+ color: rgba(180, 190, 220, 0.5); font-size: 12px; pointer-events: none;
+ text-align: center; white-space: nowrap;
+ }
+ #mass {
+ position: fixed; top: 12px; right: 12px; z-index: 10;
+ background: rgba(20, 24, 40, 0.85);
+ border: 1px solid rgba(120, 140, 200, 0.35);
+ color: #c8d0e8;
+ padding: 8px 14px;
+ border-radius: 6px;
+ font-size: 13px;
+ backdrop-filter: blur(6px);
+ display: flex; align-items: center; gap: 8px;
+ }
+ #mass input[type="range"] { width: 100px; accent-color: #6af; cursor: pointer; }
+</style>
+</head>
+<body>
+<div id="ui">
+ <button id="clearBtn">Clear</button>
+ <label><input type="checkbox" id="trailToggle" checked> Trails</label>
+ <label><input type="checkbox" id="softToggle" checked> Soften</label>
+</div>
+<div id="mass">
+ <span>Mass</span>
+ <input type="range" id="massSlider" min="1" max="100" value="20">
+ <span id="massVal">20</span>
+</div>
+<div id="hint">Click to place bodies · Drag slightly to impart velocity</div>
+<canvas id="c"></canvas>
+<script>
+(function () {
+ const canvas = document.getElementById('c');
+ const ctx = canvas.getContext('2d');
+ const G = 0.4;
+ const MIN_DIST = 4;
+ const TRAIL_MAX = 180;
+ const DT = 1;
+
+ let W, H, dpr;
+ let bodies = [];
+ let showTrails = true;
+ let soften = true;
+ let hueCounter = 0;
+
+ // Drag-to-velocity state
+ let placing = false;
+ let placeX = 0, placeY = 0;
+ let placeStartX = 0, placeStartY = 0;
+
+ function resize() {
+ dpr = window.devicePixelRatio || 1;
+ 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);
+ resize();
+
+ function radiusFromMass(m) {
+ return Math.max(3, Math.sqrt(m) * 2.2);
+ }
+
+ function nextHue() {
+ hueCounter = (hueCounter + 47) % 360;
+ return hueCounter;
+ }
+
+ function addBody(x, y, vx, vy, mass) {
+ const hue = nextHue();
+ bodies.push({
+ x, y, vx, vy,
+ mass,
+ r: radiusFromMass(mass),
+ hue,
+ trail: [],
+ color: `hsl(${hue}, 75%, 60%)`,
+ glow: `hsla(${hue}, 80%, 55%, 0.35)`
+ });
+ }
+
+ // Seed a few bodies for immediate interest
+ function seed() {
+ const cx = W / 2, cy = H / 2;
+ addBody(cx, cy, 0, 0, 80);
+ addBody(cx + 160, cy, 0, 1.6, 12);
+ addBody(cx - 220, cy + 40, 0, -1.2, 8);
+ addBody(cx + 60, cy - 200, 1.4, 0.3, 5);
+ }
+ seed();
+
+ document.getElementById('clearBtn').addEventListener('click', () => {
+ bodies = [];
+ });
+ document.getElementById('trailToggle').addEventListener('change', (e) => {
+ showTrails = e.target.checked;
+ if (!showTrails) bodies.forEach(b => { b.trail = []; });
+ });
+ document.getElementById('softToggle').addEventListener('change', (e) => {
+ soften = e.target.checked;
+ });
+ const massSlider = document.getElementById('massSlider');
+ const massVal = document.getElementById('massVal');
+ massSlider.addEventListener('input', () => { massVal.textContent = massSlider.value; });
+
+ canvas.addEventListener('pointerdown', (e) => {
+ placing = true;
+ placeX = placeStartX = e.clientX;
+ placeY = placeStartY = e.clientY;
+ });
+ canvas.addEventListener('pointermove', (e) => {
+ if (!placing) return;
+ placeX = e.clientX;
+ placeY = e.clientY;
+ });
+ function finishPlace(e) {
+ if (!placing) return;
+ placing = false;
+ const x = placeStartX;
+ const y = placeStartY;
+ const dx = (e.clientX !== undefined ? e.clientX : placeX) - placeStartX;
+ const dy = (e.clientY !== undefined ? e.clientY : placeY) - placeStartY;
+ // Velocity from drag (scale down)
+ const vx = dx * 0.035;
+ const vy = dy * 0.035;
+ const mass = parseFloat(massSlider.value) || 20;
+ addBody(x, y, vx, vy, mass);
+ }
+ canvas.addEventListener('pointerup', finishPlace);
+ canvas.addEventListener('pointercancel', () => { placing = false; });
+ canvas.addEventListener('pointerleave', (e) => {
+ if (placing) finishPlace(e);
+ });
+
+ function step() {
+ const n = bodies.length;
+ // Accelerations
+ const ax = new Float64Array(n);
+ const ay = new Float64Array(n);
+
+ for (let i = 0; i < n; i++) {
+ for (let j = i + 1; j < n; j++) {
+ const a = bodies[i], b = bodies[j];
+ let dx = b.x - a.x;
+ let dy = b.y - a.y;
+ let distSq = dx * dx + dy * dy;
+ if (soften) distSq += MIN_DIST * MIN_DIST;
+ else if (distSq < 1e-6) continue;
+ const dist = Math.sqrt(distSq);
+ const force = G / (distSq * dist); // G * m1*m2 / r^2, then /m later → G*other / r^3 * r_vec
+ const fx = force * dx;
+ const fy = force * dy;
+ ax[i] += fx * b.mass;
+ ay[i] += fy * b.mass;
+ ax[j] -= fx * a.mass;
+ ay[j] -= fy * a.mass;
+ }
+ }
+
+ // Integrate + merge on collision
+ const merged = new Set();
+ for (let i = 0; i < n; i++) {
+ if (merged.has(i)) continue;
+ const b = bodies[i];
+ b.vx += ax[i] * DT;
+ b.vy += ay[i] * DT;
+ b.x += b.vx * DT;
+ b.y += b.vy * DT;
+
+ if (showTrails) {
+ b.trail.push(b.x, b.y);
+ if (b.trail.length > TRAIL_MAX * 2) {
+ b.trail.splice(0, b.trail.length - TRAIL_MAX * 2);
+ }
+ }
+ }
+
+ // Simple inelastic merge when overlapping
+ for (let i = 0; i < bodies.length; i++) {
+ if (merged.has(i)) continue;
+ for (let j = i + 1; j < bodies.length; j++) {
+ if (merged.has(j)) continue;
+ const a = bodies[i], b = bodies[j];
+ const dx = b.x - a.x, dy = b.y - a.y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < a.r + b.r * 0.5) {
+ // Merge j into i
+ const total = a.mass + b.mass;
+ a.vx = (a.vx * a.mass + b.vx * b.mass) / total;
+ a.vy = (a.vy * a.mass + b.vy * b.mass) / total;
+ a.x = (a.x * a.mass + b.x * b.mass) / total;
+ a.y = (a.y * a.mass + b.y * b.mass) / total;
+ a.mass = total;
+ a.r = radiusFromMass(total);
+ // Keep heavier color influence
+ if (b.mass > a.mass - b.mass) {
+ a.hue = b.hue;
+ a.color = b.color;
+ a.glow = b.glow;
+ }
+ a.trail = [];
+ merged.add(j);
+ }
+ }
+ }
+ if (merged.size) {
+ bodies = bodies.filter((_, idx) => !merged.has(idx));
+ }
+ }
+
+ function draw() {
+ ctx.fillStyle = 'rgba(10, 10, 18, 0.35)';
+ ctx.fillRect(0, 0, W, H);
+
+ // Soft starfield residual: faint vignette
+ // Trails
+ if (showTrails) {
+ for (const b of bodies) {
+ const t = b.trail;
+ if (t.length < 4) continue;
+ ctx.beginPath();
+ ctx.moveTo(t[0], t[1]);
+ for (let i = 2; i < t.length; i += 2) {
+ ctx.lineTo(t[i], t[i + 1]);
+ }
+ const len = t.length / 2;
+ ctx.strokeStyle = b.color;
+ ctx.globalAlpha = 0.55;
+ ctx.lineWidth = Math.max(1, b.r * 0.35);
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.stroke();
+ ctx.globalAlpha = 1;
+ }
+ }
+
+ // Bodies
+ for (const b of bodies) {
+ // Glow
+ const grd = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, b.r * 3.5);
+ grd.addColorStop(0, b.glow);
+ grd.addColorStop(1, 'transparent');
+ ctx.fillStyle = grd;
+ ctx.beginPath();
+ ctx.arc(b.x, b.y, b.r * 3.5, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Core
+ const core = ctx.createRadialGradient(
+ b.x - b.r * 0.3, b.y - b.r * 0.3, b.r * 0.1,
+ b.x, b.y, b.r
+ );
+ core.addColorStop(0, `hsl(${b.hue}, 60%, 88%)`);
+ core.addColorStop(0.5, b.color);
+ core.addColorStop(1, `hsl(${b.hue}, 80%, 35%)`);
+ ctx.fillStyle = core;
+ ctx.beginPath();
+ ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ // Placement preview
+ if (placing) {
+ const mass = parseFloat(massSlider.value) || 20;
+ const r = radiusFromMass(mass);
+ ctx.beginPath();
+ ctx.arc(placeStartX, placeStartY, r, 0, Math.PI * 2);
+ ctx.strokeStyle = 'rgba(200, 220, 255, 0.7)';
+ ctx.lineWidth = 1.5;
+ ctx.setLineDash([4, 4]);
+ ctx.stroke();
+ ctx.setLineDash([]);
+ // Velocity arrow
+ ctx.beginPath();
+ ctx.moveTo(placeStartX, placeStartY);
+ ctx.lineTo(placeX, placeY);
+ ctx.strokeStyle = 'rgba(150, 200, 255, 0.8)';
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ // Arrow head
+ const ang = Math.atan2(placeY - placeStartY, placeX - placeStartX);
+ const ah = 8;
+ ctx.beginPath();
+ ctx.moveTo(placeX, placeY);
+ ctx.lineTo(placeX - ah * Math.cos(ang - 0.4), placeY - ah * Math.sin(ang - 0.4));
+ ctx.lineTo(placeX - ah * Math.cos(ang + 0.4), placeY - ah * Math.sin(ang + 0.4));
+ ctx.closePath();
+ ctx.fillStyle = 'rgba(150, 200, 255, 0.8)';
+ ctx.fill();
+ }
+ }
+
+ function loop() {
+ // Substeps for stability with many bodies
+ const sub = bodies.length > 25 ? 2 : 1;
+ for (let s = 0; s < sub; s++) step();
+ draw();
+ requestAnimationFrame(loop);
+ }
+ // Initial clear
+ ctx.fillStyle = '#0a0a12';
+ ctx.fillRect(0, 0, W, H);
+ requestAnimationFrame(loop);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0e969e848172/grok.png b/data/artifacts/0e969e848172/grok.png
new file mode 100644
index 0000000..4675b24
Binary files /dev/null and b/data/artifacts/0e969e848172/grok.png differ
diff --git a/data/artifacts/0e969e848172/hermes3-8b.html b/data/artifacts/0e969e848172/hermes3-8b.html
new file mode 100644
index 0000000..1e22155
--- /dev/null
+++ b/data/artifacts/0e969e848172/hermes3-8b.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>2D Gravity Sandbox</title>
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ }
+
+ canvas {
+ display: block;
+ background-color: #f0f0f0;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="canvas"></canvas>
+ <div>
+ <button id="clear">Clear</button>
+ <label><input type="checkbox" id="trailToggle"> Trails</label>
+ </div>
+
+ <script>
+ const canvas = document.getElementById('canvas');
+ const ctx = canvas.getContext('2d');
+
+ canvas.width = window.innerWidth * 0.8;
+ canvas.height = window.innerHeight * 0.8;
+
+ let bodies = [];
+ let trails = true;
+
+ function Body(x, y, radius, mass) {
+ this.x = x;
+ this.y = y;
+ this.radius = radius;
+ this.mass = mass;
+ this.speedX = 0;
+ this.speedY = 0;
+ }
+
+ function placeBody(e) {
+ if (e.button === 0) { // left click
+ const radius = Math.random() * 10 + 1;
+ const mass = radius * 2;
+ bodies.push(new Body(e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop, radius, mass));
+ }
+ }
+
+ canvas.addEventListener('mousedown', placeBody);
+
+ function updateBodies() {
+ for (let i = 0; i < bodies.length; i++) {
+ const bodyA = bodies[i];
+
+ for (let j = i + 1; j < bodies.length; j++) {
+ const bodyB = bodies[j];
+
+ const distanceX = bodyA.x - bodyB.x;
+ const distanceY = bodyA.y - bodyB.y;
+ const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
+ const force = (bodyA.mass * bodyB.mass) / (distance * distance);
+ const forceX = force * distanceX / distance;
+ const forceY = force * distanceY / distance;
+
+ bodyA.speedX += forceX;
+ bodyA.speedY += forceY;
+
+ bodyB.speedX -= forceX;
+ bodyB.speedY -= forceY;
+ }
+
+ bodyA.x += bodyA.speedX;
+ bodyA.y += bodyA.speedY;
+
+ if (trails) {
+ for (let k = 0; k < bodies.length; k++) {
+ if (i === k) continue;
+
+ const trailBody = bodies[k];
+
+ const distanceX = bodyA.x - trailBody.x;
+ const distanceY = bodyA.y - trailBody.y;
+ const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
+
+ if (distance < bodyA.radius + trailBody.radius) {
+ ctx.beginPath();
+ ctx.moveTo(trailBody.x, trailBody.y);
+ ctx.lineTo(bodyA.x, bodyA.y);
+ ctx.strokeStyle = `hsl(${i}, 100%, 50%)`;
+ ctx.stroke();
+ }
+ }
+ }
+ }
+
+ if (trails) {
+ for (let i = 0; i < bodies.length; i++) {
+ const body = bodies[i];
+
+ ctx.beginPath();
+ ctx.arc(body.x, body.y, body.radius, 0, Math.PI * 2);
+ ctx.fillStyle = `hsl(${i}, 100%, 50%)`;
+ ctx.fill();
+
+ if (trails) {
+ ctx.beginPath();
+ ctx.arc(body.x, body.y, body.radius / 3, 0, Math.PI * 2);
+ ctx.strokeStyle = `rgba(255, 255, 255, 0.7)`;
+ ctx.stroke();
+ }
+ }
+ }
+ }
+
+ function loop() {
+ requestAnimationFrame(loop);
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ updateBodies();
+ }
+
+ document.getElementById('clear').addEventListener('click', () => {
+ bodies = [];
+ });
+
+ document.getElementById('trailToggle').addEventListener('change', (e) => {
+ trails = e.target.checked;
+ });
+
+ loop();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0e969e848172/hermes3-8b.png b/data/artifacts/0e969e848172/hermes3-8b.png
new file mode 100644
index 0000000..8265c18
Binary files /dev/null and b/data/artifacts/0e969e848172/hermes3-8b.png differ
diff --git a/data/artifacts/0e969e848172/hf-qwen-coder-32b.html b/data/artifacts/0e969e848172/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..441617f
--- /dev/null
+++ b/data/artifacts/0e969e848172/hf-qwen-coder-32b.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>2D Gravity Sandbox</title>
+<style>
+ body { margin: 0; overflow: hidden; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #000; color: white; }
+ canvas { border: 1px solid white; cursor: crosshair; }
+ button, input { position: absolute; top: 10px; background-color: #333; color: white; border: none; padding: 5px 10px; cursor: pointer; }
+ button:hover, input:hover { background-color: #555; }
+ input { left: 120px; }
+</style>
+</head>
+<body>
+<canvas id="sandbox"></canvas>
+<button onclick="clearCanvas()">Clear</button>
+<input type="checkbox" id="trailToggle" checked> Trails
+<script>
+const canvas = document.getElementById('sandbox');
+const ctx = canvas.getContext('2d');
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+let bodies = [];
+let showTrails = true;
+
+function Body(x, y, mass) {
+ this.x = x;
+ this.y = y;
+ this.mass = mass;
+ this.vx = 0;
+ this.vy = 0;
+ this.trail = [];
+}
+
+Body.prototype.updateVelocity = function(bodies) {
+ for (let body of bodies) {
+ if (body === this) continue;
+ let dx = body.x - this.x;
+ let dy = body.y - this.y;
+ let distance = Math.sqrt(dx * dx + dy * dy);
+ let force = (this.mass * body.mass) / (distance * distance);
+ let angle = Math.atan2(dy, dx);
+ this.vx += force * Math.cos(angle) / this.mass;
+ this.vy += force * Math.sin(angle) / this.mass;
+ }
+};
+
+Body.prototype.updatePosition = function() {
+ this.x += this.vx;
+ this.y += this.vy;
+ if (showTrails) {
+ this.trail.push({x: this.x, y: this.y});
+ if (this.trail.length > 100) this.trail.shift();
+ }
+};
+
+Body.prototype.draw = function() {
+ ctx.beginPath();
+ ctx.arc(this.x, this.y, Math.log2(this.mass), 0, Math.PI * 2);
+ ctx.fillStyle = 'white';
+ ctx.fill();
+ if (showTrails) {
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
+ ctx.beginPath();
+ ctx.moveTo(this.trail[0].x, this.trail[0].y);
+ for (let point of this.trail) {
+ ctx.lineTo(point.x, point.y);
+ }
+ ctx.stroke();
+ }
+};
+
+function animate() {
+ if (!showTrails) clearCanvas();
+ bodies.forEach(body => body.updateVelocity(bodies));
+ bodies.forEach(body => body.updatePosition());
+ bodies.forEach(body => body.draw());
+ requestAnimationFrame(animate);
+}
+
+canvas.addEventListener('click', function(event) {
+ let rect = canvas.getBoundingClientRect();
+ let x = event.clientX - rect.left;
+ let y = event.clientY - rect.top;
+ bodies.push(new Body(x, y, Math.random() * 100 + 10));
+});
+
+function clearCanvas() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ if (!showTrails) bodies.forEach(body => body.trail = []);
+}
+
+document.getElementById('trailToggle').addEventListener('change', function(event) {
+ showTrails = event.target.checked;
+});
+
+animate();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0e969e848172/hf-qwen-coder-32b.png b/data/artifacts/0e969e848172/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..63176e8
Binary files /dev/null and b/data/artifacts/0e969e848172/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/0e969e848172/kimi.html b/data/artifacts/0e969e848172/kimi.html
new file mode 100644
index 0000000..be194d9
--- /dev/null
+++ b/data/artifacts/0e969e848172/kimi.html
@@ -0,0 +1,261 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Gravity Sandbox</title>
+ <style>
+ body {
+ margin: 0;
+ overflow: hidden;
+ background: #000;
+ font-family: sans-serif;
+ }
+ canvas {
+ display: block;
+ }
+ #controls {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ background: rgba(0,0,0,0.8);
+ padding: 15px;
+ border-radius: 8px;
+ color: white;
+ user-select: none;
+ border: 1px solid #333;
+ }
+ button {
+ margin: 5px;
+ padding: 8px 15px;
+ cursor: pointer;
+ background: #222;
+ color: white;
+ border: 1px solid #444;
+ border-radius: 4px;
+ font-size: 14px;
+ }
+ button:hover {
+ background: #333;
+ border-color: #666;
+ }
+ button.active {
+ background: #2a5;
+ border-color: #4c7;
+ }
+ #info {
+ margin-top: 10px;
+ font-size: 12px;
+ color: #888;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="canvas"></canvas>
+ <div id="controls">
+ <button id="clearBtn">Clear All</button>
+ <button id="trailBtn" class="active">Trails: ON</button>
+ <div id="info">Click & drag to launch planets</div>
+ </div>
+
+ <script>
+ const canvas = document.getElementById('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function resize() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ }
+ resize();
+ window.addEventListener('resize', resize);
+
+ const G = 0.8;
+ const SOFTENING = 10;
+ const TRAIL_LENGTH = 150;
+ const TIME_STEP = 0.4;
+
+ let planets = [];
+ let trails = true;
+ let isDragging = false;
+ let dragStart = {x: 0, y: 0};
+ let dragCurrent = {x: 0, y: 0};
+
+ class Planet {
+ constructor(x, y, vx, vy, mass) {
+ this.x = x;
+ this.y = y;
+ this.vx = vx;
+ this.vy = vy;
+ this.mass = mass;
+ this.radius = Math.cbrt(mass) * 2.5;
+ this.color = `hsl(${Math.random() * 360}, 80%, 60%)`;
+ this.history = [];
+ }
+
+ update() {
+ if (trails) {
+ this.history.push({x: this.x, y: this.y});
+ if (this.history.length > TRAIL_LENGTH) {
+ this.history.shift();
+ }
+ } else {
+ this.history = [];
+ }
+
+ this.x += this.vx * TIME_STEP;
+ this.y += this.vy * TIME_STEP;
+
+ if (this.x < -1000 || this.x > canvas.width + 1000 ||
+ this.y < -1000 || this.y > canvas.height + 1000) {
+ return false;
+ }
+ return true;
+ }
+
+ draw() {
+ if (trails && this.history.length > 1) {
+ ctx.lineWidth = 2;
+ for (let i = 0; i < this.history.length - 1; i++) {
+ const alpha = (i / this.history.length) * 0.6;
+ ctx.strokeStyle = this.color.replace(')', `, ${alpha})`).replace('hsl', 'hsla');
+ ctx.beginPath();
+ ctx.moveTo(this.history[i].x, this.history[i].y);
+ ctx.lineTo(this.history[i+1].x, this.history[i+1].y);
+ ctx.stroke();
+ }
+ }
+
+ ctx.beginPath();
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
+ ctx.fillStyle = this.color;
+ ctx.fill();
+
+ const gradient = ctx.createRadialGradient(
+ this.x, this.y, 0,
+ this.x, this.y, this.radius * 3
+ );
+ gradient.addColorStop(0, this.color.replace(')', ', 0.4)').replace('hsl', 'hsla'));
+ gradient.addColorStop(1, 'transparent');
+ ctx.fillStyle = gradient;
+ ctx.beginPath();
+ ctx.arc(this.x, this.y, this.radius * 3, 0, Math.PI * 2);
+ ctx.fill();
+ }
+ }
+
+ function applyGravity() {
+ for (let i = 0; i < planets.length; i++) {
+ for (let j = i + 1; j < planets.length; j++) {
+ const p1 = planets[i];
+ const p2 = planets[j];
+
+ const dx = p2.x - p1.x;
+ const dy = p2.y - p1.y;
+ const distSq = dx * dx + dy * dy;
+ const dist = Math.sqrt(distSq);
+
+ if (dist < p1.radius + p2.radius) {
+ const overlap = (p1.radius + p2.radius - dist) * 0.5;
+ const nx = dx / dist;
+ const ny = dy / dist;
+ p1.x -= nx * overlap;
+ p1.y -= ny * overlap;
+ p2.x += nx * overlap;
+ p2.y += ny * overlap;
+ }
+
+ const force = G * p1.mass * p2.mass / (distSq + SOFTENING * SOFTENING);
+ const fx = force * dx / dist;
+ const fy = force * dy / dist;
+
+ p1.vx += fx / p1.mass;
+ p1.vy += fy / p1.mass;
+ p2.vx -= fx / p2.mass;
+ p2.vy -= fy / p2.mass;
+ }
+ }
+ }
+
+ function animate() {
+ if (trails) {
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.15)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ } else {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ }
+
+ applyGravity();
+
+ for (let i = planets.length - 1; i >= 0; i--) {
+ const alive = planets[i].update();
+ if (!alive) {
+ planets.splice(i, 1);
+ } else {
+ planets[i].draw();
+ }
+ }
+
+ if (isDragging) {
+ ctx.beginPath();
+ ctx.moveTo(dragStart.x, dragStart.y);
+ ctx.lineTo(dragCurrent.x, dragCurrent.y);
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
+ ctx.lineWidth = 2;
+ ctx.setLineDash([5, 5]);
+ ctx.stroke();
+ ctx.setLineDash([]);
+
+ ctx.beginPath();
+ ctx.arc(dragStart.x, dragStart.y, 8, 0, Math.PI * 2);
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
+ ctx.fill();
+ }
+
+ requestAnimationFrame(animate);
+ }
+
+ canvas.addEventListener('mousedown', (e) => {
+ isDragging = true;
+ dragStart.x = e.clientX;
+ dragStart.y = e.clientY;
+ dragCurrent.x = e.clientX;
+ dragCurrent.y = e.clientY;
+ });
+
+ canvas.addEventListener('mousemove', (e) => {
+ if (isDragging) {
+ dragCurrent.x = e.clientX;
+ dragCurrent.y = e.clientY;
+ }
+ });
+
+ canvas.addEventListener('mouseup', (e) => {
+ if (isDragging) {
+ const vx = (dragStart.x - e.clientX) * 0.08;
+ const vy = (dragStart.y - e.clientY) * 0.08;
+ const mass = 15 + Math.random() * 50;
+ planets.push(new Planet(dragStart.x, dragStart.y, vx, vy, mass));
+ isDragging = false;
+ }
+ });
+
+ document.getElementById('clearBtn').addEventListener('click', () => {
+ planets = [];
+ });
+
+ document.getElementById('trailBtn').addEventListener('click', (e) => {
+ trails = !trails;
+ e.target.textContent = trails ? 'Trails: ON' : 'Trails: OFF';
+ e.target.classList.toggle('active');
+ if (!trails) {
+ for (const p of planets) {
+ p.history = [];
+ }
+ }
+ });
+
+ planets.push(new Planet(canvas.width/2, canvas.height/2, 0, 0, 300));
+ animate();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0e969e848172/kimi.png b/data/artifacts/0e969e848172/kimi.png
new file mode 100644
index 0000000..b9e8957
Binary files /dev/null and b/data/artifacts/0e969e848172/kimi.png differ
diff --git a/data/artifacts/0e969e848172/qwen25-7b.html b/data/artifacts/0e969e848172/qwen25-7b.html
new file mode 100644
index 0000000..361496d
--- /dev/null
+++ b/data/artifacts/0e969e848172/qwen25-7b.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>2D Gravity Sandbox</title>
+ <style>
+ body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #000; }
+ canvas { border: 1px solid #fff; pointer-events: none; }
+ .controls { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); }
+ </style>
+</head>
+<body>
+<canvas id="gravityCanvas" width="800" height="600"></canvas>
+<div class="controls">
+ <button onclick="clearPlanets()">Clear Planets</button>
+ <label><input type="checkbox" onchange="toggleTrails()" checked> Show Trails</label>
+</div>
+<script>
+const canvas = document.getElementById('gravityCanvas');
+const ctx = canvas.getContext('2d');
+
+let planets = [];
+let trails = [];
+
+function Planet(x, y, mass) {
+ this.x = x;
+ this.y = y;
+ this.mass = mass;
+ this.vx = 0;
+ this.vy = 0;
+}
+
+Planet.prototype.update = function() {
+ for (let i = 0; i < planets.length; i++) {
+ if (this !== planets[i]) {
+ const dx = this.x - planets[i].x;
+ const dy = this.y - planets[i].y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+ const force = (this.mass * planets[i].mass) / (dist * dist);
+ const ax = (dx / dist) * (force / this.mass);
+ const ay = (dy / dist) * (force / this.mass);
+
+ this.vx += ax;
+ this.vy += ay;
+
+ if (trails.length > 0 && trails[trails.length - 1].x === this.x && trails[trails.length - 1].y === this.y) {
+ trails.pop();
+ }
+ trails.push({ x: this.x, y: this.y });
+ }
+ }
+
+ this.x += this.vx;
+ this.y += this.vy;
+
+ if (this.x < 0 || this.x > canvas.width || this.y < 0 || this.y > canvas.height) {
+ planets.splice(planets.indexOf(this), 1);
+ trails = [];
+ }
+};
+
+function clearPlanets() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ planets = [];
+ trails = [];
+}
+
+function toggleTrails() {
+ trails = trails.length ? [] : [...trails];
+}
+
+canvas.addEventListener('click', e => {
+ const x = Math.max(0, Math.min(canvas.width - 1, (e.clientX - canvas.getBoundingClientRect().left) / canvas.width * canvas.width));
+ const y = Math.max(0, Math.min(canvas.height - 1, (e.clientY - canvas.getBoundingClientRect().top) / canvas.height * canvas.height));
+ planets.push(new Planet(x, y, e.button + 1));
+});
+
+setInterval(() => {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ for (let i = 0; i < planets.length; i++) {
+ const planet = planets[i];
+ ctx.beginPath();
+ ctx.arc(planet.x, planet.y, 5 * Math.sqrt(planet.mass), 0, 2 * Math.PI);
+ ctx.fillStyle = `hsl(${i * 360 / planets.length}, 100%, 50%)`;
+ ctx.fill();
+
+ if (trails.length) {
+ const trail = trails[trails.length - 1];
+ ctx.beginPath();
+ ctx.moveTo(trail.x, trail.y);
+ ctx.lineTo(planet.x, planet.y);
+ ctx.strokeStyle = `hsla(${i * 360 / planets.length}, 100%, 50%, 0.2)`;
+ ctx.stroke();
+ }
+
+ planet.update();
+ }
+}, 16);
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0e969e848172/qwen25-7b.png b/data/artifacts/0e969e848172/qwen25-7b.png
new file mode 100644
index 0000000..bfc031a
Binary files /dev/null and b/data/artifacts/0e969e848172/qwen25-7b.png differ
diff --git a/data/artifacts/0e969e848172/qwen3-14b.html b/data/artifacts/0e969e848172/qwen3-14b.html
new file mode 100644
index 0000000..299276b
--- /dev/null
+++ b/data/artifacts/0e969e848172/qwen3-14b.html
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>2D Gravity Sandbox</title>
+ <style>
+ body {
+ margin: 0;
+ background: #000;
+ overflow: hidden;
+ font-family: sans-serif;
+ color: #fff;
+ }
+ canvas {
+ display: block;
+ }
+ #controls {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ background: rgba(0,0,0,0.5);
+ padding: 10px;
+ border-radius: 5px;
+ }
+ button {
+ margin-right: 10px;
+ padding: 5px 10px;
+ cursor: pointer;
+ }
+ </style>
+</head>
+<body>
+ <div id="controls">
+ <button id="clear">Clear</button>
+ <button id="toggleTrail">Toggle Trail</button>
+ </div>
+ <canvas id="canvas"></canvas>
+ <script>
+ const canvas = document.getElementById('canvas');
+ 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 planets = [];
+ const trails = [];
+ const trailToggle = document.getElementById('toggleTrail');
+ const clearBtn = document.getElementById('clear');
+ let showTrails = true;
+
+ trailToggle.addEventListener('click', () => {
+ showTrails = !showTrails;
+ });
+
+ clearBtn.addEventListener('click', () => {
+ planets.length = 0;
+ trails.length = 0;
+ });
+
+ class Planet {
+ constructor(x, y, mass, color) {
+ this.x = x;
+ this.y = y;
+ this.mass = mass;
+ this.color = color;
+ this.vx = 0;
+ this.vy = 0;
+ this.trail = [];
+ }
+
+ update() {
+ let ax = 0;
+ let ay = 0;
+ for (let other of planets) {
+ if (other === this) continue;
+ let dx = other.x - this.x;
+ let dy = other.y - this.y;
+ let distSq = dx * dx + dy * dy;
+ let dist = Math.sqrt(distSq);
+ let force = (other.mass * this.mass) / (distSq * 10000);
+ ax += (dx / dist) * force;
+ ay += (dy / dist) * force;
+ }
+ this.vx += ax * 0.1;
+ this.vy += ay * 0.1;
+ this.x += this.vx;
+ this.y += this.vy;
+ }
+
+ draw() {
+ ctx.beginPath();
+ ctx.arc(this.x, this.y, Math.sqrt(this.mass) * 2, 0, Math.PI * 2);
+ ctx.fillStyle = this.color;
+ ctx.fill();
+ ctx.closePath();
+ }
+
+ drawTrail() {
+ if (!showTrails) return;
+ ctx.beginPath();
+ ctx.moveTo(this.trail[0].x, this.trail[0].y);
+ for (let i = 1; i < this.trail.length; i++) {
+ ctx.lineTo(this.trail[i].x, this.trail[i].y);
+ }
+ ctx.strokeStyle = this.color;
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ ctx.closePath();
+ }
+
+ addTrailPoint() {
+ this.trail.push({ x: this.x, y: this.y });
+ if (this.trail.length > 50) this.trail.shift();
+ }
+ }
+
+ canvas.addEventListener('click', (e) => {
+ const x = e.clientX;
+ const y = e.clientY;
+ const mass = 1000 + Math.random() * 5000;
+ const color = `hsl(${Math.random() * 360}, 100%, 50%)`;
+ planets.push(new Planet(x, y, mass, color));
+ });
+
+ function animate() {
+ ctx.clearRect(0, 0, width, height);
+ for (let planet of planets) {
+ planet.update();
+ planet.addTrailPoint();
+ planet.draw();
+ planet.drawTrail();
+ }
+ requestAnimationFrame(animate);
+ }
+
+ animate();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0e969e848172/qwen3-14b.png b/data/artifacts/0e969e848172/qwen3-14b.png
new file mode 100644
index 0000000..9986583
Binary files /dev/null and b/data/artifacts/0e969e848172/qwen3-14b.png differ
diff --git a/data/artifacts/1fd68f0d37b7/claude-code.html b/data/artifacts/1fd68f0d37b7/claude-code.html
new file mode 100644
index 0000000..15f0d56
--- /dev/null
+++ b/data/artifacts/1fd68f0d37b7/claude-code.html
@@ -0,0 +1,330 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Mic Audio Visualizer</title>
+<style>
+ :root {
+ --bg: #0b0e14;
+ --panel: #131826;
+ --text: #e8ecf4;
+ --muted: #8a93a6;
+ --accent: #4dd0e1;
+ --accent2: #b388ff;
+ --border: rgba(255,255,255,0.08);
+ }
+ [data-theme="sunset"] {
+ --bg: #1a0f14;
+ --panel: #261520;
+ --text: #ffeede;
+ --muted: #b08a7a;
+ --accent: #ff8a50;
+ --accent2: #ff4081;
+ --border: rgba(255,200,150,0.12);
+ }
+ [data-theme="matrix"] {
+ --bg: #050b05;
+ --panel: #0a140a;
+ --text: #d8ffd8;
+ --muted: #5a8a5a;
+ --accent: #00e676;
+ --accent2: #76ff03;
+ --border: rgba(0,255,100,0.12);
+ }
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ html, body { height: 100%; }
+ body {
+ background: var(--bg);
+ color: var(--text);
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 16px;
+ transition: background .4s ease, color .4s ease;
+ }
+ .app {
+ width: 100%;
+ max-width: 900px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+ header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ flex-wrap: wrap;
+ }
+ h1 {
+ font-size: 18px;
+ font-weight: 600;
+ letter-spacing: .04em;
+ }
+ h1 .dot {
+ display: inline-block;
+ width: 10px; height: 10px;
+ border-radius: 50%;
+ background: var(--muted);
+ margin-right: 8px;
+ vertical-align: baseline;
+ transition: background .3s;
+ }
+ h1 .dot.live { background: var(--accent); box-shadow: 0 0 10px var(--accent); }
+ .controls { display: flex; gap: 8px; }
+ button {
+ background: var(--panel);
+ color: var(--text);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 8px 14px;
+ font-size: 13px;
+ cursor: pointer;
+ transition: border-color .2s, transform .1s;
+ }
+ button:hover { border-color: var(--accent); }
+ button:active { transform: scale(.97); }
+ button.primary {
+ background: linear-gradient(135deg, var(--accent), var(--accent2));
+ color: #fff;
+ border: none;
+ font-weight: 600;
+ }
+ .stage {
+ position: relative;
+ background: var(--panel);
+ border: 1px solid var(--border);
+ border-radius: 14px;
+ overflow: hidden;
+ transition: background .4s ease;
+ }
+ canvas { display: block; width: 100%; }
+ #bars { height: 260px; }
+ #wave { height: 140px; border-top: 1px solid var(--border); }
+ .overlay {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 14px;
+ text-align: center;
+ padding: 24px;
+ background: color-mix(in srgb, var(--bg) 65%, transparent);
+ backdrop-filter: blur(3px);
+ }
+ .overlay.hidden { display: none; }
+ .overlay .icon { font-size: 42px; }
+ .overlay p { color: var(--muted); font-size: 14px; max-width: 420px; line-height: 1.5; }
+ .overlay .err-title { color: var(--text); font-size: 16px; font-weight: 600; }
+ footer { color: var(--muted); font-size: 12px; text-align: center; }
+</style>
+</head>
+<body data-theme="ocean">
+<div class="app">
+ <header>
+ <h1><span class="dot" id="liveDot"></span>Mic Visualizer</h1>
+ <div class="controls">
+ <button id="themeBtn" title="Cycle color theme">🎨 Theme: Ocean</button>
+ <button id="startBtn" class="primary">🎤 Start</button>
+ </div>
+ </header>
+
+ <div class="stage">
+ <canvas id="bars"></canvas>
+ <canvas id="wave"></canvas>
+ <div class="overlay" id="overlay">
+ <div class="icon">🎤</div>
+ <p id="overlayMsg">Click <strong>Start</strong> and allow microphone access to see live frequency bars and a waveform of your audio.</p>
+ </div>
+ </div>
+
+ <footer>WebAudio AnalyserNode · frequency bars + time-domain waveform · no data leaves your device</footer>
+</div>
+
+<script>
+(function () {
+ var themes = ["ocean", "sunset", "matrix"];
+ var themeNames = { ocean: "Ocean", sunset: "Sunset", matrix: "Matrix" };
+ var themeIdx = 0;
+
+ var barsCanvas = document.getElementById("bars");
+ var waveCanvas = document.getElementById("wave");
+ var barsCtx = barsCanvas.getContext("2d");
+ var waveCtx = waveCanvas.getContext("2d");
+ var overlay = document.getElementById("overlay");
+ var overlayMsg = document.getElementById("overlayMsg");
+ var startBtn = document.getElementById("startBtn");
+ var themeBtn = document.getElementById("themeBtn");
+ var liveDot = document.getElementById("liveDot");
+
+ var audioCtx = null, analyser = null, freqData = null, timeData = null;
+ var running = false, rafId = null;
+
+ function sizeCanvases() {
+ var dpr = window.devicePixelRatio || 1;
+ [barsCanvas, waveCanvas].forEach(function (c) {
+ var rect = c.getBoundingClientRect();
+ c.width = Math.max(1, Math.round(rect.width * dpr));
+ c.height = Math.max(1, Math.round(rect.height * dpr));
+ });
+ }
+ window.addEventListener("resize", sizeCanvases);
+ sizeCanvases();
+
+ function cssVar(name) {
+ return getComputedStyle(document.body).getPropertyValue(name).trim();
+ }
+
+ themeBtn.addEventListener("click", function () {
+ themeIdx = (themeIdx + 1) % themes.length;
+ var t = themes[themeIdx];
+ document.body.setAttribute("data-theme", t);
+ themeBtn.textContent = "🎨 Theme: " + themeNames[t];
+ if (!running) drawIdle();
+ });
+
+ function showError(title, msg) {
+ overlay.classList.remove("hidden");
+ overlay.innerHTML =
+ '<div class="icon">🚫</div>' +
+ '<p class="err-title">' + title + "</p>" +
+ "<p>" + msg + "</p>";
+ }
+
+ startBtn.addEventListener("click", function () {
+ if (running) return;
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
+ showError("Microphone not available",
+ "This browser (or this embedded frame) doesn't expose getUserMedia. Try opening the page directly over HTTPS or localhost.");
+ return;
+ }
+ startBtn.disabled = true;
+ startBtn.textContent = "⏳ Requesting…";
+ navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
+ var source = audioCtx.createMediaStreamSource(stream);
+ analyser = audioCtx.createAnalyser();
+ analyser.fftSize = 2048;
+ analyser.smoothingTimeConstant = 0.8;
+ source.connect(analyser);
+ freqData = new Uint8Array(analyser.frequencyBinCount);
+ timeData = new Uint8Array(analyser.fftSize);
+ running = true;
+ overlay.classList.add("hidden");
+ liveDot.classList.add("live");
+ startBtn.textContent = "🎧 Listening";
+ if (audioCtx.state === "suspended") audioCtx.resume();
+ loop();
+ }).catch(function (err) {
+ startBtn.disabled = false;
+ startBtn.textContent = "🎤 Start";
+ if (err && (err.name === "NotAllowedError" || err.name === "PermissionDeniedError" || err.name === "SecurityError")) {
+ showError("Microphone access denied",
+ "No worries — nothing is recorded either way. To use the visualizer, allow microphone access in your browser's site settings (or the frame's permission prompt) and click Start again.");
+ } else if (err && err.name === "NotFoundError") {
+ showError("No microphone found",
+ "We couldn't find an audio input device. Plug in or enable a microphone and try again.");
+ } else {
+ showError("Couldn't start the microphone",
+ (err && err.message) ? err.message : "An unexpected error occurred while opening the audio input.");
+ }
+ });
+ });
+
+ function drawIdle() {
+ var w = barsCanvas.width, h = barsCanvas.height;
+ barsCtx.clearRect(0, 0, w, h);
+ waveCtx.clearRect(0, 0, waveCanvas.width, waveCanvas.height);
+ }
+
+ function loop() {
+ if (!running) return;
+ rafId = requestAnimationFrame(loop);
+ analyser.getByteFrequencyData(freqData);
+ analyser.getByteTimeDomainData(timeData);
+ drawBars();
+ drawWave();
+ }
+
+ function drawBars() {
+ var w = barsCanvas.width, h = barsCanvas.height;
+ barsCtx.clearRect(0, 0, w, h);
+
+ var accent = cssVar("--accent") || "#4dd0e1";
+ var accent2 = cssVar("--accent2") || "#b388ff";
+ var grad = barsCtx.createLinearGradient(0, h, 0, 0);
+ grad.addColorStop(0, accent);
+ grad.addColorStop(1, accent2);
+
+ var barCount = 64;
+ // use lower ~2/3 of the spectrum where voice/music energy lives
+ var usable = Math.floor(freqData.length * 0.66);
+ var step = usable / barCount;
+ var gap = Math.max(1, w * 0.004);
+ var barW = (w - gap * (barCount + 1)) / barCount;
+
+ barsCtx.fillStyle = grad;
+ for (var i = 0; i < barCount; i++) {
+ var start = Math.floor(i * step), end = Math.floor((i + 1) * step), sum = 0;
+ for (var j = start; j < end; j++) sum += freqData[j];
+ var v = sum / Math.max(1, end - start) / 255;
+ var barH = Math.max(2, v * (h - 12));
+ var x = gap + i * (barW + gap);
+ var y = h - barH;
+ var r = Math.min(barW / 2, 6);
+ barsCtx.beginPath();
+ barsCtx.moveTo(x, h);
+ barsCtx.lineTo(x, y + r);
+ barsCtx.arcTo(x, y, x + r, y, r);
+ barsCtx.lineTo(x + barW - r, y);
+ barsCtx.arcTo(x + barW, y, x + barW, y + r, r);
+ barsCtx.lineTo(x + barW, h);
+ barsCtx.closePath();
+ barsCtx.fill();
+ }
+ }
+
+ function drawWave() {
+ var w = waveCanvas.width, h = waveCanvas.height;
+ waveCtx.clearRect(0, 0, w, h);
+
+ var accent = cssVar("--accent") || "#4dd0e1";
+ // center reference line
+ waveCtx.strokeStyle = "rgba(255,255,255,0.08)";
+ waveCtx.lineWidth = 1;
+ waveCtx.beginPath();
+ waveCtx.moveTo(0, h / 2);
+ waveCtx.lineTo(w, h / 2);
+ waveCtx.stroke();
+
+ waveCtx.strokeStyle = accent;
+ waveCtx.lineWidth = Math.max(1.5, (window.devicePixelRatio || 1) * 1.5);
+ waveCtx.lineJoin = "round";
+ waveCtx.beginPath();
+ var slice = w / timeData.length;
+ for (var i = 0; i < timeData.length; i++) {
+ var v = timeData[i] / 128.0; // 0..2, 1 = silence
+ var y = (v * h) / 2;
+ var x = i * slice;
+ if (i === 0) waveCtx.moveTo(x, y);
+ else waveCtx.lineTo(x, y);
+ }
+ waveCtx.stroke();
+
+ // soft glow pass
+ waveCtx.globalAlpha = 0.25;
+ waveCtx.lineWidth *= 3;
+ waveCtx.stroke();
+ waveCtx.globalAlpha = 1;
+ }
+
+ drawIdle();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1fd68f0d37b7/claude-code.png b/data/artifacts/1fd68f0d37b7/claude-code.png
new file mode 100644
index 0000000..e2e077c
Binary files /dev/null and b/data/artifacts/1fd68f0d37b7/claude-code.png differ
diff --git a/data/artifacts/1fd68f0d37b7/gemma3-12b.html b/data/artifacts/1fd68f0d37b7/gemma3-12b.html
new file mode 100644
index 0000000..b5dd39e
--- /dev/null
+++ b/data/artifacts/1fd68f0d37b7/gemma3-12b.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Microphone Audio Visualizer</title>
+ <style>
+ body { margin: 0; overflow: hidden; background-color: #222; color: #eee; font-family: sans-serif; }
+ #container { width: 100vw; height: 100vh; display: flex; flex-direction: column; }
+ #controls { padding: 10px; background-color: rgba(34, 34, 34, 0.8); }
+ button { margin-right: 10px; padding: 5px 10px; border: none; outline: none; color: #fff; background-color: #444; cursor: pointer; }
+ #canvas { width: 100vw; height: calc(100vh - 40px); } /* Adjust for controls */
+
+ </style>
+</head>
+<body>
+ <div id="container">
+ <div id="controls">
+ <button id="toggleTheme">Light Theme</button>
+ </div>
+ <canvas id="canvas"></canvas>
+ </div>
+
+ <script>
+ const canvas = document.getElementById('canvas');
+ const ctx = canvas.getContext('2d');
+ const toggleThemeButton = document.getElementById('toggleTheme');
+
+ let analyser;
+ let audioContext;
+ let microphone;
+ let isRecording = false;
+ let themeLight = true;
+
+
+ function requestMicrophonePermission() {
+ return navigator.mediaDevices.getUserMedia({ audio: true })
+ .then(stream => {
+ microphone = stream;
+ return stream;
+ })
+ .catch(err => {
+ alert("Microphone permission denied. Please check your browser settings.");
+ console.error("Error accessing microphone:", err);
+ return Promise.reject(err); // Reject the promise to prevent further execution
+ });
+ }
+
+
+ function init() {
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
+
+ requestMicrophonePermission()
+ .then(stream => {
+ const source = audioContext.createMediaStreamSource(stream);
+ analyser = audioContext.createAnalyser();
+ source.connect(analyser);
+ analyser.fftSize = 256; // Adjust FFT size for resolution
+
+ drawWaveform();
+ drawFrequencyBars();
+
+ })
+ .catch(error => { console.error("Initialization failed:", error); });
+ }
+
+
+ function drawWaveform() {
+ const bufferLength = analyser.frequencyBinCount;
+ const dataArray = new Uint8Array(bufferLength);
+
+ analyser.getByteTimeDomainData(dataArray);
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas before drawing
+
+ ctx.beginPath();
+ let x = 0;
+ let y = canvas.height / 2; // Center vertically
+ ctx.moveTo(x, y - dataArray[0]);
+
+ for (let i = 1; i < bufferLength; i++) {
+ x += 1;
+ y = canvas.height / 2;
+ const value = dataArray[i];
+ ctx.lineTo(x, y - value);
+ }
+
+ ctx.strokeStyle = themeLight ? '#fff' : '#000';
+ ctx.lineWidth = 1; // Adjust line width as needed
+ ctx.stroke();
+ }
+
+
+ function drawFrequencyBars() {
+ const bufferLength = analyser.frequencyBinCount;
+ const dataArray = new Uint8Array(bufferLength);
+
+ analyser.getByteFrequencyData(dataArray);
+
+ const barWidth = canvas.width / bufferLength;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas before drawing
+
+ for (let i = 0; i < bufferLength; i++) {
+ const height = dataArray[i];
+ const x = i * barWidth;
+ const y = canvas.height - height; // Invert for vertical bars
+ const width = barWidth;
+ const barHeight = y;
+
+ ctx.fillStyle = themeLight ? 'rgba(0, 255, 255, 0.5)' : 'rgba(173, 216, 230, 0.5)'; // Light blue or gray
+ ctx.fillRect(x, y, width, barHeight);
+
+ }
+ }
+
+
+ toggleThemeButton.addEventListener('click', () => {
+ themeLight = !themeLight;
+ document.body.style.backgroundColor = themeLight ? '#222' : '#333';
+ toggleThemeButton.textContent = themeLight ? 'Light Theme' : 'Dark Theme';
+
+ drawWaveform(); // Redraw waveforms with updated color
+ drawFrequencyBars(); //Redraw frequency bars with updated colors
+ });
+
+
+
+ init();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1fd68f0d37b7/gemma3-12b.png b/data/artifacts/1fd68f0d37b7/gemma3-12b.png
new file mode 100644
index 0000000..94ceb52
Binary files /dev/null and b/data/artifacts/1fd68f0d37b7/gemma3-12b.png differ
diff --git a/data/artifacts/1fd68f0d37b7/gpt.html b/data/artifacts/1fd68f0d37b7/gpt.html
new file mode 100644
index 0000000..d35ae05
--- /dev/null
+++ b/data/artifacts/1fd68f0d37b7/gpt.html
@@ -0,0 +1,541 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>Microphone Audio Visualizer</title>
+<style>
+ :root {
+ --bg: #050709;
+ --fg: #f5f7fa;
+ --accent: #4fc3f7;
+ --accent2: #ff6f61;
+ --muted: #888;
+ --canvas-bg: #050709;
+ }
+ body.light {
+ --bg: #f5f7fa;
+ --fg: #111827;
+ --accent: #2563eb;
+ --accent2: #f97316;
+ --muted: #6b7280;
+ --canvas-bg: #e5e7eb;
+ }
+ * {
+ box-sizing: border-box;
+ }
+ html, body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ background: radial-gradient(circle at top, #111 0, var(--bg) 55%);
+ color: var(--fg);
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ }
+ body {
+ display: flex;
+ align-items: stretch;
+ justify-content: center;
+ }
+ .app {
+ width: 100%;
+ max-width: 960px;
+ margin: 0 auto;
+ padding: 16px 16px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+ header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ }
+ .title {
+ display: flex;
+ flex-direction: column;
+ }
+ h1 {
+ margin: 0;
+ font-size: 1.2rem;
+ letter-spacing: 0.03em;
+ text-transform: uppercase;
+ }
+ .subtitle {
+ font-size: 0.75rem;
+ color: var(--muted);
+ margin-top: 2px;
+ }
+ .controls {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-wrap: wrap;
+ }
+ button {
+ border: none;
+ border-radius: 999px;
+ padding: 6px 14px;
+ font-size: 0.8rem;
+ cursor: pointer;
+ background: rgba(255,255,255,0.06);
+ color: var(--fg);
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ backdrop-filter: blur(6px);
+ transition: background 0.2s, transform 0.1s, box-shadow 0.2s;
+ box-shadow: 0 0 0 rgba(0,0,0,0);
+ }
+ button:hover {
+ background: rgba(255,255,255,0.12);
+ }
+ button:active {
+ transform: translateY(1px) scale(0.99);
+ box-shadow: 0 0 0 rgba(0,0,0,0);
+ }
+ button.primary {
+ background: linear-gradient(120deg, var(--accent), var(--accent2));
+ color: #fff;
+ font-weight: 600;
+ box-shadow: 0 7px 18px rgba(0,0,0,0.35);
+ }
+ button.primary:hover {
+ filter: brightness(1.05);
+ }
+ .pill {
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.08);
+ padding: 4px 8px;
+ font-size: 0.7rem;
+ color: var(--muted);
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ }
+ .pill-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 999px;
+ background: #f97316;
+ box-shadow: 0 0 6px rgba(249,115,22,0.9);
+ }
+ .status {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 0.8rem;
+ color: var(--muted);
+ }
+ .status-indicator {
+ width: 9px;
+ height: 9px;
+ border-radius: 999px;
+ background: #fbbf24;
+ box-shadow: 0 0 4px rgba(251,191,36,0.8);
+ }
+ .status-indicator.live {
+ background: #22c55e;
+ box-shadow: 0 0 8px rgba(34,197,94,0.9);
+ }
+ .status-indicator.error {
+ background: #ef4444;
+ box-shadow: 0 0 8px rgba(239,68,68,0.8);
+ }
+ main {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ }
+ .canvas-shell {
+ position: relative;
+ border-radius: 18px;
+ overflow: hidden;
+ background: radial-gradient(circle at top left, rgba(79,195,247,0.25), transparent 55%),
+ radial-gradient(circle at bottom right, rgba(244,114,182,0.18), transparent 55%),
+ var(--canvas-bg);
+ box-shadow:
+ 0 12px 40px rgba(0,0,0,0.65),
+ 0 0 0 1px rgba(255,255,255,0.04);
+ min-height: 260px;
+ }
+ canvas {
+ width: 100%;
+ height: 280px;
+ display: block;
+ }
+ .overlay {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ pointer-events: none;
+ text-align: center;
+ padding: 24px;
+ }
+ .overlay.hidden {
+ display: none;
+ }
+ .overlay h2 {
+ margin: 0 0 8px;
+ font-size: 1rem;
+ }
+ .overlay p {
+ margin: 0;
+ font-size: 0.8rem;
+ color: var(--muted);
+ }
+ .overlay .hint {
+ margin-top: 10px;
+ font-size: 0.75rem;
+ color: var(--muted);
+ }
+ .chip-row {
+ display: flex;
+ gap: 6px;
+ margin-top: 12px;
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+ .chip {
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.15);
+ padding: 3px 9px;
+ font-size: 0.7rem;
+ color: var(--muted);
+ backdrop-filter: blur(4px);
+ background: rgba(15,23,42,0.4);
+ }
+ footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 0.7rem;
+ color: var(--muted);
+ gap: 8px;
+ flex-wrap: wrap;
+ }
+ .meters {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+ .vu-shell {
+ width: 80px;
+ height: 6px;
+ border-radius: 999px;
+ background: rgba(15,23,42,0.8);
+ overflow: hidden;
+ }
+ .vu-fill {
+ height: 100%;
+ width: 0%;
+ border-radius: inherit;
+ background: linear-gradient(90deg, #22c55e, #eab308, #ef4444);
+ transition: width 0.05s linear;
+ }
+ .theme-toggle {
+ font-size: 0.75rem;
+ }
+ @media (max-width: 640px) {
+ h1 {
+ font-size: 1rem;
+ }
+ canvas {
+ height: 220px;
+ }
+ .canvas-shell {
+ min-height: 220px;
+ }
+ }
+</style>
+</head>
+<body>
+<div class="app">
+ <header>
+ <div class="title">
+ <h1>Mic Audio Visualizer</h1>
+ <div class="subtitle">Live frequency bars + waveform from your microphone</div>
+ </div>
+ <div class="controls">
+ <button id="micBtn" class="primary" type="button">
+ <span id="micBtnIcon">🎤</span>
+ <span id="micBtnLabel">Enable mic</span>
+ </button>
+ <button id="themeToggle" class="theme-toggle" type="button" aria-label="Toggle color theme">
+ 🌙 Theme
+ </button>
+ <span class="pill">
+ <span class="pill-dot"></span>
+ Runs fully offline · No recording
+ </span>
+ </div>
+ </header>
+
+ <main>
+ <div class="canvas-shell">
+ <canvas id="visualizer" aria-label="Microphone audio visualizer"></canvas>
+ <div id="overlayIdle" class="overlay">
+ <h2>Waiting for microphone permission</h2>
+ <p>Click “Enable mic” above. Your audio never leaves this page.</p>
+ <div class="chip-row">
+ <span class="chip">Frequency spectrum</span>
+ <span class="chip">Waveform</span>
+ <span class="chip">Live VU meter</span>
+ </div>
+ <div class="hint">If you don’t see a prompt, check your browser’s address bar for a mic icon.</div>
+ </div>
+ <div id="overlayError" class="overlay hidden">
+ <h2>Microphone unavailable</h2>
+ <p id="errorText">Access to the microphone was denied.</p>
+ <div class="hint">Allow mic access in your browser settings, then reload this page.</div>
+ </div>
+ </div>
+ </main>
+
+ <footer>
+ <div class="status">
+ <div id="statusIndicator" class="status-indicator"></div>
+ <span id="statusText">Idle · awaiting permission</span>
+ </div>
+ <div class="meters">
+ <span>Input level</span>
+ <div class="vu-shell" aria-hidden="true">
+ <div id="vuFill" class="vu-fill"></div>
+ </div>
+ </div>
+ </footer>
+</div>
+
+<script>
+(function() {
+ const micBtn = document.getElementById('micBtn');
+ const micBtnLabel = document.getElementById('micBtnLabel');
+ const micBtnIcon = document.getElementById('micBtnIcon');
+ const themeToggle = document.getElementById('themeToggle');
+ const statusIndicator = document.getElementById('statusIndicator');
+ const statusText = document.getElementById('statusText');
+ const vuFill = document.getElementById('vuFill');
+ const canvas = document.getElementById('visualizer');
+ const overlayIdle = document.getElementById('overlayIdle');
+ const overlayError = document.getElementById('overlayError');
+ const errorText = document.getElementById('errorText');
+ const body = document.body;
+
+ let audioCtx = null;
+ let analyser = null;
+ let waveformData = null;
+ let freqData = null;
+ let sourceNode = null;
+ let animationId = null;
+ let micEnabled = false;
+
+ function resizeCanvas() {
+ const dpr = window.devicePixelRatio || 1;
+ const rect = canvas.getBoundingClientRect();
+ canvas.width = rect.width * dpr;
+ canvas.height = rect.height * dpr;
+ }
+
+ window.addEventListener('resize', resizeCanvas);
+
+ function updateStatus(mode, message) {
+ statusIndicator.classList.remove('live', 'error');
+ if (mode === 'live') {
+ statusIndicator.classList.add('live');
+ } else if (mode === 'error') {
+ statusIndicator.classList.add('error');
+ }
+ statusText.textContent = message;
+ }
+
+ function setOverlay(state, errorMsg) {
+ if (state === 'idle') {
+ overlayIdle.classList.remove('hidden');
+ overlayError.classList.add('hidden');
+ } else if (state === 'error') {
+ overlayIdle.classList.add('hidden');
+ overlayError.classList.remove('hidden');
+ if (errorMsg) errorText.textContent = errorMsg;
+ } else if (state === 'none') {
+ overlayIdle.classList.add('hidden');
+ overlayError.classList.add('hidden');
+ }
+ }
+
+ function stopAudio() {
+ if (animationId) {
+ cancelAnimationFrame(animationId);
+ animationId = null;
+ }
+ if (sourceNode && sourceNode.mediaStream) {
+ sourceNode.mediaStream.getTracks().forEach(t => t.stop());
+ }
+ if (audioCtx) {
+ audioCtx.close().catch(() => {});
+ }
+ audioCtx = null;
+ analyser = null;
+ waveformData = null;
+ freqData = null;
+ sourceNode = null;
+ micEnabled = false;
+ }
+
+ async function initAudio() {
+ if (micEnabled) return;
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
+ setOverlay('error', 'getUserMedia is not supported in this browser.');
+ updateStatus('error', 'Unsupported browser: no microphone API');
+ return;
+ }
+ try {
+ updateStatus('pending', 'Requesting microphone access…');
+ micBtn.disabled = true;
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
+
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
+ analyser = audioCtx.createAnalyser();
+ analyser.fftSize = 2048;
+ analyser.smoothingTimeConstant = 0.8;
+
+ waveformData = new Uint8Array(analyser.fftSize);
+ freqData = new Uint8Array(analyser.frequencyBinCount);
+
+ sourceNode = audioCtx.createMediaStreamSource(stream);
+ sourceNode.connect(analyser);
+
+ micEnabled = true;
+ setOverlay('none');
+ updateStatus('live', 'Live · microphone streaming');
+ micBtnLabel.textContent = 'Mic on';
+ micBtnIcon.textContent = '✅';
+ micBtn.disabled = false;
+
+ resizeCanvas();
+ draw();
+ } catch (err) {
+ console.error(err);
+ let msg = 'Access to the microphone was denied.';
+ if (err && err.name === 'NotFoundError') {
+ msg = 'No microphone was found on this device.';
+ } else if (err && err.name === 'NotAllowedError') {
+ msg = 'Microphone permission was blocked by the browser.';
+ }
+ setOverlay('error', msg);
+ updateStatus('error', 'Error · ' + msg.replace(/\.$/, ''));
+ micBtn.disabled = false;
+ }
+ }
+
+ function draw() {
+ if (!analyser) return;
+ const ctx = canvas.getContext('2d');
+ const width = canvas.width;
+ const height = canvas.height;
+ const dpr = window.devicePixelRatio || 1;
+
+ analyser.getByteTimeDomainData(waveformData);
+ analyser.getByteFrequencyData(freqData);
+
+ let sumSquares = 0;
+ for (let i = 0; i < waveformData.length; i++) {
+ const v = (waveformData[i] - 128) / 128;
+ sumSquares += v * v;
+ }
+ const rms = Math.sqrt(sumSquares / waveformData.length);
+ const vuPct = Math.min(100, rms * 220);
+ vuFill.style.width = vuPct.toFixed(1) + '%';
+
+ ctx.clearRect(0, 0, width, height);
+
+ const gradBg = ctx.createLinearGradient(0, 0, 0, height);
+ gradBg.addColorStop(0, 'rgba(15,23,42,0.8)');
+ gradBg.addColorStop(1, 'rgba(15,23,42,0.2)');
+ ctx.fillStyle = gradBg;
+ ctx.fillRect(0, 0, width, height);
+
+ const barCount = Math.floor(width / (8 * dpr));
+ const slice = Math.floor(freqData.length / barCount) || 1;
+ const barWidth = (width / barCount) * 0.7;
+ for (let i = 0; i < barCount; i++) {
+ let sum = 0;
+ for (let j = 0; j < slice; j++) {
+ const idx = i * slice + j;
+ if (idx < freqData.length) sum += freqData[idx];
+ }
+ const value = sum / slice / 255;
+ const barHeight = value * height * 0.55;
+ const x = i * (width / barCount);
+ const y = height - barHeight - 2 * dpr;
+
+ const g = ctx.createLinearGradient(0, y, 0, y + barHeight);
+ g.addColorStop(0, 'rgba(79,195,247,0.95)');
+ g.addColorStop(0.5, 'rgba(96,165,250,0.85)');
+ g.addColorStop(1, 'rgba(248,113,113,0.8)');
+ ctx.fillStyle = g;
+ ctx.beginPath();
+ ctx.roundRect(x, y, barWidth, barHeight, 3 * dpr);
+ ctx.fill();
+ }
+
+ ctx.lineWidth = 2 * dpr;
+ const wfGrad = ctx.createLinearGradient(0, 0, width, 0);
+ wfGrad.addColorStop(0, 'rgba(56,189,248,1)');
+ wfGrad.addColorStop(0.5, 'rgba(244,114,182,1)');
+ wfGrad.addColorStop(1, 'rgba(251,191,36,1)');
+ ctx.strokeStyle = wfGrad;
+ ctx.beginPath();
+ const step = Math.max(1, Math.floor(waveformData.length / width));
+ const midY = height * 0.42;
+ const amp = height * 0.32;
+ for (let i = 0, x = 0; i < waveformData.length && x <= width; i += step, x += dpr) {
+ const v = (waveformData[i] - 128) / 128;
+ const y = midY + v * amp;
+ if (i === 0) ctx.moveTo(x, y);
+ else ctx.lineTo(x, y);
+ }
+ ctx.stroke();
+
+ ctx.save();
+ ctx.globalAlpha = 0.12;
+ ctx.lineWidth = 1 * dpr;
+ ctx.beginPath();
+ const centerX = width / 2;
+ ctx.moveTo(centerX, 0);
+ ctx.lineTo(centerX, height);
+ ctx.strokeStyle = '#64748b';
+ ctx.stroke();
+ ctx.restore();
+
+ animationId = requestAnimationFrame(draw);
+ }
+
+ micBtn.addEventListener('click', initAudio);
+
+ themeToggle.addEventListener('click', () => {
+ const light = body.classList.toggle('light');
+ themeToggle.textContent = light ? '☀️ Theme' : '🌙 Theme';
+ });
+
+ document.addEventListener('visibilitychange', () => {
+ if (document.visibilityState === 'hidden') {
+ // keep stream alive but pause animation
+ if (animationId) {
+ cancelAnimationFrame(animationId);
+ animationId = null;
+ }
+ } else if (micEnabled && !animationId) {
+ resizeCanvas();
+ draw();
+ }
+ });
+
+ resizeCanvas();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1fd68f0d37b7/gpt.png b/data/artifacts/1fd68f0d37b7/gpt.png
new file mode 100644
index 0000000..c979a59
Binary files /dev/null and b/data/artifacts/1fd68f0d37b7/gpt.png differ
diff --git a/data/artifacts/1fd68f0d37b7/grok.html b/data/artifacts/1fd68f0d37b7/grok.html
new file mode 100644
index 0000000..96fcd20
--- /dev/null
+++ b/data/artifacts/1fd68f0d37b7/grok.html
@@ -0,0 +1,521 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Mic Audio Visualizer</title>
+<style>
+ :root, [data-theme="dark"] {
+ --bg: #0d1117;
+ --surface: #161b22;
+ --text: #e6edf3;
+ --muted: #8b949e;
+ --accent: #58a6ff;
+ --bar-hue-start: 200;
+ --wave: #58a6ff;
+ --grid: rgba(88, 166, 255, 0.08);
+ --btn-bg: #21262d;
+ --btn-hover: #30363d;
+ --border: #30363d;
+ --error: #f85149;
+ }
+ [data-theme="light"] {
+ --bg: #f6f8fa;
+ --surface: #ffffff;
+ --text: #1f2328;
+ --muted: #656d76;
+ --accent: #0969da;
+ --bar-hue-start: 210;
+ --wave: #0969da;
+ --grid: rgba(9, 105, 218, 0.08);
+ --btn-bg: #f3f4f6;
+ --btn-hover: #eaeef2;
+ --border: #d0d7de;
+ --error: #cf222e;
+ }
+ [data-theme="neon"] {
+ --bg: #0a0014;
+ --surface: #12001f;
+ --text: #f0e6ff;
+ --muted: #a78bfa;
+ --accent: #ff00aa;
+ --bar-hue-start: 280;
+ --wave: #00ffcc;
+ --grid: rgba(255, 0, 170, 0.1);
+ --btn-bg: #1a0030;
+ --btn-hover: #2a0050;
+ --border: #4a0080;
+ --error: #ff4466;
+ }
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ html, body {
+ height: 100%;
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
+ background: var(--bg);
+ color: var(--text);
+ transition: background 0.3s, color 0.3s;
+ }
+ body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 100%;
+ padding: 16px;
+ }
+ .app {
+ width: min(920px, 100%);
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ }
+ header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ flex-wrap: wrap;
+ }
+ h1 {
+ font-size: 1.25rem;
+ font-weight: 600;
+ letter-spacing: -0.02em;
+ }
+ .controls {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ flex-wrap: wrap;
+ }
+ button {
+ appearance: none;
+ border: 1px solid var(--border);
+ background: var(--btn-bg);
+ color: var(--text);
+ padding: 8px 14px;
+ border-radius: 8px;
+ font-size: 0.875rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 0.15s, border-color 0.15s, transform 0.1s;
+ }
+ button:hover { background: var(--btn-hover); }
+ button:active { transform: scale(0.97); }
+ button.primary {
+ background: var(--accent);
+ border-color: transparent;
+ color: #fff;
+ }
+ [data-theme="light"] button.primary { color: #fff; }
+ [data-theme="neon"] button.primary {
+ background: linear-gradient(135deg, #ff00aa, #7c3aed);
+ color: #fff;
+ }
+ button:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ transform: none;
+ }
+ .stage {
+ position: relative;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: 16px;
+ overflow: hidden;
+ aspect-ratio: 16 / 9;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.2);
+ }
+ canvas {
+ display: block;
+ width: 100%;
+ height: 100%;
+ }
+ .overlay {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+ padding: 24px;
+ text-align: center;
+ background: color-mix(in srgb, var(--surface) 92%, transparent);
+ backdrop-filter: blur(6px);
+ transition: opacity 0.3s, visibility 0.3s;
+ }
+ .overlay.hidden {
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ }
+ .overlay p {
+ color: var(--muted);
+ font-size: 0.95rem;
+ max-width: 360px;
+ line-height: 1.5;
+ }
+ .overlay .icon {
+ font-size: 2.5rem;
+ line-height: 1;
+ margin-bottom: 4px;
+ }
+ .overlay.error .icon { filter: grayscale(0); }
+ .status {
+ font-size: 0.8rem;
+ color: var(--muted);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ .dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--muted);
+ flex-shrink: 0;
+ }
+ .dot.live {
+ background: #3fb950;
+ box-shadow: 0 0 0 0 rgba(63, 185, 80, 0.5);
+ animation: pulse 1.5s infinite;
+ }
+ @keyframes pulse {
+ 0% { box-shadow: 0 0 0 0 rgba(63, 185, 80, 0.45); }
+ 70% { box-shadow: 0 0 0 8px rgba(63, 185, 80, 0); }
+ 100% { box-shadow: 0 0 0 0 rgba(63, 185, 80, 0); }
+ }
+ .theme-label { font-size: 0.8rem; color: var(--muted); }
+</style>
+</head>
+<body>
+ <div class="app">
+ <header>
+ <h1>Microphone Visualizer</h1>
+ <div class="controls">
+ <span class="theme-label">Theme</span>
+ <button type="button" id="themeBtn" title="Cycle color theme">Dark</button>
+ <button type="button" id="startBtn" class="primary">Start Microphone</button>
+ <button type="button" id="stopBtn" disabled>Stop</button>
+ </div>
+ </header>
+ <div class="stage">
+ <canvas id="canvas"></canvas>
+ <div class="overlay" id="overlay">
+ <div class="icon" id="overlayIcon">🎙️</div>
+ <p id="overlayMsg">Click “Start Microphone” and allow access to see a live frequency-bar and waveform visualization.</p>
+ </div>
+ </div>
+ <div class="status">
+ <span class="dot" id="dot"></span>
+ <span id="statusText">Idle</span>
+ </div>
+ </div>
+<script>
+(function () {
+ const themes = ['dark', 'light', 'neon'];
+ const themeLabels = { dark: 'Dark', light: 'Light', neon: 'Neon' };
+ let themeIndex = 0;
+
+ const canvas = document.getElementById('canvas');
+ const ctx = canvas.getContext('2d');
+ const overlay = document.getElementById('overlay');
+ const overlayMsg = document.getElementById('overlayMsg');
+ const overlayIcon = document.getElementById('overlayIcon');
+ const startBtn = document.getElementById('startBtn');
+ const stopBtn = document.getElementById('stopBtn');
+ const themeBtn = document.getElementById('themeBtn');
+ const statusText = document.getElementById('statusText');
+ const dot = document.getElementById('dot');
+
+ let audioCtx = null;
+ let analyser = null;
+ let micStream = null;
+ let sourceNode = null;
+ let rafId = null;
+ let freqData = null;
+ let timeData = null;
+ let running = false;
+
+ function cssVar(name) {
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
+ }
+
+ function applyTheme(name) {
+ document.documentElement.setAttribute('data-theme', name);
+ themeBtn.textContent = themeLabels[name];
+ }
+
+ themeBtn.addEventListener('click', function () {
+ themeIndex = (themeIndex + 1) % themes.length;
+ applyTheme(themes[themeIndex]);
+ });
+ applyTheme(themes[0]);
+
+ function resize() {
+ const rect = canvas.parentElement.getBoundingClientRect();
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
+ canvas.width = Math.floor(rect.width * dpr);
+ canvas.height = Math.floor(rect.height * dpr);
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ }
+ window.addEventListener('resize', resize);
+ resize();
+
+ function setStatus(text, live) {
+ statusText.textContent = text;
+ dot.classList.toggle('live', !!live);
+ }
+
+ function showOverlay(msg, isError) {
+ overlayMsg.textContent = msg;
+ overlayIcon.textContent = isError ? '🚫' : '🎙️';
+ overlay.classList.toggle('error', !!isError);
+ overlay.classList.remove('hidden');
+ }
+
+ function hideOverlay() {
+ overlay.classList.add('hidden');
+ }
+
+ function drawIdle() {
+ const w = canvas.clientWidth;
+ const h = canvas.clientHeight;
+ ctx.clearRect(0, 0, w, h);
+ const bg = cssVar('--surface') || '#161b22';
+ ctx.fillStyle = bg;
+ ctx.fillRect(0, 0, w, h);
+ const grid = cssVar('--grid');
+ ctx.strokeStyle = grid;
+ ctx.lineWidth = 1;
+ const step = 40;
+ ctx.beginPath();
+ for (let x = 0; x <= w; x += step) {
+ ctx.moveTo(x + 0.5, 0);
+ ctx.lineTo(x + 0.5, h);
+ }
+ for (let y = 0; y <= h; y += step) {
+ ctx.moveTo(0, y + 0.5);
+ ctx.lineTo(w, y + 0.5);
+ }
+ ctx.stroke();
+ }
+
+ function draw() {
+ if (!running || !analyser) return;
+ const w = canvas.clientWidth;
+ const h = canvas.clientHeight;
+
+ analyser.getByteFrequencyData(freqData);
+ analyser.getByteTimeDomainData(timeData);
+
+ const bg = cssVar('--bg') || '#0d1117';
+ ctx.fillStyle = bg;
+ ctx.fillRect(0, 0, w, h);
+
+ const grid = cssVar('--grid');
+ ctx.strokeStyle = grid;
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ const midY = h * 0.42;
+ ctx.moveTo(0, midY + 0.5);
+ ctx.lineTo(w, midY + 0.5);
+ ctx.stroke();
+
+ const barRegionTop = h * 0.52;
+ const barRegionH = h * 0.42;
+ const binCount = freqData.length;
+ const usable = Math.floor(binCount * 0.75);
+ const barCount = Math.min(96, usable);
+ const gap = 2;
+ const totalGap = gap * (barCount - 1);
+ const barW = Math.max(2, (w - totalGap) / barCount);
+ const hueStart = parseFloat(cssVar('--bar-hue-start')) || 200;
+ const theme = document.documentElement.getAttribute('data-theme');
+
+ for (let i = 0; i < barCount; i++) {
+ const dataIndex = Math.floor(i * (usable / barCount));
+ const v = freqData[dataIndex] / 255;
+ const barH = Math.max(2, v * barRegionH);
+ const x = i * (barW + gap);
+ const y = barRegionTop + barRegionH - barH;
+
+ let grad;
+ if (theme === 'neon') {
+ grad = ctx.createLinearGradient(x, y + barH, x, y);
+ grad.addColorStop(0, 'hsla(' + (hueStart + i * 1.2) + ',100%,50%,0.3)');
+ grad.addColorStop(0.5, 'hsla(' + (hueStart + i * 1.2) + ',100%,55%,0.85)');
+ grad.addColorStop(1, 'hsla(' + ((hueStart + 60 + i) % 360) + ',100%,70%,1)');
+ } else if (theme === 'light') {
+ grad = ctx.createLinearGradient(x, y + barH, x, y);
+ grad.addColorStop(0, 'hsla(' + (hueStart + i * 0.8) + ',70%,55%,0.35)');
+ grad.addColorStop(1, 'hsla(' + (hueStart + i * 0.8) + ',80%,40%,0.95)');
+ } else {
+ grad = ctx.createLinearGradient(x, y + barH, x, y);
+ grad.addColorStop(0, 'hsla(' + (hueStart + i * 0.9) + ',85%,45%,0.25)');
+ grad.addColorStop(0.6, 'hsla(' + (hueStart + i * 0.9) + ',90%,55%,0.85)');
+ grad.addColorStop(1, 'hsla(' + ((hueStart + 40 + i * 0.5) % 360) + ',95%,65%,1)');
+ }
+ ctx.fillStyle = grad;
+ const radius = Math.min(4, barW / 2);
+ roundRect(ctx, x, y, barW, barH, radius);
+ ctx.fill();
+ }
+
+ // Waveform
+ const waveColor = cssVar('--wave') || '#58a6ff';
+ ctx.beginPath();
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = waveColor;
+ ctx.shadowColor = waveColor;
+ ctx.shadowBlur = theme === 'neon' ? 12 : 0;
+
+ const slice = w / timeData.length;
+ const waveH = h * 0.35;
+ const waveMid = h * 0.28;
+
+ for (let i = 0; i < timeData.length; i++) {
+ const v = (timeData[i] - 128) / 128;
+ const x = i * slice;
+ const y = waveMid + v * waveH;
+ if (i === 0) ctx.moveTo(x, y);
+ else ctx.lineTo(x, y);
+ }
+ ctx.stroke();
+ ctx.shadowBlur = 0;
+
+ // Soft fill under waveform
+ ctx.lineTo(w, waveMid);
+ ctx.lineTo(0, waveMid);
+ ctx.closePath();
+ const fillGrad = ctx.createLinearGradient(0, waveMid - waveH, 0, waveMid + waveH);
+ if (theme === 'neon') {
+ fillGrad.addColorStop(0, 'rgba(0,255,204,0.12)');
+ fillGrad.addColorStop(1, 'rgba(255,0,170,0.05)');
+ } else if (theme === 'light') {
+ fillGrad.addColorStop(0, 'rgba(9,105,218,0.1)');
+ fillGrad.addColorStop(1, 'rgba(9,105,218,0.02)');
+ } else {
+ fillGrad.addColorStop(0, 'rgba(88,166,255,0.12)');
+ fillGrad.addColorStop(1, 'rgba(88,166,255,0.02)');
+ }
+ ctx.fillStyle = fillGrad;
+ ctx.fill();
+
+ rafId = requestAnimationFrame(draw);
+ }
+
+ function roundRect(c, x, y, w, h, r) {
+ r = Math.min(r, w / 2, h / 2);
+ c.beginPath();
+ c.moveTo(x + r, y);
+ c.arcTo(x + w, y, x + w, y + h, r);
+ c.arcTo(x + w, y + h, x, y + h, r);
+ c.arcTo(x, y + h, x, y, r);
+ c.arcTo(x, y, x + w, y, r);
+ c.closePath();
+ }
+
+ async function start() {
+ if (running) return;
+ startBtn.disabled = true;
+ setStatus('Requesting microphone…', false);
+
+ try {
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
+ throw new Error('getUserMedia not supported in this browser.');
+ }
+
+ micStream = await navigator.mediaDevices.getUserMedia({
+ audio: {
+ echoCancellation: false,
+ noiseSuppression: false,
+ autoGainControl: false
+ },
+ video: false
+ });
+
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
+ if (audioCtx.state === 'suspended') {
+ await audioCtx.resume();
+ }
+
+ analyser = audioCtx.createAnalyser();
+ analyser.fftSize = 2048;
+ analyser.smoothingTimeConstant = 0.78;
+ analyser.minDecibels = -90;
+ analyser.maxDecibels = -25;
+
+ sourceNode = audioCtx.createMediaStreamSource(micStream);
+ sourceNode.connect(analyser);
+
+ freqData = new Uint8Array(analyser.frequencyBinCount);
+ timeData = new Uint8Array(analyser.fftSize);
+
+ running = true;
+ hideOverlay();
+ stopBtn.disabled = false;
+ startBtn.disabled = true;
+ setStatus('Live — speak or play audio near your mic', true);
+ resize();
+ draw();
+ } catch (err) {
+ running = false;
+ startBtn.disabled = false;
+ stopBtn.disabled = true;
+ const name = err && err.name ? err.name : '';
+ let msg;
+ if (name === 'NotAllowedError' || name === 'PermissionDeniedError') {
+ msg = 'Microphone access was denied. Please allow microphone permission in your browser settings and try again.';
+ } else if (name === 'NotFoundError' || name === 'DevicesNotFoundError') {
+ msg = 'No microphone was found. Connect a mic and try again.';
+ } else if (name === 'NotReadableError') {
+ msg = 'The microphone is already in use by another application.';
+ } else {
+ msg = 'Could not access the microphone: ' + (err.message || String(err));
+ }
+ showOverlay(msg, true);
+ setStatus('Microphone unavailable', false);
+ drawIdle();
+ }
+ }
+
+ function stop() {
+ running = false;
+ if (rafId) {
+ cancelAnimationFrame(rafId);
+ rafId = null;
+ }
+ if (sourceNode) {
+ try { sourceNode.disconnect(); } catch (e) {}
+ sourceNode = null;
+ }
+ if (analyser) {
+ try { analyser.disconnect(); } catch (e) {}
+ analyser = null;
+ }
+ if (micStream) {
+ micStream.getTracks().forEach(function (t) { t.stop(); });
+ micStream = null;
+ }
+ if (audioCtx) {
+ audioCtx.close().catch(function () {});
+ audioCtx = null;
+ }
+ freqData = null;
+ timeData = null;
+ startBtn.disabled = false;
+ stopBtn.disabled = true;
+ setStatus('Stopped', false);
+ showOverlay('Microphone stopped. Click “Start Microphone” to visualize again.', false);
+ drawIdle();
+ }
+
+ startBtn.addEventListener('click', start);
+ stopBtn.addEventListener('click', stop);
+
+ drawIdle();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1fd68f0d37b7/grok.png b/data/artifacts/1fd68f0d37b7/grok.png
new file mode 100644
index 0000000..24f332d
Binary files /dev/null and b/data/artifacts/1fd68f0d37b7/grok.png differ
diff --git a/data/artifacts/1fd68f0d37b7/hermes3-8b.html b/data/artifacts/1fd68f0d37b7/hermes3-8b.html
new file mode 100644
index 0000000..bb2e7b4
--- /dev/null
+++ b/data/artifacts/1fd68f0d37b7/hermes3-8b.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Microphone Audio Visualizer</title>
+ <style>
+ body {
+ font-family: Arial, sans-serif;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ margin: 0;
+ }
+
+ canvas {
+ border: 1px solid #ccc;
+ background-color: #f0f0f0;
+ }
+
+ label {
+ margin-left: 10px;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="waveform" width="800" height="200"></canvas>
+ <label for="theme">Toggle Color Theme:</label>
+ <input type="checkbox" id="theme" checked>
+
+ <script>
+ const canvas = document.getElementById('waveform');
+ const ctx = canvas.getContext('2d');
+
+ let audioContext;
+ let sourceNode;
+ let analyserNode;
+
+ function startVisualization() {
+ try {
+ // Check if Web Audio API is supported
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
+
+ navigator.mediaDevices.getUserMedia({ audio: true })
+ .then(stream => {
+ sourceNode = audioContext.createMediaStreamSource(stream);
+ analyserNode = audioContext.createAnalyser();
+
+ // Connect the MediaStreamSource to the AnalyserNode
+ sourceNode.connect(analyserNode);
+ analyserNode.connect(audioContext.destination);
+
+ // Render loop
+ renderLoop();
+
+ // Toggle theme color on checkbox change
+ document.getElementById('theme').addEventListener('change', () => {
+ ctx.globalCompositeOperation = document.getElementById('theme').checked ? 'source-over' : 'destination-over';
+ });
+ })
+ .catch(err => {
+ console.error("Permission denied to access microphone", err);
+ displayError("Access to microphone was denied. Please allow it in your browser settings.");
+ });
+ } catch (err) {
+ console.error('Web Audio API not supported in this browser', err);
+ displayError("Your browser doesn't support Web Audio API. Try updating to a newer version.");
+ }
+ }
+
+ function renderLoop() {
+ requestAnimationFrame(renderLoop);
+
+ let dataArray = new Array(800);
+
+ // Get the frequency data
+ analyserNode.getByteFrequencyData(dataArray);
+
+ // Clear canvas
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ // Draw waveform
+ for(let i = 0; i < dataArray.length; i++) {
+ let barHeight = dataArray[i];
+ let x = i;
+ let y = canvas.height - barHeight;
+
+ ctx.fillRect(x, canvas.height - barHeight, 2, barHeight);
+ }
+
+ // Draw frequency bars
+ ctx.fillStyle = document.getElementById('theme').checked ? '#f00' : '#0f0';
+ for(let i = 0; i < dataArray.length; i++) {
+ let barWidth = canvas.width / dataArray.length;
+ let height = Math.log(dataArray[i] + 1) * 10;
+
+ ctx.fillRect(i * (canvas.width / dataArray.length), canvas.height - height, barWidth, height);
+ }
+ }
+
+ function displayError(message) {
+ ctx.fillStyle = 'black';
+ ctx.font = "24px Arial";
+ ctx.textAlign = "center";
+ ctx.fillText(message, canvas.width / 2, canvas.height / 2);
+ }
+
+ document.addEventListener('DOMContentLoaded', startVisualization);
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1fd68f0d37b7/hermes3-8b.png b/data/artifacts/1fd68f0d37b7/hermes3-8b.png
new file mode 100644
index 0000000..16b3b4a
Binary files /dev/null and b/data/artifacts/1fd68f0d37b7/hermes3-8b.png differ
diff --git a/data/artifacts/1fd68f0d37b7/hf-qwen-coder-32b.html b/data/artifacts/1fd68f0d37b7/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..432342d
--- /dev/null
+++ b/data/artifacts/1fd68f0d37b7/hf-qwen-coder-32b.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <style>
+ body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #282c34; color: white; font-family: Arial, sans-serif; }
+ canvas { border: 1px solid white; }
+ button { position: absolute; top: 10px; right: 10px; background: none; border: none; color: inherit; cursor: pointer; font-size: 16px; }
+ </style>
+</head>
+<body>
+ <canvas id="visualizer" width="800" height="400"></canvas>
+ <button onclick="toggleTheme()">Toggle Theme</button>
+ <script>
+ const canvas = document.getElementById('visualizer');
+ const ctx = canvas.getContext('2d');
+ let themeLight = false;
+
+ function toggleTheme() {
+ themeLight = !themeLight;
+ document.body.style.backgroundColor = themeLight ? '#ffffff' : '#282c34';
+ document.body.style.color = themeLight ? '#000000' : '#ffffff';
+ ctx.fillStyle = themeLight ? '#000000' : '#ffffff';
+ }
+
+ async function initAudio() {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ const audioContext = new AudioContext();
+ const source = audioContext.createMediaStreamSource(stream);
+ const analyser = audioContext.createAnalyser();
+ source.connect(analyser);
+
+ analyser.fftSize = 256;
+ const bufferLength = analyser.frequencyBinCount;
+ const dataArray = new Uint8Array(bufferLength);
+
+ function draw() {
+ requestAnimationFrame(draw);
+ analyser.getByteFrequencyData(dataArray);
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ // Frequency bars
+ const barWidth = (canvas.width / bufferLength) * 2.5;
+ let barHeight;
+ let x = 0;
+
+ for(let i = 0; i < bufferLength; i++) {
+ barHeight = dataArray[i] / 2;
+ ctx.fillRect(x, canvas.height - barHeight / 2, barWidth, barHeight);
+ x += barWidth + 1;
+ }
+
+ // Waveform
+ analyser.getByteTimeDomainData(dataArray);
+ ctx.beginPath();
+ const sliceWidth = canvas.width * 1.0 / bufferLength;
+ let y;
+
+ for(let i = 0; i < bufferLength; i++) {
+ y = dataArray[i] / 128.0 * canvas.height / 2;
+
+ if(i === 0) {
+ ctx.moveTo(x, y);
+ } else {
+ ctx.lineTo(x, y);
+ }
+
+ x += sliceWidth;
+ }
+
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = '#ffcc5c';
+ ctx.stroke();
+ }
+
+ draw();
+ } catch (err) {
+ ctx.font = '20px Arial';
+ ctx.fillText('Microphone permission denied.', canvas.width / 4, canvas.height / 2);
+ }
+ }
+
+ initAudio();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1fd68f0d37b7/hf-qwen-coder-32b.png b/data/artifacts/1fd68f0d37b7/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..de19ca8
Binary files /dev/null and b/data/artifacts/1fd68f0d37b7/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/1fd68f0d37b7/kimi.html b/data/artifacts/1fd68f0d37b7/kimi.html
new file mode 100644
index 0000000..258a0ac
--- /dev/null
+++ b/data/artifacts/1fd68f0d37b7/kimi.html
@@ -0,0 +1,314 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Microphone Audio Visualizer</title>
+<style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+
+ :root {
+ --bg-color: #0a0a0a;
+ --text-color: #ffffff;
+ --accent-color: #00ff88;
+ --bar-color: #00ccff;
+ --wave-color: #ffffff;
+ --error-bg: rgba(255, 50, 50, 0.1);
+ --error-border: rgba(255, 50, 50, 0.4);
+ }
+
+ body.light {
+ --bg-color: #f5f5f5;
+ --text-color: #1a1a1a;
+ --accent-color: #0066cc;
+ --bar-color: #6600cc;
+ --wave-color: #000000;
+ --error-bg: rgba(200, 0, 0, 0.1);
+ --error-border: rgba(200, 0, 0, 0.3);
+ }
+
+ body {
+ background: var(--bg-color);
+ color: var(--text-color);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ overflow: hidden;
+ transition: background 0.3s ease, color 0.3s ease;
+ }
+
+ #container {
+ position: relative;
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ }
+
+ canvas {
+ flex: 1;
+ width: 100%;
+ height: 100%;
+ display: block;
+ }
+
+ #controls {
+ position: absolute;
+ top: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ gap: 12px;
+ z-index: 10;
+ }
+
+ button {
+ padding: 12px 24px;
+ border: 2px solid var(--accent-color);
+ background: var(--bg-color);
+ color: var(--accent-color);
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ border-radius: 30px;
+ transition: all 0.3s ease;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ }
+
+ button:hover {
+ background: var(--accent-color);
+ color: var(--bg-color);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(0,0,0,0.2);
+ }
+
+ button:active {
+ transform: translateY(0);
+ }
+
+ #error {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: var(--error-bg);
+ border: 1px solid var(--error-border);
+ color: var(--text-color);
+ padding: 24px;
+ border-radius: 12px;
+ text-align: center;
+ max-width: 400px;
+ width: 90%;
+ display: none;
+ font-size: 16px;
+ line-height: 1.5;
+ backdrop-filter: blur(10px);
+ }
+
+ #status {
+ position: absolute;
+ bottom: 30px;
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: 13px;
+ opacity: 0.6;
+ pointer-events: none;
+ letter-spacing: 0.5px;
+ }
+</style>
+</head>
+<body>
+<div id="container">
+ <div id="controls">
+ <button id="startBtn">Start Microphone</button>
+ <button id="themeBtn">Toggle Theme</button>
+ </div>
+ <div id="error"></div>
+ <div id="status">Click 'Start Microphone' to begin visualization</div>
+ <canvas id="canvas"></canvas>
+</div>
+
+<script>
+(function() {
+ const canvas = document.getElementById('canvas');
+ const ctx = canvas.getContext('2d');
+ const startBtn = document.getElementById('startBtn');
+ const themeBtn = document.getElementById('themeBtn');
+ const errorDiv = document.getElementById('error');
+ const statusDiv = document.getElementById('status');
+
+ let audioContext;
+ let analyser;
+ let microphone;
+ let freqArray;
+ let timeArray;
+ let animationId;
+ let isRunning = false;
+
+ // Theme toggle
+ themeBtn.addEventListener('click', () => {
+ document.body.classList.toggle('light');
+ });
+
+ // Handle canvas resolution
+ function resize() {
+ const dpr = window.devicePixelRatio || 1;
+ const rect = canvas.getBoundingClientRect();
+ canvas.width = rect.width * dpr;
+ canvas.height = rect.height * dpr;
+ ctx.scale(dpr, dpr);
+ }
+
+ window.addEventListener('resize', resize);
+ resize();
+
+ async function initAudio() {
+ try {
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
+
+ const stream = await navigator.mediaDevices.getUserMedia({
+ audio: {
+ echoCancellation: false,
+ noiseSuppression: false,
+ autoGainControl: false
+ }
+ });
+
+ microphone = audioContext.createMediaStreamSource(stream);
+ analyser = audioContext.createAnalyser();
+
+ analyser.fftSize = 2048;
+ const bufferLength = analyser.frequencyBinCount;
+
+ // Frequency data uses half the FFT size
+ freqArray = new Uint8Array(bufferLength);
+ // Time domain data uses full FFT size
+ timeArray = new Uint8Array(analyser.fftSize);
+
+ microphone.connect(analyser);
+
+ isRunning = true;
+ startBtn.textContent = 'Stop Microphone';
+ statusDiv.textContent = 'Visualizing live audio...';
+ errorDiv.style.display = 'none';
+
+ draw();
+
+ } catch (err) {
+ console.error('Microphone access error:', err);
+ let message = 'Unable to access microphone. ';
+ if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
+ message += 'Permission was denied. Please allow microphone access in your browser settings and try again.';
+ } else if (err.name === 'NotFoundError') {
+ message += 'No microphone found. Please ensure your device has a microphone connected.';
+ } else {
+ message += 'Please check your browser permissions and ensure a microphone is available.';
+ }
+ errorDiv.textContent = message;
+ errorDiv.style.display = 'block';
+ statusDiv.textContent = 'Error accessing microphone';
+ }
+ }
+
+ function stopAudio() {
+ if (animationId) {
+ cancelAnimationFrame(animationId);
+ }
+ if (microphone && microphone.mediaStream) {
+ microphone.mediaStream.getTracks().forEach(track => track.stop());
+ }
+ if (audioContext && audioContext.state !== 'closed') {
+ audioContext.close();
+ }
+ isRunning = false;
+ startBtn.textContent = 'Start Microphone';
+ statusDiv.textContent = "Click 'Start Microphone' to begin visualization";
+
+ // Clear canvas
+ const width = canvas.clientWidth;
+ const height = canvas.clientHeight;
+ ctx.clearRect(0, 0, width, height);
+ }
+
+ startBtn.addEventListener('click', () => {
+ if (isRunning) {
+ stopAudio();
+ } else {
+ initAudio();
+ }
+ });
+
+ function draw() {
+ if (!isRunning) return;
+
+ animationId = requestAnimationFrame(draw);
+
+ const width = canvas.clientWidth;
+ const height = canvas.clientHeight;
+
+ analyser.getByteFrequencyData(freqArray);
+ analyser.getByteTimeDomainData(timeArray);
+
+ ctx.clearRect(0, 0, width, height);
+
+ const isLight = document.body.classList.contains('light');
+ const accent = isLight ? '#0066cc' : '#00ff88';
+ const barBase = isLight ? '102, 0, 204' : '0, 204, 255';
+
+ // Draw Frequency Bars (bottom)
+ const barWidth = (width / freqArray.length) * 2.5;
+ let x = 0;
+
+ for (let i = 0; i < freqArray.length; i++) {
+ const percent = freqArray[i] / 255;
+ const barHeight = percent * (height * 0.45);
+
+ const gradient = ctx.createLinearGradient(0, height, 0, height - barHeight);
+ gradient.addColorStop(0, `rgba(${barBase}, 0.4)`);
+ gradient.addColorStop(0.5, `rgba(${barBase}, 0.7)`);
+ gradient.addColorStop(1, accent);
+
+ ctx.fillStyle = gradient;
+ ctx.fillRect(x, height - barHeight, barWidth, barHeight);
+
+ x += barWidth + 1;
+ if (x > width) break;
+ }
+
+ // Draw Waveform (center)
+ ctx.lineWidth = 3;
+ ctx.strokeStyle = isLight ? '#000000' : '#ffffff';
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.beginPath();
+
+ const sliceWidth = width / timeArray.length;
+ x = 0;
+
+ for (let i = 0; i < timeArray.length; i++) {
+ const v = timeArray[i] / 128.0;
+ const y = (v * height * 0.4) + (height * 0.3);
+
+ if (i === 0) {
+ ctx.moveTo(x, y);
+ } else {
+ ctx.lineTo(x, y);
+ }
+
+ x += sliceWidth;
+ }
+
+ ctx.stroke();
+
+ // Subtle center line
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = isLight ? 'rgba(0,0,0,0.08)' : 'rgba(255,255,255,0.08)';
+ ctx.beginPath();
+ ctx.moveTo(0, height / 2);
+ ctx.lineTo(width, height / 2);
+ ctx.stroke();
+ }
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1fd68f0d37b7/kimi.png b/data/artifacts/1fd68f0d37b7/kimi.png
new file mode 100644
index 0000000..26d172e
Binary files /dev/null and b/data/artifacts/1fd68f0d37b7/kimi.png differ
diff --git a/data/artifacts/48026228790d/claude-code.html b/data/artifacts/48026228790d/claude-code.html
new file mode 100644
index 0000000..0d23ab8
--- /dev/null
+++ b/data/artifacts/48026228790d/claude-code.html
@@ -0,0 +1,318 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Voronoi Shatter</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ overflow: hidden;
+ background: #0b0e14;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ }
+ #stage {
+ display: block;
+ width: 100vw;
+ height: 100vh;
+ cursor: crosshair;
+ }
+ #panel {
+ position: fixed;
+ top: 14px;
+ left: 14px;
+ background: rgba(12, 15, 24, 0.78);
+ border: 1px solid rgba(255, 255, 255, 0.14);
+ border-radius: 10px;
+ padding: 12px 14px;
+ color: #e8ecf4;
+ backdrop-filter: blur(6px);
+ -webkit-backdrop-filter: blur(6px);
+ user-select: none;
+ box-shadow: 0 6px 24px rgba(0,0,0,0.45);
+ }
+ #panel h1 {
+ margin: 0 0 4px 0;
+ font-size: 14px;
+ font-weight: 700;
+ letter-spacing: 0.4px;
+ }
+ #panel p {
+ margin: 0 0 10px 0;
+ font-size: 11.5px;
+ color: #9aa4b8;
+ }
+ #panel .row {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ }
+ button {
+ font: inherit;
+ font-size: 12px;
+ padding: 6px 12px;
+ border-radius: 7px;
+ border: 1px solid rgba(255,255,255,0.2);
+ background: rgba(255,255,255,0.08);
+ color: #e8ecf4;
+ cursor: pointer;
+ transition: background 0.15s ease;
+ }
+ button:hover { background: rgba(255,255,255,0.17); }
+ button.active {
+ background: #4f7cff;
+ border-color: #6c92ff;
+ }
+ #count {
+ font-size: 11.5px;
+ color: #9aa4b8;
+ min-width: 60px;
+ }
+</style>
+</head>
+<body>
+<canvas id="stage"></canvas>
+<div id="panel">
+ <h1>Voronoi Shatter</h1>
+ <p>Click anywhere to add a point & re-shatter</p>
+ <div class="row">
+ <button id="toggleSeeds" class="active">Seeds: on</button>
+ <button id="reset">Reset</button>
+ <span id="count"></span>
+ </div>
+</div>
+<script>
+(function () {
+ "use strict";
+
+ var canvas = document.getElementById("stage");
+ var ctx = canvas.getContext("2d");
+ var toggleBtn = document.getElementById("toggleSeeds");
+ var resetBtn = document.getElementById("reset");
+ var countEl = document.getElementById("count");
+
+ var W = 0, H = 0, DPR = 1;
+ var showSeeds = true;
+ var sites = []; // {x, y, hue, birth, w0} w0 = starting (negative) power weight magnitude
+ var ANIM_MS = 700;
+ var MAX_SITES = 400;
+ var needsDraw = true;
+
+ function resize() {
+ DPR = Math.max(1, Math.min(2, window.devicePixelRatio || 1));
+ W = window.innerWidth;
+ H = window.innerHeight;
+ canvas.width = Math.round(W * DPR);
+ canvas.height = Math.round(H * DPR);
+ ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
+ needsDraw = true;
+ }
+
+ function easeOutCubic(t) {
+ t = Math.min(1, Math.max(0, t));
+ var u = 1 - t;
+ return 1 - u * u * u;
+ }
+
+ // Current power-diagram weight for a site. Animates from -w0 (cell = a point)
+ // up to 0 (plain Voronoi), which makes the new cell grow smoothly while
+ // neighbors yield ground — the re-tessellation animation.
+ function weightOf(s, now) {
+ var t = (now - s.birth) / ANIM_MS;
+ if (t >= 1) return 0;
+ return (easeOutCubic(t) - 1) * s.w0;
+ }
+
+ function nearestDist2(x, y, excludeIdx) {
+ var best = Infinity;
+ for (var i = 0; i < sites.length; i++) {
+ if (i === excludeIdx) continue;
+ var dx = sites[i].x - x, dy = sites[i].y - y;
+ var d2 = dx * dx + dy * dy;
+ if (d2 < best) best = d2;
+ }
+ return best;
+ }
+
+ function addSite(x, y, birth) {
+ var d2 = sites.length ? nearestDist2(x, y, -1) : (W * W + H * H) / 4;
+ d2 = Math.max(d2, 400); // never start fully degenerate
+ sites.push({
+ x: x,
+ y: y,
+ hue: (sites.length * 137.508) % 360,
+ birth: birth,
+ w0: d2
+ });
+ }
+
+ // Sutherland–Hodgman clip of polygon against half-plane dot(p, d) <= k
+ function clipHalfPlane(poly, dx, dy, k) {
+ var out = [];
+ var n = poly.length;
+ if (n === 0) return out;
+ var px = poly[n - 1][0], py = poly[n - 1][1];
+ var pIn = (px * dx + py * dy) <= k;
+ for (var i = 0; i < n; i++) {
+ var cx = poly[i][0], cy = poly[i][1];
+ var cIn = (cx * dx + cy * dy) <= k;
+ if (cIn !== pIn) {
+ var denom = dx * (cx - px) + dy * (cy - py);
+ if (denom !== 0) {
+ var t = (k - (dx * px + dy * py)) / denom;
+ out.push([px + t * (cx - px), py + t * (cy - py)]);
+ }
+ }
+ if (cIn) out.push([cx, cy]);
+ px = cx; py = cy; pIn = cIn;
+ }
+ return out;
+ }
+
+ // Compute the power-diagram cell for site i (weights make growth animatable).
+ // Bisector of |x-pi|^2 - wi <= |x-pj|^2 - wj is the half-plane:
+ // dot(x, 2(pj-pi)) <= |pj|^2 - |pi|^2 + wi - wj
+ function cellFor(i, weights) {
+ var s = sites[i];
+ var poly = [[0, 0], [W, 0], [W, H], [0, H]];
+ var mi = s.x * s.x + s.y * s.y;
+ for (var j = 0; j < sites.length && poly.length; j++) {
+ if (j === i) continue;
+ var o = sites[j];
+ var dx = 2 * (o.x - s.x);
+ var dy = 2 * (o.y - s.y);
+ var k = (o.x * o.x + o.y * o.y) - mi + weights[i] - weights[j];
+ poly = clipHalfPlane(poly, dx, dy, k);
+ }
+ return poly;
+ }
+
+ function draw(now) {
+ var animating = false;
+ var weights = new Array(sites.length);
+ for (var i = 0; i < sites.length; i++) {
+ weights[i] = weightOf(sites[i], now);
+ if (weights[i] !== 0) animating = true;
+ }
+
+ ctx.clearRect(0, 0, W, H);
+ ctx.fillStyle = "#0b0e14";
+ ctx.fillRect(0, 0, W, H);
+
+ ctx.lineJoin = "round";
+
+ for (var s = 0; s < sites.length; s++) {
+ var poly = cellFor(s, weights);
+ if (poly.length < 3) continue;
+ var site = sites[s];
+
+ // gradient radius = farthest vertex from the seed
+ var r2max = 0;
+ for (var v = 0; v < poly.length; v++) {
+ var vx = poly[v][0] - site.x, vy = poly[v][1] - site.y;
+ var d2 = vx * vx + vy * vy;
+ if (d2 > r2max) r2max = d2;
+ }
+ var r = Math.sqrt(r2max) || 1;
+
+ var g = ctx.createRadialGradient(site.x, site.y, 0, site.x, site.y, r);
+ g.addColorStop(0, "hsl(" + site.hue.toFixed(1) + ", 70%, 66%)");
+ g.addColorStop(0.55, "hsl(" + site.hue.toFixed(1) + ", 62%, 48%)");
+ g.addColorStop(1, "hsl(" + site.hue.toFixed(1) + ", 60%, 26%)");
+
+ ctx.beginPath();
+ ctx.moveTo(poly[0][0], poly[0][1]);
+ for (var p = 1; p < poly.length; p++) ctx.lineTo(poly[p][0], poly[p][1]);
+ ctx.closePath();
+ ctx.fillStyle = g;
+ ctx.fill();
+
+ // crack lines between shards
+ ctx.strokeStyle = "rgba(8, 10, 16, 0.9)";
+ ctx.lineWidth = 1.6;
+ ctx.stroke();
+ // subtle inner highlight edge
+ ctx.strokeStyle = "rgba(255, 255, 255, 0.10)";
+ ctx.lineWidth = 0.6;
+ ctx.stroke();
+ }
+
+ if (showSeeds) {
+ for (var q = 0; q < sites.length; q++) {
+ var st = sites[q];
+ var age = Math.min(1, (now - st.birth) / ANIM_MS);
+ var pr = 3 + 2 * (1 - easeOutCubic(age)); // slight pop on birth
+ ctx.beginPath();
+ ctx.arc(st.x, st.y, pr + 1.6, 0, Math.PI * 2);
+ ctx.fillStyle = "rgba(0, 0, 0, 0.55)";
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(st.x, st.y, pr, 0, Math.PI * 2);
+ ctx.fillStyle = "#ffffff";
+ ctx.fill();
+ }
+ }
+
+ countEl.textContent = sites.length + " cells";
+ return animating;
+ }
+
+ var rafId = null;
+ function frame() {
+ rafId = null;
+ var now = performance.now();
+ var animating = draw(now);
+ needsDraw = false;
+ if (animating) schedule();
+ }
+ function schedule() {
+ if (rafId === null) rafId = requestAnimationFrame(frame);
+ }
+
+ function seedInitial() {
+ sites = [];
+ var now = performance.now();
+ var n = 22;
+ var margin = 40;
+ for (var i = 0; i < n; i++) {
+ var x = margin + Math.random() * (W - margin * 2);
+ var y = margin + Math.random() * (H - margin * 2);
+ // staggered births for a rippling intro shatter
+ addSite(x, y, now + i * 28);
+ }
+ schedule();
+ }
+
+ canvas.addEventListener("pointerdown", function (e) {
+ if (sites.length >= MAX_SITES) return;
+ var rect = canvas.getBoundingClientRect();
+ var x = e.clientX - rect.left;
+ var y = e.clientY - rect.top;
+ addSite(x, y, performance.now());
+ schedule();
+ });
+
+ toggleBtn.addEventListener("click", function () {
+ showSeeds = !showSeeds;
+ toggleBtn.textContent = "Seeds: " + (showSeeds ? "on" : "off");
+ toggleBtn.classList.toggle("active", showSeeds);
+ schedule();
+ });
+
+ resetBtn.addEventListener("click", function () {
+ seedInitial();
+ });
+
+ window.addEventListener("resize", function () {
+ resize();
+ schedule();
+ });
+
+ resize();
+ seedInitial();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/48026228790d/claude-code.png b/data/artifacts/48026228790d/claude-code.png
new file mode 100644
index 0000000..e145e62
Binary files /dev/null and b/data/artifacts/48026228790d/claude-code.png differ
diff --git a/data/artifacts/48026228790d/gemma3-12b.html b/data/artifacts/48026228790d/gemma3-12b.html
new file mode 100644
index 0000000..2753571
--- /dev/null
+++ b/data/artifacts/48026228790d/gemma3-12b.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Voronoi Shatter</title>
+<style>
+body { margin: 0; overflow: hidden; }
+canvas { display: block; }
+#seedPointsToggle { position: absolute; top: 10px; left: 10px; }
+</style>
+</head>
+<body>
+
+<input type="checkbox" id="seedPointsToggle">Show Seed Points
+
+<canvas id="voronoiCanvas"></canvas>
+
+<script>
+const canvas = document.getElementById('voronoiCanvas');
+const ctx = canvas.getContext('2d');
+const seedPointsToggle = document.getElementById('seedPointsToggle');
+
+let points = [];
+let numPoints = 15; // Initial number of points
+let animationFrameId;
+
+function init() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
+ for (let i = 0; i < numPoints; i++) {
+ points.push({
+ x: Math.random() * canvas.width,
+ y: Math.random() * canvas.height
+ });
+ }
+
+ draw();
+}
+
+
+function distance(p1, p2) {
+ return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
+}
+
+function calculateVoronoi() {
+ const voronoiMap = {};
+
+ for (let i = 0; i < points.length; i++) {
+ voronoiMap[i] = [];
+ }
+
+ for (let x = 0; x < canvas.width; x++) {
+ for (let y = 0; y < canvas.height; y++) {
+ let closestPointIndex = -1;
+ let shortestDistance = Infinity;
+
+ for (let i = 0; i < points.length; i++) {
+ const dist = distance({ x: x, y: y }, points[i]);
+ if (dist < shortestDistance) {
+ shortestDistance = dist;
+ closestPointIndex = i;
+ }
+ }
+
+ voronoiMap[closestPointIndex].push({ x: x, y: y });
+ }
+ }
+
+ return voronoiMap;
+}
+
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ const voronoi = calculateVoronoi();
+
+ for (let i = 0; i < points.length; i++) {
+ const cell = voronoi[i];
+
+ if (cell.length > 0) {
+ const gradient = ctx.createLinearGradient(
+ points[i].x, points[i].y,
+ points[i].x + Math.cos((Math.random() * 2 - 1) * Math.PI),
+ points[i].y + Math.sin((Math.random() * 2 - 1) * Math.PI)
+ );
+
+ gradient.addColorStop(0, `hsl(${Math.random() * 360}, 100%, 50%)`);
+ gradient.addColorStop(1, `hsl(${Math.random() * 360 + 30}, 100%, 50%)`);
+
+ ctx.beginPath();
+ ctx.moveTo(cell[0].x, cell[0].y);
+ for (let j = 1; j < cell.length; j++) {
+ ctx.lineTo(cell[j].x, cell[j].y);
+ }
+ ctx.closePath();
+ ctx.fillStyle = gradient;
+ ctx.fill();
+
+ }
+ }
+
+ if (seedPointsToggle.checked) {
+ ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; // Red seed points
+ for (let i = 0; i < points.length; i++) {
+ ctx.beginPath();
+ ctx.arc(points[i].x, points[i].y, 5, 0, 2 * Math.PI);
+ ctx.fill();
+ }
+ }
+
+ requestAnimationFrame(draw);
+}
+
+
+canvas.addEventListener('click', (event) => {
+ const newPoint = { x: event.clientX, y: event.clientY };
+ points.push(newPoint);
+ numPoints++;
+
+ // Smooth animation - move existing points towards the new point
+ const animationDuration = 300; // milliseconds
+ const startX = newPoint.x;
+ const startY = newPoint.y;
+
+
+ cancelAnimationFrame(animationFrameId); // Stop previous animation loop.
+
+ animationFrameId = requestAnimationFrame((timestamp) => {
+ let progress = (timestamp - 0) / animationDuration;
+ if (progress < 1) {
+ for (let i = 0; i < points.length - 1; i++) { //Avoid animating the new point itself
+ points[i].x += (newPoint.x - points[i].x) * 0.2 * progress ;
+ points[i].y += (newPoint.y - points[i].y) * 0.2 * progress;
+ }
+ requestAnimationFrame( () => animationFrameId = requestAnimationFrame((timestamp) => {
+ draw();
+
+ }))
+ } else{
+ draw()
+ }
+
+ });
+
+
+});
+
+
+
+init();
+
+</script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/48026228790d/gpt.html b/data/artifacts/48026228790d/gpt.html
new file mode 100644
index 0000000..997cb8e
--- /dev/null
+++ b/data/artifacts/48026228790d/gpt.html
@@ -0,0 +1,314 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>Voronoi Shatter</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ background: #05070b;
+ color: #eee;
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ }
+ #ui {
+ position: fixed;
+ top: 8px;
+ left: 10px;
+ padding: 6px 10px;
+ background: rgba(0,0,0,0.6);
+ border-radius: 6px;
+ font-size: 13px;
+ user-select: none;
+ z-index: 10;
+ }
+ #ui button {
+ margin-right: 8px;
+ padding: 4px 8px;
+ font-size: 12px;
+ background: #222;
+ color: #eee;
+ border: 1px solid #444;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ #ui button:hover {
+ background: #333;
+ }
+ #ui span {
+ opacity: 0.75;
+ }
+ canvas {
+ display: block;
+ }
+</style>
+</head>
+<body>
+<div id="ui">
+ <button id="togglePoints">Hide Seed Points</button>
+ <span>Click: add seed & animate shatter</span>
+</div>
+<canvas id="c"></canvas>
+<script>
+(function() {
+ const canvas = document.getElementById('c');
+ const ctx = canvas.getContext('2d');
+ let width = window.innerWidth;
+ let height = window.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+
+ window.addEventListener('resize', () => {
+ width = window.innerWidth;
+ height = window.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+ drawFrame(performance.now());
+ });
+
+ const toggleBtn = document.getElementById('togglePoints');
+ let showPoints = true;
+ toggleBtn.onclick = () => {
+ showPoints = !showPoints;
+ toggleBtn.textContent = showPoints ? 'Hide Seed Points' : 'Show Seed Points';
+ drawFrame(performance.now());
+ };
+
+ // Seed points
+ let seeds = [];
+ const INITIAL_SEEDS = 20;
+
+ function randomSeed() {
+ return {
+ x: Math.random() * width,
+ y: Math.random() * height
+ };
+ }
+
+ function initSeeds() {
+ seeds = [];
+ for (let i = 0; i < INITIAL_SEEDS; i++) {
+ seeds.push(randomSeed());
+ }
+ }
+
+ initSeeds();
+
+ // Lloyd relaxation (one iteration) for nicer cells
+ function relax(points) {
+ const sites = points.map(p => ({x: p.x, y: p.y, sumx: 0, sumy: 0, count: 0}));
+ // Sample grid for approximate centroids
+ const step = 15;
+ for (let y = 0; y < height; y += step) {
+ for (let x = 0; x < width; x += step) {
+ let minD = Infinity, idx = 0;
+ for (let i = 0; i < sites.length; i++) {
+ const dx = x - sites[i].x;
+ const dy = y - sites[i].y;
+ const d = dx*dx + dy*dy;
+ if (d < minD) { minD = d; idx = i; }
+ }
+ sites[idx].sumx += x;
+ sites[idx].sumy += y;
+ sites[idx].count++;
+ }
+ }
+ const relaxed = [];
+ for (let s of sites) {
+ if (s.count > 0) {
+ relaxed.push({x: s.sumx / s.count, y: s.sumy / s.count});
+ } else {
+ relaxed.push({x: s.x, y: s.y});
+ }
+ }
+ return relaxed;
+ }
+
+ // Generate clip path polygon for each cell by sampling grid
+ function computeCellPolygons(points) {
+ const polygons = new Array(points.length);
+ for (let i = 0; i < polygons.length; i++) polygons[i] = [];
+
+ const step = 12; // sampling step; balance speed vs detail
+ for (let y = 0; y <= height; y += step) {
+ for (let x = 0; x <= width; x += step) {
+ let minD = Infinity, idx = 0;
+ for (let i = 0; i < points.length; i++) {
+ const dx = x - points[i].x;
+ const dy = y - points[i].y;
+ const d = dx*dx + dy*dy;
+ if (d < minD) { minD = d; idx = i; }
+ }
+ polygons[idx].push({x, y});
+ }
+ }
+
+ // Convex hull via Graham scan for each cell's sample points
+ function convexHull(pts) {
+ if (pts.length < 3) return pts.slice();
+ // sort by x then y
+ pts = pts.slice().sort((a, b) => a.x === b.x ? a.y - b.y : a.x - b.x);
+ const cross = (o, a, b) => (a.x - o.x)*(b.y - o.y) - (a.y - o.y)*(b.x - o.x);
+ const lower = [];
+ for (let p of pts) {
+ while (lower.length >= 2 && cross(lower[lower.length-2], lower[lower.length-1], p) <= 0)
+ lower.pop();
+ lower.push(p);
+ }
+ const upper = [];
+ for (let i = pts.length - 1; i >= 0; i--) {
+ const p = pts[i];
+ while (upper.length >= 2 && cross(upper[upper.length-2], upper[upper.length-1], p) <= 0)
+ upper.pop();
+ upper.push(p);
+ }
+ upper.pop();
+ lower.pop();
+ return lower.concat(upper);
+ }
+
+ const hulls = [];
+ for (let i = 0; i < polygons.length; i++) {
+ if (polygons[i].length === 0) {
+ hulls.push([]);
+ } else {
+ hulls.push(convexHull(polygons[i]));
+ }
+ }
+ return hulls;
+ }
+
+ // Color generation for each seed
+ function makePalette(n) {
+ const cols = [];
+ for (let i = 0; i < n; i++) {
+ const h = (i / n) * 360;
+ const s = 60 + Math.random() * 20;
+ const l1 = 30 + Math.random() * 10;
+ const l2 = 55 + Math.random() * 15;
+ cols.push({
+ from: `hsl(${h},${s}%,${l1}%)`,
+ to: `hsl(${(h+40)%360},${s}%,${l2}%)`
+ });
+ }
+ return cols;
+ }
+
+ let relaxedSeeds = relax(seeds);
+ let cellPolys = computeCellPolygons(relaxedSeeds);
+ let colors = makePalette(seeds.length);
+
+ // Animation state
+ let animStart = 0;
+ const ANIM_DURATION = 800; // ms
+ let fromSeeds = relaxedSeeds;
+ let toSeeds = relaxedSeeds;
+
+ function easeOutCubic(t) {
+ return 1 - Math.pow(1 - t, 3);
+ }
+
+ function interpolateSeeds(t) {
+ const interp = [];
+ const n = fromSeeds.length;
+ for (let i = 0; i < n; i++) {
+ const a = fromSeeds[i];
+ const b = toSeeds[i] || fromSeeds[i];
+ interp.push({
+ x: a.x + (b.x - a.x) * t,
+ y: a.y + (b.y - a.y) * t
+ });
+ }
+ return interp;
+ }
+
+ function drawFrame(now) {
+ ctx.clearRect(0, 0, width, height);
+
+ let t = 1;
+ if (animStart > 0) {
+ t = Math.min(1, (now - animStart) / ANIM_DURATION);
+ t = easeOutCubic(t);
+ if (t >= 1) animStart = 0;
+ }
+ const interpSeeds = interpolateSeeds(t);
+ const interpPolys = computeCellPolygons(interpSeeds);
+
+ for (let i = 0; i < interpSeeds.length; i++) {
+ const poly = interpPolys[i];
+ if (!poly || poly.length === 0) continue;
+
+ const seed = interpSeeds[i];
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(poly[0].x, poly[0].y);
+ for (let j = 1; j < poly.length; j++) {
+ ctx.lineTo(poly[j].x, poly[j].y);
+ }
+ ctx.closePath();
+ ctx.clip();
+
+ // Radial gradient from seed outward
+ const maxR = Math.max(width, height);
+ const grad = ctx.createRadialGradient(seed.x, seed.y, 0, seed.x, seed.y, maxR * 0.8);
+ const col = colors[i];
+ grad.addColorStop(0, col.from);
+ grad.addColorStop(1, col.to);
+
+ ctx.fillStyle = grad;
+ ctx.fillRect(0, 0, width, height);
+
+ // Subtle inner border
+ ctx.strokeStyle = 'rgba(0,0,0,0.35)';
+ ctx.lineWidth = 1;
+ ctx.stroke();
+
+ ctx.restore();
+ }
+
+ // Seed points
+ if (showPoints) {
+ ctx.fillStyle = '#ffffff';
+ for (let i = 0; i < interpSeeds.length; i++) {
+ const s = interpSeeds[i];
+ ctx.beginPath();
+ ctx.arc(s.x, s.y, 3, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.strokeStyle = 'rgba(0,0,0,0.6)';
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ }
+ }
+ }
+
+ function loop(now) {
+ drawFrame(now);
+ requestAnimationFrame(loop);
+ }
+ requestAnimationFrame(loop);
+
+ // On click: add seed and animate towards relaxed configuration
+ canvas.addEventListener('click', e => {
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ seeds.push({x, y});
+ colors = makePalette(seeds.length);
+
+ fromSeeds = relaxedSeeds.slice();
+ // ensure equal length
+ fromSeeds.push({x, y});
+
+ const relaxedNew = relax(seeds);
+ relaxedSeeds = relaxedNew;
+ toSeeds = relaxedNew.slice();
+
+ animStart = performance.now();
+ });
+
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/48026228790d/gpt.png b/data/artifacts/48026228790d/gpt.png
new file mode 100644
index 0000000..6d7066f
Binary files /dev/null and b/data/artifacts/48026228790d/gpt.png differ
diff --git a/data/artifacts/48026228790d/grok.html b/data/artifacts/48026228790d/grok.html
new file mode 100644
index 0000000..5219a40
--- /dev/null
+++ b/data/artifacts/48026228790d/grok.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>Voronoi Shatter</title>
+<style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ html, body {
+ width: 100%; height: 100%; overflow: hidden;
+ background: #0a0a0f;
+ font-family: "Segoe UI", system-ui, sans-serif;
+ }
+ canvas {
+ display: block;
+ width: 100%; height: 100%;
+ cursor: crosshair;
+ }
+ .hud {
+ position: fixed;
+ top: 20px; left: 20px;
+ z-index: 10;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ pointer-events: none;
+ }
+ .hud h1 {
+ color: rgba(255,255,255,0.9);
+ font-size: 15px;
+ font-weight: 600;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ text-shadow: 0 2px 12px rgba(0,0,0,0.5);
+ }
+ .hud p {
+ color: rgba(255,255,255,0.45);
+ font-size: 12px;
+ letter-spacing: 0.04em;
+ }
+ .controls {
+ position: fixed;
+ bottom: 24px; left: 50%;
+ transform: translateX(-50%);
+ z-index: 10;
+ display: flex;
+ gap: 10px;
+ pointer-events: auto;
+ }
+ button {
+ background: rgba(255,255,255,0.08);
+ border: 1px solid rgba(255,255,255,0.18);
+ color: rgba(255,255,255,0.85);
+ padding: 10px 18px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 500;
+ letter-spacing: 0.06em;
+ cursor: pointer;
+ backdrop-filter: blur(12px);
+ transition: background 0.2s, border-color 0.2s, transform 0.15s;
+ }
+ button:hover {
+ background: rgba(255,255,255,0.16);
+ border-color: rgba(255,255,255,0.35);
+ }
+ button:active { transform: scale(0.96); }
+ button.active {
+ background: rgba(120,180,255,0.25);
+ border-color: rgba(140,200,255,0.55);
+ color: #cfe6ff;
+ }
+</style>
+</head>
+<body>
+<canvas id="c"></canvas>
+<div class="hud">
+ <h1>Voronoi Shatter</h1>
+ <p>Click to add a seed · cells re-tessellate</p>
+</div>
+<div class="controls">
+ <button id="toggleSeeds" class="active">Seed Points: On</button>
+ <button id="reset">Reset</button>
+</div>
+<script>
+(function () {
+ const canvas = document.getElementById("c");
+ const ctx = canvas.getContext("2d");
+ const toggleBtn = document.getElementById("toggleSeeds");
+ const resetBtn = document.getElementById("reset");
+
+ let W, H, dpr;
+ let sites = [];
+ let showSeeds = true;
+ let animT = 1;
+ let animFrom = null;
+ let animTo = null;
+ let animating = false;
+ const ANIM_MS = 480;
+
+ function resize() {
+ dpr = Math.min(window.devicePixelRatio || 1, 2);
+ W = window.innerWidth;
+ H = window.innerHeight;
+ canvas.width = W * dpr;
+ canvas.height = H * dpr;
+ canvas.style.width = W + "px";
+ canvas.style.height = H + "px";
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ }
+
+ function randColor(i) {
+ const h = (i * 137.508 + 40) % 360;
+ const s = 55 + (i * 17) % 30;
+ const l = 48 + (i * 13) % 18;
+ return { h, s, l };
+ }
+
+ function hsl(c, a) {
+ return a == null
+ ? `hsl(${c.h},${c.s}%,${c.l}%)`
+ : `hsla(${c.h},${c.s}%,${c.l}%,${a})`;
+ }
+
+ function seedInitial(n) {
+ sites = [];
+ const pad = 40;
+ for (let i = 0; i < n; i++) {
+ sites.push({
+ x: pad + Math.random() * (W - pad * 2),
+ y: pad + Math.random() * (H - pad * 2),
+ color: randColor(i),
+ id: i
+ });
+ }
+ }
+
+ // --- Geometry helpers ---
+ function circumcircle(a, b, c) {
+ const ax = a.x, ay = a.y, bx = b.x, by = b.y, cx = c.x, cy = c.y;
+ const D = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
+ if (Math.abs(D) < 1e-12) return null;
+ const a2 = ax * ax + ay * ay;
+ const b2 = bx * bx + by * by;
+ const c2 = cx * cx + cy * cy;
+ const ux = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / D;
+ const uy = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / D;
+ const r2 = (ux - ax) * (ux - ax) + (uy - ay) * (uy - ay);
+ return { x: ux, y: uy, r2 };
+ }
+
+ function inCircle(p, circ) {
+ const dx = p.x - circ.x, dy = p.y - circ.y;
+ return dx * dx + dy * dy < circ.r2 - 1e-9;
+ }
+
+ // Bowyer–Watson Delaunay
+ function delaunay(pts) {
+ if (pts.length < 3) return [];
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
+ for (const p of pts) {
+ if (p.x < minX) minX = p.x;
+ if (p.y < minY) minY = p.y;
+ if (p.x > maxX) maxX = p.x;
+ if (p.y > maxY) maxY = p.y;
+ }
+ const dx = maxX - minX, dy = maxY - minY;
+ const dmax = Math.max(dx, dy) * 3;
+ const mx = (minX + maxX) / 2, my = (minY + maxY) / 2;
+ const superTri = [
+ { x: mx - 2 * dmax, y: my - dmax, _s: true },
+ { x: mx, y: my + 2 * dmax, _s: true },
+ { x: mx + 2 * dmax, y: my - dmax, _s: true }
+ ];
+ let tris = [{ a: superTri[0], b: superTri[1], c: superTri[2] }];
+
+ for (const p of pts) {
+ const bad = [];
+ for (const t of tris) {
+ const cc = circumcircle(t.a, t.b, t.c);
+ if (cc && inCircle(p, cc)) bad.push(t);
+ }
+ const edges = [];
+ for (const t of bad) {
+ edges.push([t.a, t.b], [t.b, t.c], [t.c, t.a]);
+ }
+ // unique edges
+ const unique = [];
+ for (let i = 0; i < edges.length; i++) {
+ let shared = false;
+ for (let j = 0; j < edges.length; j++) {
+ if (i === j) continue;
+ const e1 = edges[i], e2 = edges[j];
+ if ((e1[0] === e2[0] && e1[1] === e2[1]) ||
+ (e1[0] === e2[1] && e1[1] === e2[0])) {
+ shared = true;
+ break;
+ }
+ }
+ if (!shared) unique.push(edges[i]);
+ }
+ tris = tris.filter(t => bad.indexOf(t) === -1);
+ for (const e of unique) {
+ tris.push({ a: e[0], b: e[1], c: p });
+ }
+ }
+
+ return tris.filter(t =>
+ !t.a._s && !t.b._s && !t.c._s
+ );
+ }
+
+ // Build Voronoi cells from Delaunay dual
+ function voronoiCells(pts) {
+ const n = pts.length;
+ if (n === 0) return [];
+ if (n === 1) {
+ return [{
+ site: pts[0],
+ poly: [
+ { x: -1, y: -1 }, { x: W + 1, y: -1 },
+ { x: W + 1, y: H + 1 }, { x: -1, y: H + 1 }
+ ]
+ }];
+ }
+
+ const tris = delaunay(pts);
+ // Map site -> list of circumcenters (with triangle refs)
+ const adj = new Map();
+ for (const p of pts) adj.set(p, []);
+
+ const centers = [];
+ for (const t of tris) {
+ const cc = circumcircle(t.a, t.b, t.c);
+ if (!cc) continue;
+ const c = { x: cc.x, y: cc.y };
+ centers.push(c);
+ adj.get(t.a).push(c);
+ adj.get(t.b).push(c);
+ adj.get(t.c).push(c);
+ }
+
+ // For boundary sites, extend with clipped box intersections via half-planes
+ // Clip infinite rays against bounding box using half-plane intersection
+ const box = [
+ { x: 0, y: 0 }, { x: W, y: 0 },
+ { x: W, y: H }, { x: 0, y: H }
+ ];
+
+ const cells = [];
+ for (const site of pts) {
+ // Collect half-planes: for each other site, bisector half-plane
+ // Start with bounding box polygon, clip by each bisector
+ let poly = box.map(p => ({ x: p.x, y: p.y }));
+ for (const other of pts) {
+ if (other === site) continue;
+ // Half-plane: points closer to site than other
+ // Midpoint + direction perpendicular to (other - site)
+ const mx = (site.x + other.x) / 2;
+ const my = (site.y + other.y) / 2;
+ // Normal pointing toward site from other
+ let nx = site.x - other.x;
+ let ny = site.y - other.y;
+ // Plane: (p - mid) · n >= 0 (on site side)
+ poly = clipPoly(poly, mx, my, nx, ny);
+ if (poly.length < 3) break;
+ }
+ if (poly.length >= 3) {
+ cells.push({ site, poly });
+ }
+ }
+ return cells;
+ }
+
+ // Sutherland–Hodgman style half-plane clip
+ function clipPoly(poly, px, py, nx, ny) {
+ if (!poly.length) return [];
+ const out = [];
+ const n = poly.length;
+ for (let i = 0; i < n; i++) {
+ const cur = poly[i];
+ const prev = poly[(i + n - 1) % n];
+ const curIn = (cur.x - px) * nx + (cur.y - py) * ny >= -1e-9;
+ const prevIn = (prev.x - px) * nx + (prev.y - py) * ny >= -1e-9;
+ if (curIn) {
+ if (!prevIn) {
+ const ip = lineIntersect(prev, cur, px, py, nx, ny);
+ if (ip) out.push(ip);
+ }
+ out.push(cur);
+ } else if (prevIn) {
+ const ip = lineIntersect(prev, cur, px, py, nx, ny);
+ if (ip) out.push(ip);
+ }
+ }
+ return out;
+ }
+
+ function lineIntersect(a, b, px, py, nx, ny) {
+ const dx = b.x - a.x, dy = b.y - a.y;
+ const denom = dx * nx + dy * ny;
+ if (Math.abs(denom) < 1e-14) return { x: a.x, y: a.y };
+ const t = ((px - a.x) * nx + (py - a.y) * ny) / denom;
+ return { x: a.x + t * dx, y: a.y + t * dy };
+ }
+
+ // Match cells between frames for animation (by site identity / nearest)
+ function computeCells() {
+ return voronoiCells(sites);
+ }
+
+ let currentCells = [];
+
+ function easeOutCubic(t) {
+ return 1 - Math.pow(1 - t, 3);
+ }
+
+ function lerp(a, b, t) {
+ return a + (b - a) * t;
+ }
+
+ function drawCell(cell, alpha) {
+ const poly = cell.poly;
+ if (!poly || poly.length < 3) return;
+ const s = cell.site;
+ const c = s.color;
+
+ ctx.beginPath();
+ ctx.moveTo(poly[0].x, poly[0].y);
+ for (let i = 1; i < poly.length; i++) {
+ ctx.lineTo(poly[i].x, poly[i].y);
+ }
+ ctx.closePath();
+
+ // Radial gradient from site
+ let maxR = 0;
+ for (const p of poly) {
+ const dx = p.x - s.x, dy = p.y - s.y;
+ const r = Math.sqrt(dx * dx + dy * dy);
+ if (r > maxR) maxR = r;
+ }
+ maxR = Math.max(maxR, 40);
+
+ const g = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, maxR);
+ g.addColorStop(0, hsl({ h: c.h, s: c.s, l: Math.min(c.l + 18, 78) }, alpha));
+ g.addColorStop(0.45, hsl(c, alpha));
+ g.addColorStop(1, hsl({ h: c.h, s: c.s + 5, l: Math.max(c.l - 22, 12) }, alpha));
+ ctx.fillStyle = g;
+ ctx.fill();
+
+ // Subtle edge
+ ctx.strokeStyle = `rgba(255,255,255,${0.12 * alpha})`;
+ ctx.lineWidth = 1.25;
+ ctx.stroke();
+
+ // Inner highlight edge
+ ctx.strokeStyle = hsl({ h: c.h, s: c.s, l: Math.min(c.l + 30, 90) }, 0.15 * alpha);
+ ctx.lineWidth = 0.6;
+ ctx.stroke();
+ }
+
+ function drawSeeds(list, alpha) {
+ for (const s of list) {
+ ctx.beginPath();
+ ctx.arc(s.x, s.y, 3.5, 0, Math.PI * 2);
+ ctx.fillStyle = `rgba(255,255,255,${0.85 * alpha})`;
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(s.x, s.y, 6, 0, Math.PI * 2);
+ ctx.strokeStyle = `rgba(255,255,255,${0.25 * alpha})`;
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ }
+ }
+
+ // Interpolate polygons: match by angle around site centroid
+ function sortPoly(poly, cx, cy) {
+ return poly.slice().sort((a, b) =>
+ Math.atan2(a.y - cy, a.x - cx) - Math.atan2(b.y - cy, b.x - cx)
+ );
+ }
+
+ function resamplePoly(poly, n, cx, cy) {
+ const sorted = sortPoly(poly, cx, cy);
+ // Build cumulative edge lengths for closed poly
+ const pts = sorted.concat([sorted[0]]);
+ const segs = [];
+ let total = 0;
+ for (let i = 0; i < pts.length - 1; i++) {
+ const dx = pts[i + 1].x - pts[i].x;
+ const dy = pts[i + 1].y - pts[i].y;
+ const len = Math.sqrt(dx * dx + dy * dy);
+ segs.push({ a: pts[i], b: pts[i + 1], len, acc: total });
+ total += len;
+ }
+ if (total < 1e-6) {
+ return Array.from({ length: n }, () => ({ x: cx, y: cy }));
+ }
+ const out = [];
+ for (let i = 0; i < n; i++) {
+ const t = (i / n) * total;
+ let seg = segs[segs.length - 1];
+ for (const s of segs) {
+ if (t >= s.acc && t <= s.acc + s.len + 1e-9) { seg = s; break; }
+ }
+ const lt = seg.len < 1e-9 ? 0 : (t - seg.acc) / seg.len;
+ out.push({
+ x: lerp(seg.a.x, seg.b.x, lt),
+ y: lerp(seg.a.y, seg.b.y, lt)
+ });
+ }
+ return out;
+ }
+
+ function morphCells(fromCells, toCells, t) {
+ // Match by site id
+ const fromMap = new Map();
+ for (const c of fromCells) fromMap.set(c.site.id, c);
+ const result = [];
+ for (const tc of toCells) {
+ const fc = fromMap.get(tc.site.id);
+ if (!fc) {
+ // New cell: scale from site
+ const poly = tc.poly.map(p => ({
+ x: lerp(tc.site.x, p.x, t),
+ y: lerp(tc.site.y, p.y, t)
+ }));
+ result.push({ site: tc.site, poly });
+ continue;
+ }
+ const n = Math.max(fc.poly.length, tc.poly.length, 8);
+ const a = resamplePoly(fc.poly, n, fc.site.x, fc.site.y);
+ const b = resamplePoly(tc.poly, n, tc.site.x, tc.site.y);
+ const poly = a.map((p, i) => ({
+ x: lerp(p.x, b[i].x, t),
+ y: lerp(p.y, b[i].y, t)
+ }));
+ // Lerp site position too (in case)
+ const site = {
+ x: lerp(fc.site.x, tc.site.x, t),
+ y: lerp(fc.site.y, tc.site.y, t),
+ color: tc.site.color,
+ id: tc.site.id
+ };
+ result.push({ site, poly });
+ }
+ return result;
+ }
+
+ let animStart = 0;
+ let prevCells = [];
+ let targetCells = [];
+
+ function render(now) {
+ if (animating) {
+ const elapsed = now - animStart;
+ animT = Math.min(1, elapsed / ANIM_MS);
+ if (animT >= 1) {
+ animating = false;
+ animT = 1;
+ currentCells = targetCells;
+ } else {
+ currentCells = morphCells(prevCells, targetCells, easeOutCubic(animT));
+ }
+ }
+
+ // Background
+ ctx.fillStyle = "#0a0a0f";
+ ctx.fillRect(0, 0, W, H);
+
+ // Soft vignette base
+ const vg = ctx.createRadialGradient(W / 2, H / 2, 0, W / 2, H / 2, Math.max(W, H) * 0.7);
+ vg.addColorStop(0, "rgba(20,22,40,0.4)");
+ vg.addColorStop(1, "rgba(5,5,10,0)");
+ ctx.fillStyle = vg;
+ ctx.fillRect(0, 0, W, H);
+
+ for (const cell of currentCells) {
+ drawCell(cell, 1);
+ }
+
+ if (showSeeds) {
+ drawSeeds(sites, 1);
+ }
+
+ // Flash ring on newest site during anim
+ if (animating && sites.length) {
+ const newest = sites[sites.length - 1];
+ const pulse = easeOutCubic(animT);
+ ctx.beginPath();
+ ctx.arc(newest.x, newest.y, 8 + pulse * 40, 0, Math.PI * 2);
+ ctx.strokeStyle = `rgba(255,255,255,${(1 - pulse) * 0.5})`;
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ }
+
+ requestAnimationFrame(render);
+ }
+
+ function recompute(animate) {
+ const next = computeCells();
+ if (animate && currentCells.length) {
+ prevCells = currentCells.map(c => ({
+ site: c.site,
+ poly: c.poly.map(p => ({ x: p.x, y: p.y }))
+ }));
+ targetCells = next;
+ animT = 0;
+ animStart = performance.now();
+ animating = true;
+ } else {
+ currentCells = next;
+ targetCells = next;
+ animating = false;
+ }
+ }
+
+ canvas.addEventListener("click", (e) => {
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ const id = sites.length ? sites[sites.length - 1].id + 1 : 0;
+ sites.push({
+ x, y,
+ color: randColor(id + Math.floor(Math.random() * 50)),
+ id
+ });
+ recompute(true);
+ });
+
+ toggleBtn.addEventListener("click", () => {
+ showSeeds = !showSeeds;
+ toggleBtn.textContent = showSeeds ? "Seed Points: On" : "Seed Points: Off";
+ toggleBtn.classList.toggle("active", showSeeds);
+ });
+
+ resetBtn.addEventListener("click", () => {
+ seedInitial(8 + Math.floor(Math.random() * 5));
+ recompute(true);
+ });
+
+ window.addEventListener("resize", () => {
+ resize();
+ recompute(false);
+ });
+
+ resize();
+ seedInitial(10);
+ recompute(false);
+ requestAnimationFrame(render);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/48026228790d/grok.png b/data/artifacts/48026228790d/grok.png
new file mode 100644
index 0000000..3a916bc
Binary files /dev/null and b/data/artifacts/48026228790d/grok.png differ
diff --git a/data/artifacts/48026228790d/hermes3-8b.html b/data/artifacts/48026228790d/hermes3-8b.html
new file mode 100644
index 0000000..deaeac7
--- /dev/null
+++ b/data/artifacts/48026228790d/hermes3-8b.html
@@ -0,0 +1,309 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Voronoi Shatter Effect</title>
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ background-color: #f5f5f5;
+ }
+
+ canvas {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ border: 1px solid #ccc;
+ }
+
+ .seed-points {
+ position: fixed;
+ top: 10px;
+ right: 10px;
+ background-color: #fff;
+ padding: 5px;
+ border-radius: 3px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+ }
+
+ .seed-points label {
+ display: block;
+ margin-bottom: 5px;
+ }
+
+ .seed-points input[type="checkbox"] {
+ margin-left: 10px;
+ }
+ </style>
+</head>
+<body>
+ <div class="seed-points">
+ <label for="toggle-seed-points">Show/Hide Seed Points</label>
+ <input type="checkbox" id="toggle-seed-points" />
+ </div>
+
+ <canvas id="voronoi-canvas"></canvas>
+
+ <script>
+ const canvas = document.getElementById('voronoi-canvas');
+ const context = canvas.getContext('2d');
+
+ canvas.width = 600;
+ canvas.height = 400;
+
+ let sites = [];
+ let cells = {};
+
+ function site(x, y) {
+ return { x: x, y: y };
+ }
+
+ function distance(p1, p2) {
+ const dx = (p1.x - p2.x) * (p1.x - p2.x);
+ const dy = (p1.y - p2.y) * (p1.y - p2.y);
+ return Math.sqrt(dx + dy);
+ }
+
+ function Voronoi(points) {
+ this.cells = {};
+ this.points = points;
+
+ for (let i = 0; i < points.length; ++i) {
+ const point = points[i];
+
+ let cell = {
+ site: point,
+ region: [],
+ edges: []
+ };
+
+ cells[point.id] = cell;
+
+ for (let j = 0; j < points.length; ++j) {
+ if(i != j){
+ const otherPoint = points[j];
+
+ let edge;
+
+ if(!cells[otherPoint.id].region.length && !cell.region.length){
+ let center = {x: point.x + 0.5*(points[i].x - points[j].x),
+ y: point.y + 0.5*(points[i].y - points[j].y)};
+
+ let d1 = distance(center, point);
+ let d2 = distance(center, otherPoint);
+
+ let a = ((point.x * point.x + point.y * point.y) / (d1 * d1)) ** 0.5;
+ let b = ((otherPoint.x * otherPoint.x + otherPoint.y * otherPoint.y) / (d2 * d2)) ** 0.5;
+
+ if(a > b){
+ edge = { p1: center, p2: point };
+ cell.edges.push({line: new Path.Line(edge.p1, edge.p2), site: point});
+ cell.region.push(otherPoint);
+ } else {
+ edge = { p1: center, p2: otherPoint };
+ cells[otherPoint.id].region.push(point);
+ cells[otherPoint.id].edges.push({line: new Path.Line(edge.p1, edge.p2), site: otherPoint});
+ cell.edges.push(edge);
+ }
+ } else {
+ let dx = points[j].x - points[i].x;
+ let dy = points[j].y - points[i].y;
+
+ if(dx < 0) {
+ if(dy < 0) edge = { p1: points[j], p2: points[i] };
+ else edge = { p1: points[i], p2: points[j] };
+ } else {
+ if(dy < 0) edge = { p1: points[i], p2: points[j] };
+ else edge = { p1: points[j], p2: points[i] };
+ }
+
+ cell.edges.push(edge);
+ }
+
+ let inEdge = null;
+ let inSite = null;
+
+ for(let k = 0; k < cell.region.length; ++k) {
+ if(distance(cell.site, cell.region[k]) > a){
+ inSite = cell.region[k];
+ break;
+ }
+ }
+
+ for(let k = 0; k < cells[otherPoint.id].region.length; ++k) {
+ if(distance(otherPoint, cells[otherPoint.id].region[k]) < b){
+ inEdge = { p1: cells[otherPoint.id].edges[k].line.p2,
+ p2: cells[otherPoint.id].edges[k].line.p1 };
+
+ break;
+ }
+ }
+
+ if(inSite != null) {
+ cell.edges.push({p1: edge.p2, p2: inEdge.p2, site: inSite});
+ cells[otherPoint.id].region.push(cell.site);
+ cells[otherPoint.id].edges.push({line: new Path.Line(inEdge.p2, inEdge.p1), site: inSite});
+ }
+
+ cell.edges.push(edge);
+
+ if(inEdge != null) {
+ edge = { p1: inEdge.p1,
+ p2: otherPoint };
+
+ cells[otherPoint.id].edges.push({line: new Path.Line(inEdge.p1, inEdge.p2), site: otherPoint});
+ cell.region.push(otherPoint);
+ }
+
+ let area = Math.abs(edge.p1.x * edge.p2.y - edge.p2.x * edge.p1.y) / 2;
+
+ if(area > 0){
+ cells[otherPoint.id].region.forEach(function(site){
+ site.area = area;
+ });
+
+ cell.region.forEach(function(site){
+ site.area = area;
+ });
+ }
+
+ }
+ }
+
+ for(let k = 0; k < cell.edges.length; ++k) {
+
+ if(cell.edges[k].site == point) {
+ let edgeOther = { p1: cell.edges[k].line.p2,
+ p2: cell.edges[k].line.p1 };
+
+ cell.edges.splice(k, 1);
+ k--;
+
+ cells[cell.region[0].id].edges.push({ line: new Path.Line(edgeOther.p1, edgeOther.p2), site: cell.region[0] });
+ }
+ }
+ }
+
+ for(let i = 0; i < points.length; ++i) {
+
+ const point = points[i];
+ let site = { id: point.id, x: point.x, y: point.y };
+
+ sites.push(site);
+
+ if(!cells[site.id].region.length){
+ cells[site.id] = {
+ site: site,
+ region: [],
+ edges: []
+ };
+ }
+ }
+
+ let regions = [];
+
+ for(let i = 0; i < points.length; ++i) {
+
+ const point = points[i];
+
+ if(cells[point.id].region.length > 0){
+ cell = cells[point.id];
+ cell.site = point;
+
+ regions.push(cell);
+ delete cells[point.id];
+ }
+ }
+
+ this.regions = regions;
+ }
+
+ function drawVoronoi() {
+ context.clearRect(0, 0, canvas.width, canvas.height);
+
+ for (let i = 0; i < sites.length; ++i) {
+ let site = sites[i];
+
+ if (site.id === addPoint.x || site.id === addPoint.y) {
+ continue;
+ }
+
+ context.beginPath();
+ context.arc(site.x, site.y, 3, 0, 2 * Math.PI);
+ context.fillStyle = "#000";
+ context.fill();
+
+ context.strokeStyle = "#ccc";
+ context.stroke();
+ }
+
+ for (let i in cells) {
+ let cell = cells[i];
+
+ if (!cell.region.length ||
+ (addPoint.x && addPoint.x === cell.site.id) ||
+ (addPoint.y && addPoint.y === cell.site.id)) continue;
+
+ context.beginPath();
+
+ for(let j = 0; j < cell.edges.length; ++j) {
+ let edge = cell.edges[j];
+
+ if(edge.site == cell.site){
+ continue;
+ }
+
+ context.moveTo(edge.line.p1.x, edge.line.p1.y);
+ context.lineTo(edge.line.p2.x, edge.line.p2.y);
+ }
+
+ context.strokeStyle = "#000";
+ context.stroke();
+
+ let gradient = context.createLinearGradient(cell.site.x, cell.site.y,
+ cell.region[0].x, cell.region[0].y);
+
+ context.fillStyle = gradient;
+
+ context.fillRect(cell.site.x - 10, cell.site.y - 10, 20, 20);
+ }
+ }
+
+ let addPoint = null;
+
+ canvas.addEventListener('click', function(e) {
+ if (addPoint) {
+ sites.push(site(e.pageX, e.pageY));
+ cells[sites[sites.length - 1].id] = {
+ site: sites[sites.length - 1],
+ region: [],
+ edges: []
+ };
+
+ addPoint = null;
+ } else {
+ addPoint = sites[0];
+ canvas.removeEventListener('click', arguments.callee);
+ }
+
+ setTimeout(drawVoronoi, 10);
+ });
+
+ let toggleSeedPoints = document.getElementById('toggle-seed-points');
+
+ toggleSeedPoints.addEventListener('change', function(e) {
+ for(let i = 0; i < sites.length; ++i) {
+ context.beginPath();
+ context.arc(sites[i].x, sites[i].y, 3, 0, 2 * Math.PI);
+ context.fillStyle = e.target.checked ? "#ccc" : "#000";
+ context.fill();
+ context.strokeStyle = "#fff";
+ context.stroke();
+ }
+ });
+
+ drawVoronoi();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/48026228790d/hermes3-8b.png b/data/artifacts/48026228790d/hermes3-8b.png
new file mode 100644
index 0000000..3056ff5
Binary files /dev/null and b/data/artifacts/48026228790d/hermes3-8b.png differ
diff --git a/data/artifacts/48026228790d/qwen25-7b.html b/data/artifacts/48026228790d/qwen25-7b.html
new file mode 100644
index 0000000..0cd0d2b
--- /dev/null
+++ b/data/artifacts/48026228790d/qwen25-7b.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Voronoi Shatter Effect</title>
+ <style>
+ body { margin: 0; overflow: hidden; }
+ canvas { display: block; width: 100%; height: 100%; background-color: #222; cursor: crosshair; }
+ .cell {
+ fill: url(#gradient);
+ transition: opacity 0.5s;
+ }
+ </style>
+</head>
+<body>
+<canvas id="canvas"></canvas>
+<script>
+const canvas = document.getElementById('canvas');
+const ctx = canvas.getContext('2d');
+let points = [];
+let showSeeds = true;
+
+function init() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ draw();
+}
+
+function draw() {
+ const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
+ gradient.addColorStop(0, 'hsl(' + (Math.random() * 360) + ',100%,50%)');
+ gradient.addColorStop(1, 'hsl(' + (Math.random() * 360) + ',100%,50%)');
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ if (showSeeds) {
+ for (const point of points) {
+ ctx.beginPath();
+ ctx.arc(point.x, point.y, 4, 0, Math.PI * 2);
+ ctx.fillStyle = 'white';
+ ctx.fill();
+ }
+ }
+
+ const voronoi = new Voronoi({ sites: points });
+ voronoi.polygons().forEach(polygon => {
+ if (polygon) {
+ const gradientFill = polygon.map((point, index, array) => [point.x, point.y]).reduce((a, c) => a + ` ${c[0]}px,${c[1]}px`, 'M').replace(/(.*),(.*)/, 'L$2 L$1');
+ ctx.beginPath();
+ ctx.moveTo(...polygon[0]);
+ polygon.forEach(point => ctx.lineTo(...point));
+ ctx.closePath();
+ ctx.strokeStyle = '#000';
+ ctx.stroke();
+
+ ctx.fillStyle = gradient;
+ ctx.fill();
+ }
+ });
+}
+
+function addPoint(event) {
+ const x = event.clientX - canvas.offsetLeft;
+ const y = event.clientY - canvas.offsetTop;
+
+ points.push({ x, y });
+
+ draw();
+}
+
+canvas.addEventListener('click', addPoint);
+
+function toggleSeeds() {
+ showSeeds = !showSeeds;
+ draw();
+}
+
+document.body.appendChild(document.createElement('button')).onclick = toggleSeeds;
+
+init();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/48026228790d/qwen25-7b.png b/data/artifacts/48026228790d/qwen25-7b.png
new file mode 100644
index 0000000..1697e8b
Binary files /dev/null and b/data/artifacts/48026228790d/qwen25-7b.png differ
diff --git a/data/artifacts/48026228790d/qwen3-14b.html b/data/artifacts/48026228790d/qwen3-14b.html
new file mode 100644
index 0000000..7c346ad
--- /dev/null
+++ b/data/artifacts/48026228790d/qwen3-14b.html
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Voronoi Shatter Effect</title>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ width: 100%;
+ height: 100%;
+ background: #000;
+ font-family: sans-serif;
+ }
+ canvas {
+ display: block;
+ }
+ #controls {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ background: rgba(255, 255, 255, 0.1);
+ padding: 10px;
+ border-radius: 5px;
+ user-select: none;
+ }
+ #controls label {
+ display: block;
+ margin: 5px 0;
+ }
+ </style>
+</head>
+<body>
+ <div id="controls">
+ <label>
+ <input type="checkbox" id="togglePoints" checked> Show Seed Points
+ </label>
+ </div>
+ <canvas id="voronoiCanvas"></canvas>
+ <script>
+ const canvas = document.getElementById('voronoiCanvas');
+ const ctx = canvas.getContext('2d');
+ let points = [];
+ let showPoints = true;
+ const togglePoints = document.getElementById('togglePoints');
+
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
+ window.addEventListener('resize', () => {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ draw();
+ });
+
+ togglePoints.addEventListener('change', (e) => {
+ showPoints = e.target.checked;
+ draw();
+ });
+
+ function getDistance(a, b) {
+ return Math.hypot(a.x - b.x, a.y - b.y);
+ }
+
+ function getVoronoiCells(points) {
+ const cells = [];
+ for (let i = 0; i < points.length; i++) {
+ const cell = {
+ index: i,
+ points: []
+ };
+ for (let j = 0; j < points.length; j++) {
+ if (i === j) continue;
+ const dist = getDistance(points[i], points[j]);
+ if (dist < 100) {
+ cell.points.push(points[j]);
+ }
+ }
+ cells.push(cell);
+ }
+ return cells;
+ }
+
+ function drawVoronoi(cells) {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ for (const cell of cells) {
+ const { index, points } = cell;
+ const color = `hsl(${(index * 45) % 360}, 80%, 60%)`;
+ const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
+ gradient.addColorStop(0, color);
+ gradient.addColorStop(1, `hsl(${(index * 30) % 360}, 70%, 50%)`);
+ ctx.fillStyle = gradient;
+ 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.closePath();
+ ctx.fill();
+ }
+ }
+
+ function drawPoints() {
+ if (!showPoints) return;
+ ctx.fillStyle = 'white';
+ for (const p of points) {
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, 4, 0, Math.PI * 2);
+ ctx.fill();
+ }
+ }
+
+ function draw() {
+ if (points.length < 2) return;
+ const cells = getVoronoiCells(points);
+ drawVoronoi(cells);
+ drawPoints();
+ }
+
+ canvas.addEventListener('click', (e) => {
+ const x = e.clientX;
+ const y = e.clientY;
+ points.push({ x, y });
+ draw();
+ });
+
+ function addInitialPoints() {
+ const numPoints = 15;
+ for (let i = 0; i < numPoints; i++) {
+ points.push({
+ x: Math.random() * canvas.width,
+ y: Math.random() * canvas.height
+ });
+ }
+ }
+
+ addInitialPoints();
+ draw();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/48026228790d/qwen3-14b.png b/data/artifacts/48026228790d/qwen3-14b.png
new file mode 100644
index 0000000..2ea7518
Binary files /dev/null and b/data/artifacts/48026228790d/qwen3-14b.png differ
diff --git a/data/artifacts/6c13ff8455cc/claude-code.html b/data/artifacts/6c13ff8455cc/claude-code.html
new file mode 100644
index 0000000..55b5eb7
--- /dev/null
+++ b/data/artifacts/6c13ff8455cc/claude-code.html
@@ -0,0 +1,388 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>A* Pathfinding Visualizer</title>
+<style>
+ :root{
+ --bg:#0f1220; --panel:#181c30; --line:#262b47;
+ --accent:#6ea8ff; --text:#e8ecff; --muted:#9aa3c7;
+ }
+ *{box-sizing:border-box; margin:0; padding:0;}
+ body{
+ background:var(--bg); color:var(--text);
+ font-family:-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
+ display:flex; flex-direction:column; align-items:center;
+ min-height:100vh; padding:14px; user-select:none; -webkit-user-select:none;
+ }
+ h1{font-size:18px; font-weight:600; letter-spacing:.5px; margin-bottom:10px;}
+ h1 span{color:var(--accent);}
+ .toolbar{
+ display:flex; flex-wrap:wrap; gap:10px; align-items:center; justify-content:center;
+ background:var(--panel); border:1px solid var(--line); border-radius:10px;
+ padding:10px 14px; margin-bottom:12px; max-width:1000px;
+ }
+ button{
+ background:#232948; color:var(--text); border:1px solid var(--line);
+ border-radius:7px; padding:7px 14px; font-size:13px; cursor:pointer;
+ transition:background .15s, transform .05s;
+ }
+ button:hover{background:#2d3560;}
+ button:active{transform:translateY(1px);}
+ button.primary{background:var(--accent); border-color:var(--accent); color:#0b1020; font-weight:700;}
+ button.primary:hover{background:#8cbaff;}
+ .slider-wrap{display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted);}
+ input[type=range]{width:130px; accent-color:var(--accent);}
+ #status{font-size:12px; color:var(--muted); min-width:150px; text-align:center;}
+ #board{
+ border:1px solid var(--line); border-radius:8px;
+ background:#12162a; touch-action:none; max-width:100%; height:auto; display:block;
+ cursor:crosshair;
+ }
+ .legend{
+ display:flex; flex-wrap:wrap; gap:14px; margin-top:10px; font-size:12px; color:var(--muted);
+ }
+ .legend i{
+ display:inline-block; width:12px; height:12px; border-radius:3px;
+ margin-right:5px; vertical-align:-2px;
+ }
+</style>
+</head>
+<body>
+<h1>A<span>*</span> Pathfinding Visualizer</h1>
+
+<div class="toolbar">
+ <button id="runBtn" class="primary">▶ Visualize</button>
+ <button id="mazeBtn">Random Maze</button>
+ <button id="clearPathBtn">Clear Path</button>
+ <button id="clearBtn">Clear All</button>
+ <div class="slider-wrap">
+ <span>Speed</span>
+ <input type="range" id="speed" min="1" max="100" value="60">
+ </div>
+ <div id="status">Draw walls, drag ● / ★, then Visualize</div>
+</div>
+
+<canvas id="board" width="1000" height="520"></canvas>
+
+<div class="legend">
+ <span><i style="background:#35d07f"></i>Start</span>
+ <span><i style="background:#ff5d73"></i>End</span>
+ <span><i style="background:#3a4166"></i>Wall</span>
+ <span><i style="background:#1e3a6e"></i>Visited</span>
+ <span><i style="background:#2f6fbf"></i>Frontier</span>
+ <span><i style="background:#ffd166"></i>Path</span>
+</div>
+
+<script>
+(function(){
+ const CELL = 20, COLS = 50, ROWS = 26;
+ const canvas = document.getElementById('board');
+ const ctx = canvas.getContext('2d');
+ canvas.width = COLS * CELL; canvas.height = ROWS * CELL;
+
+ const speedEl = document.getElementById('speed');
+ const statusEl = document.getElementById('status');
+
+ // cell states
+ let walls, visited, frontier, path;
+ let start = {c:5, r:13}, end = {c:44, r:13};
+ let animTimer = null, hasResult = false;
+
+ function makeGrid(fill){ return Array.from({length:ROWS}, () => new Array(COLS).fill(fill)); }
+ function resetSearchLayers(){ visited = makeGrid(false); frontier = makeGrid(false); path = makeGrid(false); }
+ function init(){ walls = makeGrid(false); resetSearchLayers(); }
+ init();
+
+ // ---------- drawing ----------
+ function draw(){
+ ctx.fillStyle = '#12162a';
+ ctx.fillRect(0,0,canvas.width,canvas.height);
+ for(let r=0;r<ROWS;r++){
+ for(let c=0;c<COLS;c++){
+ let col = null;
+ if(walls[r][c]) col = '#3a4166';
+ else if(path[r][c]) col = '#ffd166';
+ else if(frontier[r][c]) col = '#2f6fbf';
+ else if(visited[r][c]) col = '#1e3a6e';
+ if(col){
+ ctx.fillStyle = col;
+ ctx.fillRect(c*CELL+1, r*CELL+1, CELL-2, CELL-2);
+ }
+ }
+ }
+ // grid lines
+ ctx.strokeStyle = 'rgba(120,130,180,0.10)';
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ for(let c=0;c<=COLS;c++){ ctx.moveTo(c*CELL+.5,0); ctx.lineTo(c*CELL+.5,canvas.height); }
+ for(let r=0;r<=ROWS;r++){ ctx.moveTo(0,r*CELL+.5); ctx.lineTo(canvas.width,r*CELL+.5); }
+ ctx.stroke();
+ // start / end markers
+ drawMarker(start, '#35d07f');
+ drawMarker(end, '#ff5d73', true);
+ }
+ function drawMarker(p, color, isStar){
+ const x = p.c*CELL + CELL/2, y = p.r*CELL + CELL/2;
+ ctx.fillStyle = color;
+ if(!isStar){
+ ctx.beginPath(); ctx.arc(x,y,CELL*0.34,0,Math.PI*2); ctx.fill();
+ }else{
+ ctx.beginPath();
+ for(let i=0;i<10;i++){
+ const ang = -Math.PI/2 + i*Math.PI/5;
+ const rad = (i%2===0) ? CELL*0.42 : CELL*0.18;
+ const px = x + Math.cos(ang)*rad, py = y + Math.sin(ang)*rad;
+ i===0 ? ctx.moveTo(px,py) : ctx.lineTo(px,py);
+ }
+ ctx.closePath(); ctx.fill();
+ }
+ }
+
+ // ---------- A* ----------
+ function astar(){
+ // returns {steps:[{closed:{r,c}, opened:[{r,c}]}...], path:[{r,c}] or null}
+ const key = (r,c)=> r*COLS+c;
+ const g = new Map(), cameFrom = new Map();
+ const h = (r,c)=> Math.abs(r-end.r) + Math.abs(c-end.c);
+ // binary min-heap on f, tie-break on h
+ const heap = [];
+ function push(node){
+ heap.push(node); let i = heap.length-1;
+ while(i>0){
+ const p = (i-1)>>1;
+ if(heap[p].f < node.f || (heap[p].f===node.f && heap[p].h<=node.h)) break;
+ heap[i]=heap[p]; i=p;
+ }
+ heap[i]=node;
+ }
+ function pop(){
+ const top = heap[0], last = heap.pop();
+ if(heap.length){
+ let i=0; heap[0]=last;
+ for(;;){
+ let l=2*i+1, r=2*i+2, m=i;
+ if(l<heap.length && (heap[l].f<heap[m].f || (heap[l].f===heap[m].f && heap[l].h<heap[m].h))) m=l;
+ if(r<heap.length && (heap[r].f<heap[m].f || (heap[r].f===heap[m].f && heap[r].h<heap[m].h))) m=r;
+ if(m===i) break;
+ [heap[i],heap[m]]=[heap[m],heap[i]]; i=m;
+ }
+ }
+ return top;
+ }
+
+ const closed = new Set();
+ const steps = [];
+ const h0 = h(start.r,start.c);
+ g.set(key(start.r,start.c), 0);
+ push({r:start.r, c:start.c, f:h0, h:h0});
+ const dirs = [[0,1],[1,0],[0,-1],[-1,0]];
+
+ while(heap.length){
+ const cur = pop();
+ const ck = key(cur.r,cur.c);
+ if(closed.has(ck)) continue;
+ closed.add(ck);
+ const opened = [];
+ if(cur.r===end.r && cur.c===end.c){
+ steps.push({closed:{r:cur.r,c:cur.c}, opened});
+ const p = [];
+ let k = ck;
+ while(k !== undefined){
+ p.push({r:Math.floor(k/COLS), c:k%COLS});
+ k = cameFrom.get(k);
+ }
+ p.reverse();
+ return {steps, path:p};
+ }
+ for(const [dr,dc] of dirs){
+ const nr=cur.r+dr, nc=cur.c+dc;
+ if(nr<0||nr>=ROWS||nc<0||nc>=COLS) continue;
+ if(walls[nr][nc]) continue;
+ const nk = key(nr,nc);
+ if(closed.has(nk)) continue;
+ const ng = g.get(ck) + 1;
+ if(ng < (g.get(nk) ?? Infinity)){
+ g.set(nk, ng);
+ cameFrom.set(nk, ck);
+ const nh = h(nr,nc);
+ push({r:nr, c:nc, f:ng+nh, h:nh});
+ opened.push({r:nr, c:nc});
+ }
+ }
+ steps.push({closed:{r:cur.r,c:cur.c}, opened});
+ }
+ return {steps, path:null};
+ }
+
+ // ---------- animation ----------
+ function stopAnim(){ if(animTimer){ cancelAnimationFrame(animTimer); animTimer = null; } }
+
+ function visualize(){
+ stopAnim();
+ resetSearchLayers();
+ hasResult = false;
+ const result = astar();
+ const steps = result.steps;
+ let si = 0, pi = 0, phase = 'search';
+ statusEl.textContent = 'Searching…';
+
+ function frame(){
+ const sp = +speedEl.value;
+ // 1 → ~1 step/frame ; 100 → hundreds/frame
+ const perFrame = Math.max(1, Math.round(Math.pow(sp/18, 2.4)));
+ if(phase==='search'){
+ for(let i=0; i<perFrame && si<steps.length; i++, si++){
+ const s = steps[si];
+ frontier[s.closed.r][s.closed.c] = false;
+ visited[s.closed.r][s.closed.c] = true;
+ for(const o of s.opened) frontier[o.r][o.c] = true;
+ }
+ draw();
+ if(si >= steps.length){
+ if(!result.path){
+ statusEl.textContent = 'No path found 😞 (' + steps.length + ' nodes explored)';
+ hasResult = true; animTimer = null; return;
+ }
+ phase = 'path';
+ }
+ }else{
+ const per = Math.max(1, Math.round(perFrame/3));
+ for(let i=0; i<per && pi<result.path.length; i++, pi++){
+ const p = result.path[pi];
+ path[p.r][p.c] = true;
+ }
+ draw();
+ if(pi >= result.path.length){
+ statusEl.textContent = 'Path: ' + (result.path.length-1) + ' steps · explored ' + steps.length + ' nodes';
+ hasResult = true; animTimer = null; return;
+ }
+ }
+ animTimer = requestAnimationFrame(frame);
+ }
+ animTimer = requestAnimationFrame(frame);
+ }
+
+ // instant recompute (used while dragging start/end after a run)
+ function recomputeInstant(){
+ stopAnim();
+ resetSearchLayers();
+ const result = astar();
+ for(const s of result.steps) visited[s.closed.r][s.closed.c] = true;
+ if(result.path){
+ for(const p of result.path) path[p.r][p.c] = true;
+ statusEl.textContent = 'Path: ' + (result.path.length-1) + ' steps · explored ' + result.steps.length + ' nodes';
+ }else{
+ statusEl.textContent = 'No path found 😞';
+ }
+ draw();
+ }
+
+ // ---------- maze (recursive division) ----------
+ function randomMaze(){
+ stopAnim(); resetSearchLayers(); hasResult = false;
+ walls = makeGrid(false);
+ // border
+ for(let c=0;c<COLS;c++){ walls[0][c]=walls[ROWS-1][c]=true; }
+ for(let r=0;r<ROWS;r++){ walls[r][0]=walls[r][COLS-1]=true; }
+ const rnd = (a,b)=> a + Math.floor(Math.random()*(b-a+1));
+ function divide(r1,c1,r2,c2){
+ const w = c2-c1, hgt = r2-r1;
+ if(w < 3 || hgt < 3) return;
+ const horizontal = hgt > w ? true : (w > hgt ? false : Math.random()<0.5);
+ if(horizontal){
+ const candidates = [];
+ for(let r=r1+2; r<=r2-2; r+=2) candidates.push(r);
+ if(!candidates.length) return;
+ const wr = candidates[rnd(0,candidates.length-1)];
+ const gaps = [];
+ for(let c=c1+1; c<=c2-1; c+=2) gaps.push(c);
+ const gc = gaps[rnd(0,gaps.length-1)];
+ for(let c=c1+1; c<=c2-1; c++) if(c!==gc) walls[wr][c]=true;
+ divide(r1,c1,wr,c2); divide(wr,c1,r2,c2);
+ }else{
+ const candidates = [];
+ for(let c=c1+2; c<=c2-2; c+=2) candidates.push(c);
+ if(!candidates.length) return;
+ const wc = candidates[rnd(0,candidates.length-1)];
+ const gaps = [];
+ for(let r=r1+1; r<=r2-1; r+=2) gaps.push(r);
+ const gr = gaps[rnd(0,gaps.length-1)];
+ for(let r=r1+1; r<=r2-1; r++) if(r!==gr) walls[r][wc]=true;
+ divide(r1,c1,r2,wc); divide(r1,wc,r2,c2);
+ }
+ }
+ divide(0,0,ROWS-1,COLS-1);
+ // keep start/end clear (with a small pocket so they're never sealed)
+ for(const p of [start,end]){
+ for(let dr=-1;dr<=1;dr++)for(let dc=-1;dc<=1;dc++){
+ const r=p.r+dr,c=p.c+dc;
+ if(r>0&&r<ROWS-1&&c>0&&c<COLS-1) walls[r][c]=false;
+ }
+ }
+ statusEl.textContent = 'Maze generated — hit Visualize';
+ draw();
+ }
+
+ // ---------- interaction ----------
+ let dragging = null; // 'wall' | 'erase' | 'start' | 'end'
+ function cellFromEvent(e){
+ const rect = canvas.getBoundingClientRect();
+ const x = (e.clientX - rect.left) * (canvas.width / rect.width);
+ const y = (e.clientY - rect.top) * (canvas.height / rect.height);
+ const c = Math.floor(x / CELL), r = Math.floor(y / CELL);
+ if(r<0||r>=ROWS||c<0||c>=COLS) return null;
+ return {r,c};
+ }
+ function applyDrag(cell){
+ if(!cell) return;
+ if(dragging==='start' || dragging==='end'){
+ const other = dragging==='start' ? end : start;
+ if(walls[cell.r][cell.c] || (cell.r===other.r && cell.c===other.c)) return;
+ const p = dragging==='start' ? start : end;
+ if(p.r===cell.r && p.c===cell.c) return;
+ p.r = cell.r; p.c = cell.c;
+ if(hasResult) recomputeInstant(); else draw();
+ }else{
+ if((cell.r===start.r&&cell.c===start.c)||(cell.r===end.r&&cell.c===end.c)) return;
+ const val = dragging==='wall';
+ if(walls[cell.r][cell.c] !== val){
+ walls[cell.r][cell.c] = val;
+ if(hasResult) recomputeInstant(); else draw();
+ }
+ }
+ }
+ canvas.addEventListener('pointerdown', e => {
+ e.preventDefault();
+ canvas.setPointerCapture(e.pointerId);
+ const cell = cellFromEvent(e);
+ if(!cell) return;
+ stopAnim();
+ if(cell.r===start.r && cell.c===start.c) dragging='start';
+ else if(cell.r===end.r && cell.c===end.c) dragging='end';
+ else dragging = walls[cell.r][cell.c] ? 'erase' : 'wall';
+ applyDrag(cell);
+ });
+ canvas.addEventListener('pointermove', e => {
+ if(!dragging) return;
+ applyDrag(cellFromEvent(e));
+ });
+ window.addEventListener('pointerup', () => { dragging = null; });
+
+ document.getElementById('runBtn').addEventListener('click', visualize);
+ document.getElementById('mazeBtn').addEventListener('click', randomMaze);
+ document.getElementById('clearPathBtn').addEventListener('click', () => {
+ stopAnim(); resetSearchLayers(); hasResult=false;
+ statusEl.textContent = 'Path cleared'; draw();
+ });
+ document.getElementById('clearBtn').addEventListener('click', () => {
+ stopAnim(); init(); hasResult=false;
+ statusEl.textContent = 'Board cleared'; draw();
+ });
+
+ draw();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6c13ff8455cc/claude-code.png b/data/artifacts/6c13ff8455cc/claude-code.png
new file mode 100644
index 0000000..ccc9974
Binary files /dev/null and b/data/artifacts/6c13ff8455cc/claude-code.png differ
diff --git a/data/artifacts/6c13ff8455cc/gemma3-12b.html b/data/artifacts/6c13ff8455cc/gemma3-12b.html
new file mode 100644
index 0000000..d7235b7
--- /dev/null
+++ b/data/artifacts/6c13ff8455cc/gemma3-12b.html
@@ -0,0 +1,324 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>A* Pathfinding Visualizer</title>
+<style>
+body { margin: 0; overflow: hidden; }
+#container { width: 100vw; height: 100vh; display: flex; flex-direction: column; }
+#controls { background-color: #f0f0f0; padding: 10px; flex-direction: row; align-items: center; justify-content: space-around; }
+#grid-container { width: 80vw; height: calc(100vh - 50px); display: grid; grid-template-columns: repeat(30, 20px); }
+.cell { width: 20px; height: 20px; border: 1px solid #ccc; box-sizing: border-box; }
+.wall { background-color: black; }
+.start { background-color: green; }
+.end { background-color: red; }
+.open { background-color: lightblue; }
+.closed { background-color: gray; }
+.path { background-color: yellow; }
+
+#speed-slider-container { display: flex; align-items: center; }
+#speed-slider { width: 150px; }
+</style>
+</head>
+<body>
+<div id="container">
+ <div id="controls">
+ <div>Start: <span id="startNode"></span> End: <span id="endNode"></span></div>
+ <div id="speed-slider-container">Speed: <input type="range" id="speedSlider" min="1" max="100" value="50"></div>
+ <button id="clearButton">Clear</button>
+ <button id="randomMazeButton">Random Maze</button>
+ </div>
+ <div id="grid-container"></div>
+</div>
+
+<script>
+const gridContainer = document.getElementById('grid-container');
+let grid = [];
+let startNode = null;
+let endNode = null;
+let cellSize = 20; // Adjust for desired resolution
+let animationSpeed = 50; // Default speed
+let drawingMode = false;
+
+function createGrid(rows, cols) {
+ grid = [];
+ for (let i = 0; i < rows; i++) {
+ grid[i] = [];
+ for (let j = 0; j < cols; j++) {
+ grid[i][j] = {
+ x: j,
+ y: i,
+ wall: false,
+ f: 0,
+ g: 0,
+ h: 0,
+ visited: false,
+ previous: null
+ };
+ }
+ }
+
+ const rowsCount = Math.floor((window.innerHeight - 50) / cellSize);
+ const colsCount = Math.floor(window.innerWidth / cellSize);
+
+ gridContainer.style.gridTemplateColumns = `repeat(${colsCount}, ${cellSize}px)`;
+ gridContainer.innerHTML = '';
+
+ for (let i = 0; i < rowsCount; i++) {
+ for (let j = 0; j < colsCount; j++) {
+ const cell = document.createElement('div');
+ cell.classList.add('cell');
+ cell.dataset.x = j;
+ cell.dataset.y = i;
+ grid[i][j] = {...grid[i][j]}; // Ensure each cell has its own object reference
+ gridContainer.appendChild(cell);
+ }
+ }
+
+ updateGridVisuals();
+}
+
+
+function updateGridVisuals() {
+ for (let i = 0; i < grid.length; i++) {
+ for (let j = 0; j < grid[i].length; j++) {
+ const cell = gridContainer.querySelector(`[data-x="${j}"][data-y="${i}"]`);
+ if (grid[i][j].wall) {
+ cell.classList.add('wall');
+ } else {
+ cell.classList.remove('wall');
+ }
+
+ if (startNode && grid[i][j] === startNode) {
+ cell.classList.add('start');
+ } else {
+ cell.classList.remove('start');
+ }
+
+ if (endNode && grid[i][j] === endNode) {
+ cell.classList.add('end');
+ } else {
+ cell.classList.remove('end');
+ }
+
+ if(grid[i][j].visited){
+ cell.classList.add('closed');
+ }else{
+ cell.classList.remove('closed');
+ }
+
+ if (grid[i][j].f > 0) { // Open set
+ cell.classList.add('open');
+ } else {
+ cell.classList.remove('open');
+ }
+
+ if(grid[i][j].previous){
+ cell.classList.add('path');
+ }else{
+ cell.classList.remove('path');
+ }
+ }
+ }
+}
+
+
+function drawWall(event) {
+ const x = Math.floor(event.offsetX / cellSize);
+ const y = Math.floor(event.offsetY / cellSize);
+
+ if (x >= 0 && x < grid[0].length && y >= 0 && y < grid.length) {
+ grid[y][x].wall = true;
+ updateGridVisuals();
+ }
+}
+
+
+function setStartNode(event) {
+ const x = Math.floor(event.offsetX / cellSize);
+ const y = Math.floor(event.offsetY / cellSize);
+
+ if (x >= 0 && x < grid[0].length && y >= 0 && y < grid.length && !grid[y][x].wall) {
+ if(startNode){
+ startNode.visited=false;
+ startNode.previous = null;
+ }
+
+ startNode = grid[y][x];
+ updateGridVisuals();
+ }
+}
+
+
+function setEndNode(event) {
+ const x = Math.floor(event.offsetX / cellSize);
+ const y = Math.floor(event.offsetY / cellSize);
+
+ if (x >= 0 && x < grid[0].length && y >= 0 && y < grid.length && !grid[y][x].wall) {
+ if(endNode){
+ endNode.visited=false;
+ endNode.previous = null;
+ }
+
+ endNode = grid[y][x];
+ updateGridVisuals();
+ }
+}
+
+
+function heuristic(a, b) {
+ return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
+}
+
+
+
+function aStar() {
+ const openList = [startNode];
+ startNode.g = 0;
+ startNode.h = heuristic(startNode, endNode);
+ startNode.f = startNode.g + startNode.h;
+
+ while (openList.length > 0) {
+ // Find node with lowest f score
+ let currentNode = openList[0];
+ for (let i = 1; i < openList.length; i++) {
+ if (openList[i].f < currentNode.f) {
+ currentNode = openList[i];
+ }
+ }
+
+ if(currentNode === endNode){
+ break;
+ }
+
+
+ openList.splice(openList.indexOf(currentNode), 1);
+ currentNode.visited=true;
+
+ const neighbors = [
+ { x: currentNode.x + 1, y: currentNode.y },
+ { x: currentNode.x - 1, y: currentNode.y },
+ { x: currentNode.x, y: currentNode.y + 1 },
+ { x: currentNode.x, y: currentNode.y - 1 }
+ ];
+
+ for (const neighbor of neighbors) {
+ if (neighbor.x >= 0 && neighbor.x < grid[0].length && neighbor.y >= 0 && neighbor.y < grid.length && !grid[neighbor.y][neighbor.x].wall) {
+ let tentativeGScore = currentNode.g + 1;
+
+ if(openList.includes(grid[neighbor.y][neighbor.x]) || (grid[neighbor.y][neighbor.x].previous === null)){
+ if (tentativeGScore < grid[neighbor.y][neighbor.x].g) {
+ grid[neighbor.y][neighbor.x].g = tentativeGScore;
+ grid[neighbor.y][neighbor.x].previous = currentNode;
+
+ grid[neighbor.y][neighbor.x].h = heuristic(grid[neighbor.y][neighbor.x], endNode);
+ grid[neighbor.y][neighbor.x].f = grid[neighbor.y][neighbor.x].g + grid[neighbor.y][neighbor.x].h;
+
+ }
+ }else{
+ grid[neighbor.y][neighbor.x].g = tentativeGScore;
+ grid[neighbor.y][neighbor.x].previous = currentNode;
+ grid[neighbor.y][neighbor.x].h = heuristic(grid[neighbor.y][neighbor.x], endNode);
+ grid[neighbor.y][neighbor.x].f = grid[neighbor.y][neighbor.x].g + grid[neighbor.y][neighbor.x].h;
+ openList.push(grid[neighbor.y][neighbor.x]);
+
+ }
+ }
+ }
+ }
+
+ // Reconstruct path
+ let current = endNode;
+ while (current !== null) {
+ if(current !== startNode){
+ current.previous.path = true;
+ }
+
+ current = current.previous;
+ }
+
+ updateGridVisuals();
+}
+
+function clearGrid() {
+ createGrid(rows, cols);
+ startNode = null;
+ endNode = null;
+}
+
+function generateRandomMaze(density = 0.3) {
+ const rows = Math.floor((window.innerHeight - 50) / cellSize);
+ const cols = Math.floor(window.innerWidth / cellSize);
+ createGrid(rows,cols);
+
+ for (let i = 0; i < rows; i++) {
+ for (let j = 0; j < cols; j++) {
+ if (Math.random() < density) {
+ grid[i][j].wall = true;
+ } else {
+ grid[i][j].wall = false;
+ }
+ }
+ }
+
+ //Ensure start and end are not walls
+ let startX = Math.floor(Math.random() * cols);
+ let startY = Math.floor(Math.random() * rows);
+ while (grid[startY][startX].wall) {
+ startX = Math.floor(Math.random() * cols);
+ startY = Math.floor(Math.random() * rows);
+ }
+
+ startNode = grid[startY][startX];
+
+
+ let endX = Math.floor(Math.random() * cols);
+ let endY = Math.floor(Math.random() * rows);
+ while (grid[endY][endX].wall || (endX === startX && endY === startY)) {
+ endX = Math.floor(Math.random() * cols);
+ endY = Math.floor(Math.random() * rows);
+ }
+
+ endNode = grid[endY][endX];
+
+
+
+ updateGridVisuals();
+}
+
+
+const rows = Math.floor((window.innerHeight - 50) / cellSize);
+const cols = Math.floor(window.innerWidth / cellSize);
+
+createGrid(rows, cols);
+
+gridContainer.addEventListener('mousedown', (event) => {
+ drawingMode = true;
+ setStartNode(event);
+});
+
+gridContainer.addEventListener('mouseup', () => {
+ drawingMode = false;
+});
+
+gridContainer.addEventListener('mouseover', (event) => {
+ if(drawingMode){
+ drawWall(event);
+ }else{
+ setEndNode(event);
+ }
+});
+
+
+document.getElementById('clearButton').addEventListener('click', clearGrid);
+
+document.getElementById('randomMazeButton').addEventListener('click', generateRandomMaze);
+
+document.getElementById('speedSlider').addEventListener('input', (event) => {
+ animationSpeed = event.target.value;
+});
+
+
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6c13ff8455cc/gemma3-12b.png b/data/artifacts/6c13ff8455cc/gemma3-12b.png
new file mode 100644
index 0000000..bae185c
Binary files /dev/null and b/data/artifacts/6c13ff8455cc/gemma3-12b.png differ
diff --git a/data/artifacts/6c13ff8455cc/gpt.html b/data/artifacts/6c13ff8455cc/gpt.html
new file mode 100644
index 0000000..c8fffed
--- /dev/null
+++ b/data/artifacts/6c13ff8455cc/gpt.html
@@ -0,0 +1,602 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<title>A* Pathfinding Visualizer</title>
+<style>
+ :root {
+ color-scheme: light dark;
+ --bg: #111;
+ --fg: #f5f5f5;
+ --accent: #4caf50;
+ --accent2: #2196f3;
+ --wall: #333;
+ --start: #4caf50;
+ --end: #e91e63;
+ --open: #00bcd4;
+ --closed: #ff9800;
+ --path: #ffff00;
+ --grid-border: #222;
+ }
+
+ * {
+ box-sizing: border-box;
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ }
+
+ body {
+ margin: 0;
+ background: radial-gradient(circle at top, #333 0, #050505 55%);
+ color: var(--fg);
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ overflow: hidden;
+ }
+
+ header {
+ padding: 8px 12px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ background: rgba(0,0,0,0.7);
+ border-bottom: 1px solid #333;
+ font-size: 14px;
+ }
+
+ header h1 {
+ font-size: 16px;
+ margin: 0 8px 0 0;
+ font-weight: 600;
+ letter-spacing: 0.03em;
+ color: var(--accent2);
+ }
+
+ .controls {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ align-items: center;
+ }
+
+ button {
+ background: #1e1e1e;
+ border: 1px solid #555;
+ color: var(--fg);
+ padding: 4px 10px;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 12px;
+ transition: background 0.15s, transform 0.05s, border-color 0.15s;
+ }
+
+ button:hover {
+ background: #2b2b2b;
+ }
+
+ button:active {
+ transform: scale(0.97);
+ }
+
+ button.primary {
+ background: var(--accent2);
+ border-color: var(--accent2);
+ color: #fff;
+ }
+
+ button.primary:hover {
+ background: #1b7cd1;
+ }
+
+ button.danger {
+ background: #b71c1c;
+ border-color: #e53935;
+ }
+
+ button.danger:hover {
+ background: #c62828;
+ }
+
+ label {
+ font-size: 12px;
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ }
+
+ input[type="range"] {
+ accent-color: var(--accent2);
+ }
+
+ .mode-group {
+ display: inline-flex;
+ border-radius: 999px;
+ border: 1px solid #444;
+ overflow: hidden;
+ }
+
+ .mode-btn {
+ border-radius: 0;
+ border: none;
+ padding: 4px 8px;
+ background: transparent;
+ border-right: 1px solid #444;
+ font-size: 11px;
+ }
+
+ .mode-btn:last-child {
+ border-right: none;
+ }
+
+ .mode-btn.active {
+ background: var(--accent2);
+ color: #fff;
+ }
+
+ .legend {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+ font-size: 11px;
+ margin-left: auto;
+ flex-wrap: wrap;
+ }
+
+ .legend-item {
+ display: inline-flex;
+ align-items: center;
+ gap: 3px;
+ }
+
+ .legend-swatch {
+ width: 14px;
+ height: 14px;
+ border-radius: 3px;
+ border: 1px solid #111;
+ }
+
+ .sw-start { background: var(--start); }
+ .sw-end { background: var(--end); }
+ .sw-wall { background: var(--wall); }
+ .sw-open { background: var(--open); }
+ .sw-closed { background: var(--closed); }
+ .sw-path { background: var(--path); }
+
+ main {
+ flex: 1;
+ display: flex;
+ padding: 8px;
+ }
+
+ .grid-container {
+ margin: auto;
+ max-width: 100%;
+ max-height: calc(100vh - 60px);
+ aspect-ratio: 1 / 1;
+ background: #000;
+ border-radius: 8px;
+ padding: 6px;
+ box-shadow: 0 0 0 1px #444, 0 0 18px rgba(0,0,0,0.7);
+ display: flex;
+ }
+
+ #grid {
+ flex: 1;
+ display: grid;
+ background: #050505;
+ border-radius: 4px;
+ overflow: hidden;
+ }
+
+ .cell {
+ border: 1px solid var(--grid-border);
+ background: #111;
+ transition: background 0.08s linear, transform 0.05s;
+ }
+
+ .cell.wall {
+ background: var(--wall);
+ }
+
+ .cell.start {
+ background: var(--start);
+ }
+
+ .cell.end {
+ background: var(--end);
+ }
+
+ .cell.open {
+ background: radial-gradient(circle at center, var(--open) 0, #004b5a 90%);
+ }
+
+ .cell.closed {
+ background: radial-gradient(circle at center, var(--closed) 0, #5d2900 90%);
+ }
+
+ .cell.path {
+ background: linear-gradient(135deg, #fffacd, var(--path));
+ }
+
+ .cell.visited-once {
+ transform: scale(1.02);
+ }
+
+ @media (max-width: 720px) {
+ header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 6px;
+ }
+ .legend {
+ margin-left: 0;
+ }
+ }
+</style>
+</head>
+<body>
+<header>
+ <h1>A* Pathfinding</h1>
+ <div class="controls">
+ <button id="startBtn" class="primary">Start</button>
+ <button id="clearPathBtn">Clear Path</button>
+ <button id="clearAllBtn" class="danger">Clear All</button>
+ <button id="randomMazeBtn">Random Maze</button>
+
+ <label>
+ Speed
+ <input id="speedSlider" type="range" min="1" max="120" value="60">
+ </label>
+
+ <div class="mode-group" id="modeGroup">
+ <button class="mode-btn active" data-mode="wall">Walls</button>
+ <button class="mode-btn" data-mode="start">Start</button>
+ <button class="mode-btn" data-mode="end">End</button>
+ </div>
+ </div>
+
+ <div class="legend">
+ <div class="legend-item"><span class="legend-swatch sw-start"></span>Start</div>
+ <div class="legend-item"><span class="legend-swatch sw-end"></span>End</div>
+ <div class="legend-item"><span class="legend-swatch sw-wall"></span>Wall</div>
+ <div class="legend-item"><span class="legend-swatch sw-open"></span>Open</div>
+ <div class="legend-item"><span class="legend-swatch sw-closed"></span>Closed</div>
+ <div class="legend-item"><span class="legend-swatch sw-path"></span>Path</div>
+ </div>
+</header>
+
+<main>
+ <div class="grid-container">
+ <div id="grid"></div>
+ </div>
+</main>
+
+<script>
+(function() {
+ const ROWS = 30;
+ const COLS = 30;
+
+ const gridEl = document.getElementById('grid');
+ const startBtn = document.getElementById('startBtn');
+ const clearAllBtn = document.getElementById('clearAllBtn');
+ const clearPathBtn = document.getElementById('clearPathBtn');
+ const randomMazeBtn = document.getElementById('randomMazeBtn');
+ const speedSlider = document.getElementById('speedSlider');
+ const modeGroup = document.getElementById('modeGroup');
+
+ let mode = 'wall';
+ let isMouseDown = false;
+ let dragType = null;
+ let running = false;
+ let stopRequested = false;
+
+ gridEl.style.gridTemplateRows = `repeat(${ROWS}, 1fr)`;
+ gridEl.style.gridTemplateColumns = `repeat(${COLS}, 1fr)`;
+
+ const grid = [];
+ for (let r = 0; r < ROWS; r++) {
+ const row = [];
+ for (let c = 0; c < COLS; c++) {
+ const cellEl = document.createElement('div');
+ cellEl.className = 'cell';
+ cellEl.dataset.row = r;
+ cellEl.dataset.col = c;
+ gridEl.appendChild(cellEl);
+ row.push({
+ row: r,
+ col: c,
+ wall: false,
+ start: false,
+ end: false,
+ open: false,
+ closed: false,
+ path: false,
+ el: cellEl,
+ g: Infinity,
+ h: 0,
+ f: Infinity,
+ parent: null,
+ });
+ }
+ grid.push(row);
+ }
+
+ let startNode = grid[Math.floor(ROWS/2)][Math.floor(COLS/4)];
+ let endNode = grid[Math.floor(ROWS/2)][Math.floor(3*COLS/4)];
+ startNode.start = true;
+ endNode.end = true;
+ startNode.el.classList.add('start');
+ endNode.el.classList.add('end');
+
+ function resetPathOnly() {
+ for (let r = 0; r < ROWS; r++) {
+ for (let c = 0; c < COLS; c++) {
+ const n = grid[r][c];
+ n.open = n.closed = n.path = false;
+ n.g = Infinity;
+ n.h = 0;
+ n.f = Infinity;
+ n.parent = null;
+ n.el.classList.remove('open','closed','path','visited-once');
+ }
+ }
+ }
+
+ function resetAll() {
+ stopRequested = true;
+ running = false;
+ startBtn.textContent = 'Start';
+ for (let r = 0; r < ROWS; r++) {
+ for (let c = 0; c < COLS; c++) {
+ const n = grid[r][c];
+ n.wall = n.start = n.end = n.open = n.closed = n.path = false;
+ n.g = Infinity;
+ n.h = 0;
+ n.f = Infinity;
+ n.parent = null;
+ n.el.className = 'cell';
+ }
+ }
+ startNode = grid[Math.floor(ROWS/2)][Math.floor(COLS/4)];
+ endNode = grid[Math.floor(ROWS/2)][Math.floor(3*COLS/4)];
+ startNode.start = true;
+ endNode.end = true;
+ startNode.el.classList.add('start');
+ endNode.el.classList.add('end');
+ }
+
+ function heuristic(a, b) {
+ return Math.abs(a.row - b.row) + Math.abs(a.col - b.col);
+ }
+
+ function neighbors(node) {
+ const res = [];
+ const {row, col} = node;
+ if (row > 0) res.push(grid[row-1][col]);
+ if (row < ROWS-1) res.push(grid[row+1][col]);
+ if (col > 0) res.push(grid[row][col-1]);
+ if (col < COLS-1) res.push(grid[row][col+1]);
+ return res;
+ }
+
+ function reconstructPath(end) {
+ let cur = end;
+ const path = [];
+ while (cur) {
+ path.push(cur);
+ cur = cur.parent;
+ }
+ path.reverse();
+ for (const node of path) {
+ if (!node.start && !node.end) {
+ node.path = true;
+ node.el.classList.add('path');
+ }
+ }
+ }
+
+ async function sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ function getDelayFromSpeed() {
+ const v = Number(speedSlider.value);
+ const max = Number(speedSlider.max);
+ const min = Number(speedSlider.min);
+ const norm = (v - min) / (max - min + 0.0001);
+ const minDelay = 0;
+ const maxDelay = 80;
+ return Math.round(maxDelay * (1 - norm) + minDelay * norm);
+ }
+
+ async function runAStar() {
+ resetPathOnly();
+ running = true;
+ stopRequested = false;
+ startBtn.textContent = 'Stop';
+
+ const openSet = [];
+ const inOpen = new Set();
+
+ startNode.g = 0;
+ startNode.h = heuristic(startNode, endNode);
+ startNode.f = startNode.h;
+ openSet.push(startNode);
+ inOpen.add(startNode);
+
+ let found = false;
+
+ while (openSet.length && !stopRequested) {
+ let bestIdx = 0;
+ for (let i = 1; i < openSet.length; i++) {
+ if (openSet[i].f < openSet[bestIdx].f ||
+ (openSet[i].f === openSet[bestIdx].f && openSet[i].h < openSet[bestIdx].h)) {
+ bestIdx = i;
+ }
+ }
+ const current = openSet.splice(bestIdx, 1)[0];
+ inOpen.delete(current);
+
+ if (!current.start && !current.end) {
+ current.closed = true;
+ current.el.classList.remove('open');
+ current.el.classList.add('closed','visited-once');
+ }
+
+ if (current === endNode) {
+ found = true;
+ break;
+ }
+
+ const delay = getDelayFromSpeed();
+ if (delay > 0) await sleep(delay);
+
+ for (const nb of neighbors(current)) {
+ if (nb.wall || nb.closed) continue;
+ const tentativeG = current.g + 1;
+ if (tentativeG < nb.g) {
+ nb.parent = current;
+ nb.g = tentativeG;
+ nb.h = heuristic(nb, endNode);
+ nb.f = nb.g + nb.h;
+ if (!inOpen.has(nb)) {
+ openSet.push(nb);
+ inOpen.add(nb);
+ if (!nb.start && !nb.end) {
+ nb.open = true;
+ nb.el.classList.add('open');
+ }
+ }
+ }
+ }
+ }
+
+ if (found && !stopRequested) {
+ reconstructPath(endNode);
+ }
+
+ running = false;
+ startBtn.textContent = 'Start';
+ stopRequested = false;
+ }
+
+ function handleCellAction(node) {
+ if (running) return;
+ if (mode === 'wall') {
+ if (node.start || node.end) return;
+ node.wall = !node.wall;
+ node.el.classList.toggle('wall', node.wall);
+ } else if (mode === 'start') {
+ if (node.end) return;
+ startNode.start = false;
+ startNode.el.classList.remove('start');
+ startNode = node;
+ node.start = true;
+ node.wall = false;
+ node.el.classList.remove('wall','end');
+ node.el.classList.add('start');
+ } else if (mode === 'end') {
+ if (node.start) return;
+ endNode.end = false;
+ endNode.el.classList.remove('end');
+ endNode = node;
+ node.end = true;
+ node.wall = false;
+ node.el.classList.remove('wall','start');
+ node.el.classList.add('end');
+ }
+ resetPathOnly();
+ }
+
+ gridEl.addEventListener('mousedown', e => {
+ if (!(e.target && e.target.classList.contains('cell'))) return;
+ isMouseDown = true;
+ const r = Number(e.target.dataset.row);
+ const c = Number(e.target.dataset.col);
+ const node = grid[r][c];
+ dragType = mode === 'wall' ? (!node.wall ? 'wall-on' : 'wall-off') : mode;
+ if (dragType === 'wall-on') {
+ if (!node.start && !node.end) {
+ node.wall = true;
+ node.el.classList.add('wall');
+ }
+ } else if (dragType === 'wall-off') {
+ node.wall = false;
+ node.el.classList.remove('wall');
+ } else if (dragType === 'start' || dragType === 'end') {
+ handleCellAction(node);
+ }
+ });
+
+ gridEl.addEventListener('mouseover', e => {
+ if (!isMouseDown) return;
+ if (!(e.target && e.target.classList.contains('cell'))) return;
+ const r = Number(e.target.dataset.row);
+ const c = Number(e.target.dataset.col);
+ const node = grid[r][c];
+
+ if (dragType === 'wall-on') {
+ if (!node.start && !node.end) {
+ node.wall = true;
+ node.el.classList.add('wall');
+ }
+ } else if (dragType === 'wall-off') {
+ node.wall = false;
+ node.el.classList.remove('wall');
+ } else if (dragType === 'start' || dragType === 'end') {
+ handleCellAction(node);
+ }
+ });
+
+ window.addEventListener('mouseup', () => {
+ isMouseDown = false;
+ dragType = null;
+ });
+
+ modeGroup.addEventListener('click', e => {
+ const btn = e.target.closest('.mode-btn');
+ if (!btn) return;
+ mode = btn.dataset.mode;
+ for (const b of modeGroup.querySelectorAll('.mode-btn')) {
+ b.classList.toggle('active', b === btn);
+ }
+ });
+
+ startBtn.addEventListener('click', () => {
+ if (!running) {
+ runAStar();
+ } else {
+ stopRequested = true;
+ running = false;
+ startBtn.textContent = 'Start';
+ }
+ });
+
+ clearAllBtn.addEventListener('click', () => {
+ resetAll();
+ });
+
+ clearPathBtn.addEventListener('click', () => {
+ resetPathOnly();
+ });
+
+ randomMazeBtn.addEventListener('click', () => {
+ if (running) return;
+ resetAll();
+ const density = 0.32;
+ for (let r = 0; r < ROWS; r++) {
+ for (let c = 0; c < COLS; c++) {
+ const node = grid[r][c];
+ if (node.start || node.end) continue;
+ if (Math.random() < density) {
+ node.wall = true;
+ node.el.classList.add('wall');
+ }
+ }
+ }
+ });
+
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6c13ff8455cc/gpt.png b/data/artifacts/6c13ff8455cc/gpt.png
new file mode 100644
index 0000000..b41762e
Binary files /dev/null and b/data/artifacts/6c13ff8455cc/gpt.png differ
diff --git a/data/artifacts/6c13ff8455cc/grok.html b/data/artifacts/6c13ff8455cc/grok.html
new file mode 100644
index 0000000..72f1256
--- /dev/null
+++ b/data/artifacts/6c13ff8455cc/grok.html
@@ -0,0 +1,596 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>A* Pathfinding Visualizer</title>
+<style>
+ :root {
+ --bg: #0f1419;
+ --panel: #1a2332;
+ --border: #2d3a4f;
+ --text: #e7ecf3;
+ --muted: #8b9bb4;
+ --accent: #3b82f6;
+ --start: #22c55e;
+ --end: #ef4444;
+ --wall: #1e293b;
+ --wall-edge: #334155;
+ --open: #1d4ed8;
+ --closed: #7c3aed;
+ --path: #fbbf24;
+ --grid-line: #243044;
+ }
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ body {
+ font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
+ background: var(--bg);
+ color: var(--text);
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 16px;
+ gap: 12px;
+ }
+ h1 {
+ font-size: 1.35rem;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ color: var(--text);
+ }
+ h1 span { color: var(--accent); }
+ .toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ align-items: center;
+ justify-content: center;
+ background: var(--panel);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ padding: 10px 14px;
+ max-width: 960px;
+ width: 100%;
+ }
+ .btn {
+ appearance: none;
+ border: 1px solid var(--border);
+ background: #243044;
+ color: var(--text);
+ padding: 8px 14px;
+ border-radius: 8px;
+ font-size: 0.85rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 0.15s, border-color 0.15s, transform 0.1s;
+ }
+ .btn:hover:not(:disabled) {
+ background: #2d3f5c;
+ border-color: var(--accent);
+ }
+ .btn:active:not(:disabled) { transform: scale(0.97); }
+ .btn:disabled { opacity: 0.45; cursor: not-allowed; }
+ .btn.primary { background: var(--accent); border-color: #2563eb; color: #fff; }
+ .btn.primary:hover:not(:disabled) { background: #2563eb; }
+ .btn.danger { background: #7f1d1d; border-color: #991b1b; }
+ .btn.danger:hover:not(:disabled) { background: #991b1b; }
+ .btn.active {
+ outline: 2px solid var(--accent);
+ outline-offset: 1px;
+ }
+ .mode-group, .slider-group {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+ .mode-group label, .slider-group label {
+ font-size: 0.8rem;
+ color: var(--muted);
+ white-space: nowrap;
+ }
+ .divider {
+ width: 1px;
+ height: 28px;
+ background: var(--border);
+ margin: 0 4px;
+ }
+ input[type="range"] {
+ width: 100px;
+ accent-color: var(--accent);
+ cursor: pointer;
+ }
+ .status {
+ font-size: 0.8rem;
+ color: var(--muted);
+ min-height: 1.2em;
+ text-align: center;
+ }
+ .status strong { color: var(--text); }
+ .canvas-wrap {
+ background: var(--panel);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ padding: 8px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.35);
+ }
+ canvas {
+ display: block;
+ border-radius: 6px;
+ cursor: crosshair;
+ background: #111827;
+ }
+ .legend {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px 18px;
+ justify-content: center;
+ font-size: 0.75rem;
+ color: var(--muted);
+ }
+ .legend span {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ }
+ .swatch {
+ width: 14px;
+ height: 14px;
+ border-radius: 3px;
+ border: 1px solid rgba(255,255,255,0.12);
+ }
+</style>
+</head>
+<body>
+ <h1><span>A*</span> Pathfinding Visualizer</h1>
+ <div class="toolbar">
+ <div class="mode-group">
+ <label>Draw:</label>
+ <button class="btn active" id="btnWall" data-mode="wall">Wall</button>
+ <button class="btn" id="btnStart" data-mode="start">Start</button>
+ <button class="btn" id="btnEnd" data-mode="end">End</button>
+ <button class="btn" id="btnErase" data-mode="erase">Erase</button>
+ </div>
+ <div class="divider"></div>
+ <button class="btn primary" id="btnRun">Run A*</button>
+ <button class="btn" id="btnStop" disabled>Stop</button>
+ <button class="btn" id="btnClear">Clear</button>
+ <button class="btn" id="btnMaze">Random Maze</button>
+ <div class="divider"></div>
+ <div class="slider-group">
+ <label for="speed">Speed</label>
+ <input type="range" id="speed" min="1" max="100" value="60">
+ </div>
+ </div>
+ <div class="status" id="status">Draw walls, set start & end, then run A*.</div>
+ <div class="canvas-wrap">
+ <canvas id="grid"></canvas>
+ </div>
+ <div class="legend">
+ <span><i class="swatch" style="background:#22c55e"></i>Start</span>
+ <span><i class="swatch" style="background:#ef4444"></i>End</span>
+ <span><i class="swatch" style="background:#334155"></i>Wall</span>
+ <span><i class="swatch" style="background:#3b82f6"></i>Open</span>
+ <span><i class="swatch" style="background:#7c3aed"></i>Closed</span>
+ <span><i class="swatch" style="background:#fbbf24"></i>Path</span>
+ </div>
+<script>
+(function () {
+ const COLS = 40;
+ const ROWS = 28;
+ const CELL = 18;
+
+ const canvas = document.getElementById("grid");
+ const ctx = canvas.getContext("2d");
+ canvas.width = COLS * CELL;
+ canvas.height = ROWS * CELL;
+
+ const statusEl = document.getElementById("status");
+ const speedEl = document.getElementById("speed");
+ const btnRun = document.getElementById("btnRun");
+ const btnStop = document.getElementById("btnStop");
+ const btnClear = document.getElementById("btnClear");
+ const btnMaze = document.getElementById("btnMaze");
+
+ const EMPTY = 0, WALL = 1, START = 2, END = 3;
+ const VIS_NONE = 0, VIS_OPEN = 1, VIS_CLOSED = 2, VIS_PATH = 3;
+
+ let grid = [];
+ let vis = [];
+ let start = { x: 3, y: 14 };
+ let end = { x: 36, y: 14 };
+ let mode = "wall";
+ let drawing = false;
+ let running = false;
+ let abort = false;
+ let pathCells = [];
+
+ function initGrid() {
+ grid = Array.from({ length: ROWS }, () => Array(COLS).fill(EMPTY));
+ vis = Array.from({ length: ROWS }, () => Array(COLS).fill(VIS_NONE));
+ grid[start.y][start.x] = START;
+ grid[end.y][end.x] = END;
+ pathCells = [];
+ }
+
+ function clearVis() {
+ vis = Array.from({ length: ROWS }, () => Array(COLS).fill(VIS_NONE));
+ pathCells = [];
+ }
+
+ function cellColor(x, y) {
+ const t = grid[y][x];
+ if (t === START) return "#22c55e";
+ if (t === END) return "#ef4444";
+ if (t === WALL) return "#334155";
+ const v = vis[y][x];
+ if (v === VIS_PATH) return "#fbbf24";
+ if (v === VIS_CLOSED) return "#6d28d9";
+ if (v === VIS_OPEN) return "#2563eb";
+ return "#111827";
+ }
+
+ function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ for (let y = 0; y < ROWS; y++) {
+ for (let x = 0; x < COLS; x++) {
+ const px = x * CELL;
+ const py = y * CELL;
+ ctx.fillStyle = cellColor(x, y);
+ ctx.fillRect(px, py, CELL, CELL);
+ if (vis[y][x] === VIS_OPEN && grid[y][x] === EMPTY) {
+ ctx.fillStyle = "rgba(96,165,250,0.35)";
+ ctx.fillRect(px + 1, py + 1, CELL - 2, CELL - 2);
+ }
+ if (vis[y][x] === VIS_PATH && grid[y][x] === EMPTY) {
+ ctx.fillStyle = "#f59e0b";
+ ctx.beginPath();
+ ctx.arc(px + CELL / 2, py + CELL / 2, CELL * 0.22, 0, Math.PI * 2);
+ ctx.fill();
+ }
+ ctx.strokeStyle = "#1f2937";
+ ctx.lineWidth = 1;
+ ctx.strokeRect(px + 0.5, py + 0.5, CELL - 1, CELL - 1);
+ }
+ }
+ // highlight start/end with ring
+ [[start, "#86efac"], [end, "#fca5a5"]].forEach(([p, c]) => {
+ ctx.strokeStyle = c;
+ ctx.lineWidth = 2;
+ ctx.strokeRect(p.x * CELL + 2, p.y * CELL + 2, CELL - 4, CELL - 4);
+ });
+ }
+
+ function posFromEvent(e) {
+ const r = canvas.getBoundingClientRect();
+ const scaleX = canvas.width / r.width;
+ const scaleY = canvas.height / r.height;
+ const x = Math.floor(((e.clientX - r.left) * scaleX) / CELL);
+ const y = Math.floor(((e.clientY - r.top) * scaleY) / CELL);
+ if (x < 0 || y < 0 || x >= COLS || y >= ROWS) return null;
+ return { x, y };
+ }
+
+ function applyDraw(p) {
+ if (!p || running) return;
+ clearVis();
+ if (mode === "wall") {
+ if (grid[p.y][p.x] === EMPTY) grid[p.y][p.x] = WALL;
+ } else if (mode === "erase") {
+ if (grid[p.y][p.x] === WALL) grid[p.y][p.x] = EMPTY;
+ } else if (mode === "start") {
+ if (grid[p.y][p.x] === END) return;
+ grid[start.y][start.x] = EMPTY;
+ start = { x: p.x, y: p.y };
+ grid[start.y][start.x] = START;
+ } else if (mode === "end") {
+ if (grid[p.y][p.x] === START) return;
+ grid[end.y][end.x] = EMPTY;
+ end = { x: p.x, y: p.y };
+ grid[end.y][end.x] = END;
+ }
+ draw();
+ }
+
+ canvas.addEventListener("mousedown", (e) => {
+ if (running) return;
+ drawing = true;
+ applyDraw(posFromEvent(e));
+ });
+ canvas.addEventListener("mousemove", (e) => {
+ if (!drawing || running) return;
+ if (mode === "wall" || mode === "erase") applyDraw(posFromEvent(e));
+ });
+ window.addEventListener("mouseup", () => { drawing = false; });
+ canvas.addEventListener("mouseleave", () => { drawing = false; });
+
+ // touch
+ canvas.addEventListener("touchstart", (e) => {
+ e.preventDefault();
+ if (running) return;
+ drawing = true;
+ const t = e.touches[0];
+ applyDraw(posFromEvent(t));
+ }, { passive: false });
+ canvas.addEventListener("touchmove", (e) => {
+ e.preventDefault();
+ if (!drawing || running) return;
+ if (mode === "wall" || mode === "erase") {
+ const t = e.touches[0];
+ applyDraw(posFromEvent(t));
+ }
+ }, { passive: false });
+ canvas.addEventListener("touchend", () => { drawing = false; });
+
+ document.querySelectorAll("[data-mode]").forEach((btn) => {
+ btn.addEventListener("click", () => {
+ if (running) return;
+ mode = btn.dataset.mode;
+ document.querySelectorAll("[data-mode]").forEach((b) => b.classList.remove("active"));
+ btn.classList.add("active");
+ });
+ });
+
+ function setRunning(v) {
+ running = v;
+ btnRun.disabled = v;
+ btnStop.disabled = !v;
+ btnClear.disabled = v;
+ btnMaze.disabled = v;
+ document.querySelectorAll("[data-mode]").forEach((b) => { b.disabled = v; });
+ }
+
+ function heuristic(ax, ay, bx, by) {
+ return Math.abs(ax - bx) + Math.abs(ay - by);
+ }
+
+ function neighbors(x, y) {
+ const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
+ const out = [];
+ for (const [dx, dy] of dirs) {
+ const nx = x + dx, ny = y + dy;
+ if (nx >= 0 && ny >= 0 && nx < COLS && ny < ROWS && grid[ny][nx] !== WALL) {
+ out.push({ x: nx, y: ny });
+ }
+ }
+ return out;
+ }
+
+ function sleep(ms) {
+ return new Promise((res) => setTimeout(res, ms));
+ }
+
+ function speedDelay() {
+ const s = Number(speedEl.value);
+ // 1 = slow (~80ms), 100 = fast (~0ms)
+ return Math.max(0, Math.floor(80 - s * 0.8));
+ }
+
+ async function runAStar() {
+ clearVis();
+ draw();
+ abort = false;
+ setRunning(true);
+ statusEl.innerHTML = "Searching…";
+
+ const open = [];
+ const gScore = Array.from({ length: ROWS }, () => Array(COLS).fill(Infinity));
+ const fScore = Array.from({ length: ROWS }, () => Array(COLS).fill(Infinity));
+ const cameFrom = Array.from({ length: ROWS }, () => Array(COLS).fill(null));
+ const closed = Array.from({ length: ROWS }, () => Array(COLS).fill(false));
+ const inOpen = Array.from({ length: ROWS }, () => Array(COLS).fill(false));
+
+ const sx = start.x, sy = start.y;
+ const ex = end.x, ey = end.y;
+
+ gScore[sy][sx] = 0;
+ fScore[sy][sx] = heuristic(sx, sy, ex, ey);
+ open.push({ x: sx, y: sy, f: fScore[sy][sx] });
+ inOpen[sy][sx] = true;
+ vis[sy][sx] = VIS_OPEN;
+
+ let found = false;
+ let expanded = 0;
+ let steps = 0;
+
+ while (open.length > 0) {
+ if (abort) {
+ statusEl.innerHTML = "Stopped.";
+ setRunning(false);
+ return;
+ }
+
+ // pick lowest f
+ let best = 0;
+ for (let i = 1; i < open.length; i++) {
+ if (open[i].f < open[best].f) best = i;
+ else if (open[i].f === open[best].f) {
+ const hi = heuristic(open[i].x, open[i].y, ex, ey);
+ const hb = heuristic(open[best].x, open[best].y, ex, ey);
+ if (hi < hb) best = i;
+ }
+ }
+ const current = open.splice(best, 1)[0];
+ const cx = current.x, cy = current.y;
+ inOpen[cy][cx] = false;
+
+ if (closed[cy][cx]) continue;
+ closed[cy][cx] = true;
+ expanded++;
+
+ if (!(cx === sx && cy === sy) && !(cx === ex && cy === ey)) {
+ vis[cy][cx] = VIS_CLOSED;
+ }
+
+ if (cx === ex && cy === ey) {
+ found = true;
+ break;
+ }
+
+ for (const n of neighbors(cx, cy)) {
+ if (closed[n.y][n.x]) continue;
+ const tent = gScore[cy][cx] + 1;
+ if (tent < gScore[n.y][n.x]) {
+ cameFrom[n.y][n.x] = { x: cx, y: cy };
+ gScore[n.y][n.x] = tent;
+ fScore[n.y][n.x] = tent + heuristic(n.x, n.y, ex, ey);
+ if (!inOpen[n.y][n.x]) {
+ open.push({ x: n.x, y: n.y, f: fScore[n.y][n.x] });
+ inOpen[n.y][n.x] = true;
+ if (!(n.x === ex && n.y === ey) && !(n.x === sx && n.y === sy)) {
+ vis[n.y][n.x] = VIS_OPEN;
+ }
+ } else {
+ // update f in open list
+ for (let i = 0; i < open.length; i++) {
+ if (open[i].x === n.x && open[i].y === n.y) {
+ open[i].f = fScore[n.y][n.x];
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ steps++;
+ if (steps % Math.max(1, Math.floor(Number(speedEl.value) / 25)) === 0) {
+ draw();
+ statusEl.innerHTML = `Exploring… <strong>${expanded}</strong> nodes expanded, open set <strong>${open.length}</strong>`;
+ await sleep(speedDelay());
+ }
+ }
+
+ draw();
+
+ if (!found) {
+ statusEl.innerHTML = `No path found. Expanded <strong>${expanded}</strong> nodes.`;
+ setRunning(false);
+ return;
+ }
+
+ // reconstruct path
+ const path = [];
+ let cur = { x: ex, y: ey };
+ while (cur) {
+ path.push(cur);
+ cur = cameFrom[cur.y][cur.x];
+ }
+ path.reverse();
+
+ for (let i = 0; i < path.length; i++) {
+ if (abort) break;
+ const p = path[i];
+ if (!(p.x === sx && p.y === sy) && !(p.x === ex && p.y === ey)) {
+ vis[p.y][p.x] = VIS_PATH;
+ }
+ pathCells.push(p);
+ if (i % 2 === 0 || i === path.length - 1) {
+ draw();
+ await sleep(Math.max(8, speedDelay()));
+ }
+ }
+ draw();
+ statusEl.innerHTML = `Path length <strong>${path.length - 1}</strong> · Expanded <strong>${expanded}</strong> nodes`;
+ setRunning(false);
+ }
+
+ btnRun.addEventListener("click", () => {
+ if (running) return;
+ runAStar();
+ });
+ btnStop.addEventListener("click", () => { abort = true; });
+
+ btnClear.addEventListener("click", () => {
+ if (running) return;
+ start = { x: 3, y: 14 };
+ end = { x: 36, y: 14 };
+ initGrid();
+ statusEl.textContent = "Grid cleared. Draw walls, set start & end, then run A*.";
+ draw();
+ });
+
+ function generateMaze() {
+ // recursive backtracker on odd cells, walls everywhere else
+ for (let y = 0; y < ROWS; y++) {
+ for (let x = 0; x < COLS; x++) {
+ grid[y][x] = WALL;
+ vis[y][x] = VIS_NONE;
+ }
+ }
+ pathCells = [];
+
+ const stack = [];
+ const ox = 1, oy = 1;
+ grid[oy][ox] = EMPTY;
+ stack.push({ x: ox, y: oy });
+
+ const dirs = [[2,0],[-2,0],[0,2],[0,-2]];
+
+ function shuffle(a) {
+ for (let i = a.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [a[i], a[j]] = [a[j], a[i]];
+ }
+ return a;
+ }
+
+ while (stack.length) {
+ const cur = stack[stack.length - 1];
+ const opts = [];
+ for (const [dx, dy] of dirs) {
+ const nx = cur.x + dx, ny = cur.y + dy;
+ if (nx > 0 && ny > 0 && nx < COLS - 1 && ny < ROWS - 1 && grid[ny][nx] === WALL) {
+ opts.push({ x: nx, y: ny, dx, dy });
+ }
+ }
+ if (opts.length === 0) {
+ stack.pop();
+ continue;
+ }
+ shuffle(opts);
+ const n = opts[0];
+ grid[cur.y + n.dy / 2][cur.x + n.dx / 2] = EMPTY;
+ grid[n.y][n.x] = EMPTY;
+ stack.push({ x: n.x, y: n.y });
+ }
+
+ // place start/end on empty cells
+ function findEmpty(fromLeft) {
+ if (fromLeft) {
+ for (let x = 1; x < COLS - 1; x++) {
+ for (let y = 1; y < ROWS - 1; y++) {
+ if (grid[y][x] === EMPTY) return { x, y };
+ }
+ }
+ } else {
+ for (let x = COLS - 2; x >= 1; x--) {
+ for (let y = ROWS - 2; y >= 1; y--) {
+ if (grid[y][x] === EMPTY) return { x, y };
+ }
+ }
+ }
+ return { x: 1, y: 1 };
+ }
+ start = findEmpty(true);
+ end = findEmpty(false);
+ if (start.x === end.x && start.y === end.y) {
+ end = { x: Math.min(COLS - 2, start.x + 4), y: start.y };
+ grid[end.y][end.x] = EMPTY;
+ }
+ grid[start.y][start.x] = START;
+ grid[end.y][end.x] = END;
+ }
+
+ btnMaze.addEventListener("click", () => {
+ if (running) return;
+ generateMaze();
+ statusEl.textContent = "Maze generated. Run A* to find the path.";
+ draw();
+ });
+
+ initGrid();
+ draw();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6c13ff8455cc/grok.png b/data/artifacts/6c13ff8455cc/grok.png
new file mode 100644
index 0000000..5aacde2
Binary files /dev/null and b/data/artifacts/6c13ff8455cc/grok.png differ
diff --git a/data/artifacts/6c13ff8455cc/hermes3-8b.html b/data/artifacts/6c13ff8455cc/hermes3-8b.html
new file mode 100644
index 0000000..e6efe8d
--- /dev/null
+++ b/data/artifacts/6c13ff8455cc/hermes3-8b.html
@@ -0,0 +1,211 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>A* Pathfinding Visualizer</title>
+ <style>
+ body {
+ font-family: monospace;
+ background-color: #000;
+ color: #0f0;
+ margin: 0;
+ padding: 10px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ canvas {
+ border: 1px solid #fff;
+ outline: none;
+ }
+
+ input[type=range] {
+ width: 100%;
+ margin-top: 5px;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="grid" style="width: 400px; height: 400px;"></canvas>
+ <br>
+ <input type="range" id="speed" min="10" max="2000">
+ <label for="speed">Speed:</label>
+ <button id="clear">Clear</button>
+ <button id="randomMaze">Random Maze</button>
+
+ <script>
+ const canvas = document.getElementById('grid');
+ const ctx = canvas.getContext('2d');
+
+ let gridSize = 20;
+ let grid = Array.from({ length: gridSize }, () => Array(gridSize).fill(0));
+ let startNode, endNode;
+
+ function drawGrid() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ for (let y = 0; y < gridSize; y++) {
+ for (let x = 0; x < gridSize; x++) {
+ ctx.fillStyle = grid[y][x] === 1 ? '#fff' : '#000';
+ ctx.fillRect(x * 20, y * 20, 20, 20);
+
+ if ([startNode, endNode].includes({x, y})) {
+ ctx.strokeStyle = '#0f0';
+ ctx.strokeRect(x * 20, y * 20, 20, 20);
+ }
+ }
+ }
+ }
+
+ function drawPath(path) {
+ path.forEach(({x, y}) => {
+ grid[y][x] = 2;
+ });
+
+ for (let i = 0; i < path.length - 1; i++) {
+ setTimeout(() => {
+ let {x, y} = path[i];
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(x * 20, y * 20, 20, 20);
+
+ if (i === path.length - 2) {
+ let {x: endX, y: endY} = path[path.length - 1];
+ ctx.lineWidth = 4;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo((startNode.x + gridSize / 2) * 20, (startNode.y + gridSize / 2) * 20);
+ ctx.lineTo((endX + gridSize / 2) * 20, (endY + gridSize / 2) * 20);
+ ctx.stroke();
+ }
+ }, 50 * i);
+ }
+ }
+
+ canvas.addEventListener('mousedown', (e) => {
+ if ([startNode, endNode].includes({x: Math.floor(e.clientX / 20), y: Math.floor(e.clientY / 20)})) return;
+
+ let {x, y} = {x: Math.floor(e.clientX / 20), y: Math.floor(e.clientY / 20)};
+
+ if (e.buttons === 1) {
+ grid[y][x] = 1;
+ } else if (!startNode && !endNode) {
+ startNode = { x, y };
+ grid[startNode.y][startNode.x] = 3;
+ } else if (!startNode || !endNode) {
+ endNode = { x, y };
+ grid[endNode.y][endNode.x] = 4;
+ }
+
+ drawGrid();
+ });
+
+ document.getElementById('clear').addEventListener('click', () => {
+ startNode = undefined;
+ endNode = undefined;
+ grid = Array.from({ length: gridSize }, () => Array(gridSize).fill(0));
+ drawGrid();
+ });
+
+ document.getElementById('randomMaze').addEventListener('click', () => {
+ for (let y = 0; y < gridSize; y++) {
+ for (let x = 0; x < gridSize; x++) {
+ grid[y][x] = Math.random() > 0.7 ? 1 : 0;
+ }
+ }
+
+ if (!startNode || !endNode) {
+ startNode = { x: gridSize / 2 | 0, y: gridSize / 2 | 0 };
+ endNode = { x: gridSize / 2 | 0, y: gridSize / 2 | 0 };
+
+ do {
+ endNode.x = Math.floor(Math.random() * gridSize);
+ endNode.y = Math.floor(Math.random() * gridSize);
+ } while (startNode.x === endNode.x && startNode.y === endNode.y);
+ }
+
+ grid[startNode.y][startNode.x] = 3;
+ grid[endNode.y][endNode.x] = 4;
+
+ drawGrid();
+ });
+
+ document.getElementById('speed').addEventListener('input', (e) => {
+ const speed = e.target.value;
+ const delay = Math.max(1, Math.min(2000, speed));
+ document.querySelector('#grid canvas').style.animationDuration = `${delay}ms`;
+ });
+
+ document.getElementById('grid').addEventListener('click', () => {
+ if (!startNode || !endNode) return;
+
+ let openSet = [{pos: startNode, fScore: 0, gScore: 0, parent: null}];
+ let cameFrom = {};
+
+ while (openSet.length > 0) {
+ let current = openSet.shift();
+
+ if (current.pos.x === endNode.x && current.pos.y === endNode.y) {
+ drawPath(reconstructPath(cameFrom, startNode));
+ return;
+ }
+
+ for (let neighbor of getNeighbors(current.pos, grid)) {
+ let tentGScore = current.gScore + 1;
+
+ if (tentGScore < (neighbor.gScore || Infinity)) {
+ cameFrom[neighbor.pos] = current.pos;
+ neighbor.gScore = tentGScore;
+ neighbor.fScore = tentGScore + heuristic(neighbor.pos, endNode);
+
+ if (!openSet.find(s => s.pos.x === neighbor.pos.x && s.pos.y === neighbor.pos.y)) {
+ openSet.push({pos: neighbor.pos, fScore: neighbor.fScore, gScore: neighbor.gScore, parent: current.pos});
+ }
+ }
+ }
+
+ drawGrid();
+ }
+ });
+
+ function heuristic(pos, goal) {
+ return Math.abs(pos.x - goal.x) + Math.abs(pos.y - goal.y);
+ }
+
+ function getNeighbors(pos, grid) {
+ let neighbors = [];
+
+ for (let y of [pos.y-1, pos.y, pos.y+1]) {
+ for (let x of [pos.x-1, pos.x, pos.x+1]) {
+ if ((y >= 0 && y < gridSize) && (x >= 0 && x < gridSize)) {
+ let isWall = grid[y][x] === 1;
+ let isOutside = (y < 0 || y >= gridSize) || (x < 0 || x >= gridSize);
+
+ if (!isWall && !(isOutside)) {
+ neighbors.push({pos: {x, y}});
+ }
+ }
+ }
+ }
+
+ return neighbors;
+ }
+
+ function reconstructPath(cameFrom, start) {
+ let totalPath = [start];
+
+ while (totalPath[totalPath.length - 1] !== endNode) {
+ let current = cameFrom[totalPath[totalPath.length - 1]];
+
+ if (!current) break;
+
+ totalPath.push(current);
+ }
+
+ return totalPath;
+ }
+
+ document.getElementById('clear').click();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6c13ff8455cc/hermes3-8b.png b/data/artifacts/6c13ff8455cc/hermes3-8b.png
new file mode 100644
index 0000000..52d631c
Binary files /dev/null and b/data/artifacts/6c13ff8455cc/hermes3-8b.png differ
diff --git a/data/artifacts/6c13ff8455cc/hf-qwen-coder-32b.html b/data/artifacts/6c13ff8455cc/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..73bbd25
--- /dev/null
+++ b/data/artifacts/6c13ff8455cc/hf-qwen-coder-32b.html
@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>A* Pathfinding Visualizer</title>
+<style>
+ body { display: flex; flex-direction: column; align-items: center; font-family: Arial, sans-serif; }
+ #grid-container { display: grid; border: 1px solid #000; }
+ .cell { width: 20px; height: 20px; box-sizing: border-box; border: 1px solid #ccc; transition: background-color 0.1s; }
+ .wall { background-color: black; }
+ .start { background-color: green; }
+ .end { background-color: red; }
+ .open { background-color: lightblue; }
+ .closed { background-color: orange; }
+ .path { background-color: purple; }
+ #controls { margin-top: 20px; }
+ input[type="range"] { width: 300px; }
+</style>
+</head>
+<body>
+<div id="grid-container"></div>
+<div id="controls">
+ <label for="speed">Speed:</label>
+ <input type="range" id="speed" min="1" max="100" value="50">
+ <button onclick="clearGrid()">Clear</button>
+ <button onclick="randomMaze()">Random Maze</button>
+</div>
+
+<script>
+const gridSize = 20;
+let grid = Array.from({ length: gridSize }, () => Array(gridSize).fill(0));
+let start, end;
+let openSet = [], closedSet = [];
+let path = [];
+let draggingWall = false;
+
+function setupGrid() {
+ const container = document.getElementById('grid-container');
+ container.style.gridTemplateColumns = `repeat(${gridSize}, 1fr)`;
+ for (let i = 0; i < gridSize; i++) {
+ for (let j = 0; j < gridSize; j++) {
+ const cell = document.createElement('div');
+ cell.classList.add('cell');
+ cell.dataset.row = i;
+ cell.dataset.col = j;
+ cell.addEventListener('mousedown', () => handleCellClick(i, j));
+ cell.addEventListener('mouseover', () => draggingWall && handleCellClick(i, j));
+ container.appendChild(cell);
+ }
+ }
+}
+
+function drawGrid() {
+ const cells = document.querySelectorAll('.cell');
+ cells.forEach(cell => {
+ const row = parseInt(cell.dataset.row);
+ const col = parseInt(cell.dataset.col);
+ cell.className = 'cell';
+ if (grid[row][col] === 1) cell.classList.add('wall');
+ else if (row === start?.i && col === start?.j) cell.classList.add('start');
+ else if (row === end?.i && col === end?.j) cell.classList.add('end');
+ openSet.forEach(node => {
+ if (node.i === row && node.j === col) cell.classList.add('open');
+ });
+ closedSet.forEach(node => {
+ if (node.i === row && node.j === col) cell.classList.add('closed');
+ });
+ path.forEach(node => {
+ if (node.i === row && node.j === col) cell.classList.add('path');
+ });
+ });
+}
+
+function handleCellClick(i, j) {
+ if (!start) {
+ start = { i, j };
+ } else if (!end) {
+ end = { i, j };
+ } else {
+ grid[i][j] = 1 - grid[i][j];
+ }
+ drawGrid();
+}
+
+function heuristic(a, b) {
+ return Math.abs(a.i - b.i) + Math.abs(a.j - b.j);
+}
+
+async function aStar() {
+ if (!start || !end) return;
+ openSet.push(start);
+ while (openSet.length > 0) {
+ let winner = 0;
+ for (let i = 1; i < openSet.length; i++) {
+ if (openSet[i].f < openSet[winner].f) {
+ winner = i;
+ }
+ }
+ const current = openSet.splice(winner, 1)[0];
+ closedSet.push(current);
+
+ if (current === end) {
+ path = [];
+ let temp = current;
+ while (temp.previous) {
+ path.push(temp);
+ temp = temp.previous;
+ }
+ drawGrid();
+ return;
+ }
+
+ const neighbors = getNeighbors(current);
+ for (const neighbor of neighbors) {
+ if (!closedSet.includes(neighbor)) {
+ const tempG = current.g + 1;
+ let newPath = false;
+ if (openSet.includes(neighbor)) {
+ if (tempG < neighbor.g) {
+ neighbor.g = tempG;
+ newPath = true;
+ }
+ } else {
+ neighbor.g = tempG;
+ newPath = true;
+ openSet.push(neighbor);
+ }
+
+ if (newPath) {
+ neighbor.h = heuristic(neighbor, end);
+ neighbor.f = neighbor.g + neighbor.h;
+ neighbor.previous = current;
+ }
+ }
+ }
+
+ drawGrid();
+ await new Promise(resolve => setTimeout(resolve, 100 - document.getElementById('speed').value));
+ }
+}
+
+function getNeighbors(cell) {
+ const neighbors = [];
+ if (cell.i > 0 && grid[cell.i - 1][cell.j] === 0) neighbors.push({ i: cell.i - 1, j: cell.j });
+ if (cell.i < gridSize - 1 && grid[cell.i + 1][cell.j] === 0) neighbors.push({ i: cell.i + 1, j: cell.j });
+ if (cell.j > 0 && grid[cell.i][cell.j - 1] === 0) neighbors.push({ i: cell.i, j: cell.j - 1 });
+ if (cell.j < gridSize - 1 && grid[cell.i][cell.j + 1] === 0) neighbors.push({ i: cell.i, j: cell.j + 1 });
+ return neighbors;
+}
+
+function clearGrid() {
+ start = null;
+ end = null;
+ openSet = [];
+ closedSet = [];
+ path = [];
+ grid = Array.from({ length: gridSize }, () => Array(gridSize).fill(0));
+ drawGrid();
+}
+
+function randomMaze() {
+ clearGrid();
+ for (let i = 0; i < gridSize; i++) {
+ for (let j = 0; j < gridSize; j++) {
+ if (Math.random() > 0.7 && !start && !end) start = { i, j };
+ else if (Math.random() > 0.7 && !end) end = { i, j };
+ else if (Math.random() > 0.85) grid[i][j] = 1;
+ }
+ }
+ drawGrid();
+}
+
+document.addEventListener('mouseup', () => draggingWall = false);
+document.addEventListener('mousedown', () => draggingWall = true);
+
+setupGrid();
+drawGrid();
+
+document.querySelector('#controls button').addEventListener('click', aStar);
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6c13ff8455cc/hf-qwen-coder-32b.png b/data/artifacts/6c13ff8455cc/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..3ce41b4
Binary files /dev/null and b/data/artifacts/6c13ff8455cc/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/6c13ff8455cc/kimi.html b/data/artifacts/6c13ff8455cc/kimi.html
new file mode 100644
index 0000000..7a820da
--- /dev/null
+++ b/data/artifacts/6c13ff8455cc/kimi.html
@@ -0,0 +1,515 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>A* Pathfinding Visualizer</title>
+ <style>
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+ background: #f0f2f5;
+ margin: 0;
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-height: 100vh;
+ box-sizing: border-box;
+ }
+
+ h1 {
+ color: #2c3e50;
+ margin-bottom: 10px;
+ font-size: 24px;
+ }
+
+ #controls {
+ background: white;
+ padding: 15px 25px;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ margin-bottom: 20px;
+ display: flex;
+ gap: 12px;
+ align-items: center;
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ button {
+ padding: 8px 16px;
+ border: none;
+ border-radius: 6px;
+ background: #3498db;
+ color: white;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s;
+ font-size: 14px;
+ }
+
+ button:hover:not(:disabled) {
+ background: #2980b9;
+ transform: translateY(-1px);
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ }
+
+ button:active:not(:disabled) {
+ transform: translateY(0);
+ }
+
+ button:disabled {
+ background: #bdc3c7;
+ cursor: not-allowed;
+ }
+
+ #btnStart { background: #27ae60; }
+ #btnStart:hover:not(:disabled) { background: #229954; }
+
+ #btnClearAll { background: #e74c3c; }
+ #btnClearAll:hover:not(:disabled) { background: #c0392b; }
+
+ #btnMaze { background: #9b59b6; }
+ #btnMaze:hover:not(:disabled) { background: #8e44ad; }
+
+ .control-group {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-left: 10px;
+ padding-left: 10px;
+ border-left: 1px solid #ddd;
+ }
+
+ label {
+ font-size: 14px;
+ color: #555;
+ font-weight: 600;
+ }
+
+ input[type="range"] {
+ width: 120px;
+ cursor: pointer;
+ }
+
+ #grid {
+ display: grid;
+ gap: 1px;
+ background: #bdc3c7;
+ border: 2px solid #34495e;
+ border-radius: 4px;
+ padding: 2px;
+ user-select: none;
+ }
+
+ .cell {
+ width: 25px;
+ height: 25px;
+ background: white;
+ cursor: pointer;
+ transition: background-color 0.1s;
+ }
+
+ .cell:hover {
+ filter: brightness(0.95);
+ }
+
+ .wall {
+ background: #2c3e50;
+ animation: pop 0.2s;
+ }
+
+ .start {
+ background: #27ae60;
+ border-radius: 50%;
+ transform: scale(0.9);
+ box-shadow: inset 0 0 4px rgba(0,0,0,0.3);
+ }
+
+ .end {
+ background: #e74c3c;
+ border-radius: 50%;
+ transform: scale(0.9);
+ box-shadow: inset 0 0 4px rgba(0,0,0,0.3);
+ }
+
+ .visited {
+ background: #74b9ff;
+ animation: fadeIn 0.3s;
+ }
+
+ .path {
+ background: #fdcb6e;
+ animation: pulse 0.5s;
+ box-shadow: inset 0 0 6px rgba(0,0,0,0.2);
+ }
+
+ @keyframes pop {
+ 0% { transform: scale(0.8); }
+ 50% { transform: scale(1.1); }
+ 100% { transform: scale(1); }
+ }
+
+ @keyframes fadeIn {
+ from { opacity: 0; transform: scale(0.8); }
+ to { opacity: 1; transform: scale(1); }
+ }
+
+ @keyframes pulse {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.1); }
+ 100% { transform: scale(1); }
+ }
+
+ .instructions {
+ margin-top: 15px;
+ color: #7f8c8d;
+ font-size: 13px;
+ text-align: center;
+ max-width: 600px;
+ line-height: 1.5;
+ }
+ </style>
+</head>
+<body>
+ <h1>A* Pathfinding Visualizer</h1>
+
+ <div id="controls">
+ <button id="btnStart">Find Path</button>
+ <button id="btnClear">Clear Path</button>
+ <button id="btnClearAll">Clear All</button>
+ <button id="btnMaze">Random Maze</button>
+ <div class="control-group">
+ <label for="speed">Speed:</label>
+ <input type="range" id="speed" min="1" max="100" value="50">
+ </div>
+ </div>
+
+ <div id="grid"></div>
+
+ <div class="instructions">
+ <strong>Instructions:</strong> Drag to draw walls. Drag green (start) or red (end) nodes to move them.
+ Click "Find Path" to visualize A* search algorithm.
+ </div>
+
+ <script>
+ const ROWS = 20;
+ const COLS = 30;
+ let grid = [];
+ let startPos = { x: 5, y: 10 };
+ let endPos = { x: 24, y: 10 };
+ let isRunning = false;
+ let isMouseDown = false;
+ let dragMode = null; // 'wall', 'erase', 'start', 'end'
+
+ const gridEl = document.getElementById('grid');
+ const speedSlider = document.getElementById('speed');
+
+ function init() {
+ createGrid();
+ setupEventListeners();
+ }
+
+ function createGrid() {
+ gridEl.style.gridTemplateColumns = `repeat(${COLS}, 25px)`;
+
+ for (let y = 0; y < ROWS; y++) {
+ grid[y] = [];
+ for (let x = 0; x < COLS; x++) {
+ const cell = document.createElement('div');
+ cell.className = 'cell';
+ cell.dataset.x = x;
+ cell.dataset.y = y;
+
+ const node = {
+ x, y,
+ element: cell,
+ isWall: false,
+ isVisited: false,
+ parent: null,
+ g: Infinity,
+ f: Infinity
+ };
+
+ if (x === startPos.x && y === startPos.y) {
+ cell.classList.add('start');
+ node.isStart = true;
+ } else if (x === endPos.x && y === endPos.y) {
+ cell.classList.add('end');
+ node.isEnd = true;
+ }
+
+ grid[y][x] = node;
+ gridEl.appendChild(cell);
+ }
+ }
+ }
+
+ function setupEventListeners() {
+ // Grid mouse events
+ gridEl.addEventListener('mousedown', handleMouseDown);
+ gridEl.addEventListener('mouseover', handleMouseOver);
+ window.addEventListener('mouseup', () => {
+ isMouseDown = false;
+ dragMode = null;
+ });
+
+ // Prevent drag behavior
+ gridEl.addEventListener('dragstart', (e) => e.preventDefault());
+
+ // Buttons
+ document.getElementById('btnStart').addEventListener('click', startPathfinding);
+ document.getElementById('btnClear').addEventListener('click', clearPath);
+ document.getElementById('btnClearAll').addEventListener('click', clearAll);
+ document.getElementById('btnMaze').addEventListener('click', generateMaze);
+ }
+
+ function handleMouseDown(e) {
+ if (isRunning) return;
+
+ const cell = e.target;
+ if (!cell.classList.contains('cell')) return;
+
+ isMouseDown = true;
+ const x = parseInt(cell.dataset.x);
+ const y = parseInt(cell.dataset.y);
+ const node = grid[y][x];
+
+ if (node.isStart) {
+ dragMode = 'start';
+ } else if (node.isEnd) {
+ dragMode = 'end';
+ } else if (node.isWall) {
+ dragMode = 'erase';
+ setWall(node, false);
+ } else {
+ dragMode = 'wall';
+ setWall(node, true);
+ }
+ }
+
+ function handleMouseOver(e) {
+ if (!isMouseDown || isRunning) return;
+
+ const cell = e.target;
+ if (!cell.classList.contains('cell')) return;
+
+ const x = parseInt(cell.dataset.x);
+ const y = parseInt(cell.dataset.y);
+ const node = grid[y][x];
+
+ if (dragMode === 'wall' && !node.isWall && !node.isStart && !node.isEnd) {
+ setWall(node, true);
+ } else if (dragMode === 'erase' && node.isWall) {
+ setWall(node, false);
+ } else if (dragMode === 'start' && !node.isEnd && !node.isWall) {
+ moveStart(node);
+ } else if (dragMode === 'end' && !node.isStart && !node.isWall) {
+ moveEnd(node);
+ }
+ }
+
+ function setWall(node, isWall) {
+ node.isWall = isWall;
+ if (isWall) {
+ node.element.classList.add('wall');
+ } else {
+ node.element.classList.remove('wall');
+ }
+ }
+
+ function moveStart(node) {
+ // Clear old
+ const oldStart = grid[startPos.y][startPos.x];
+ oldStart.isStart = false;
+ oldStart.element.classList.remove('start');
+
+ // Set new
+ node.isStart = true;
+ node.element.classList.add('start');
+ startPos = { x: node.x, y: node.y };
+ }
+
+ function moveEnd(node) {
+ // Clear old
+ const oldEnd = grid[endPos.y][endPos.x];
+ oldEnd.isEnd = false;
+ oldEnd.element.classList.remove('end');
+
+ // Set new
+ node.isEnd = true;
+ node.element.classList.add('end');
+ endPos = { x: node.x, y: node.y };
+ }
+
+ function sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ function getDelay() {
+ const speed = parseInt(speedSlider.value);
+ // Map 1-100 to 100ms-1ms
+ return Math.max(1, 100 - speed);
+ }
+
+ async function startPathfinding() {
+ if (isRunning) return;
+ isRunning = true;
+ toggleControls(false);
+
+ clearPath();
+
+ const startNode = grid[startPos.y][startPos.x];
+ const endNode = grid[endPos.y][endPos.x];
+
+ // Initialize A*
+ startNode.g = 0;
+ startNode.f = heuristic(startNode, endNode);
+
+ const openSet = [startNode];
+
+ while (openSet.length > 0) {
+ // Get node with lowest f score
+ let lowestIdx = 0;
+ for (let i = 1; i < openSet.length; i++) {
+ if (openSet[i].f < openSet[lowestIdx].f) {
+ lowestIdx = i;
+ }
+ }
+
+ const current = openSet[lowestIdx];
+
+ // Check if reached end
+ if (current === endNode) {
+ await reconstructPath(current);
+ isRunning = false;
+ toggleControls(true);
+ return;
+ }
+
+ // Remove from open set and mark visited
+ openSet.splice(lowestIdx, 1);
+ current.isVisited = true;
+
+ if (current !== startNode) {
+ current.element.classList.add('visited');
+ await sleep(getDelay());
+ }
+
+ // Check neighbors
+ const neighbors = getNeighbors(current);
+ for (const neighbor of neighbors) {
+ if (neighbor.isVisited || neighbor.isWall) continue;
+
+ const tentativeG = current.g + 1;
+ const isInOpenSet = openSet.includes(neighbor);
+
+ if (tentativeG < neighbor.g || !isInOpenSet) {
+ neighbor.parent = current;
+ neighbor.g = tentativeG;
+ neighbor.f = neighbor.g + heuristic(neighbor, endNode);
+
+ if (!isInOpenSet) {
+ openSet.push(neighbor);
+ }
+ }
+ }
+ }
+
+ // No path found
+ alert('No path found!');
+ isRunning = false;
+ toggleControls(true);
+ }
+
+ function heuristic(a, b) {
+ // Manhattan distance
+ return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
+ }
+
+ function getNeighbors(node) {
+ const neighbors = [];
+ const dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]];
+
+ for (const [dx, dy] of dirs) {
+ const nx = node.x + dx;
+ const ny = node.y + dy;
+ if (nx >= 0 && nx < COLS && ny >= 0 && ny < ROWS) {
+ neighbors.push(grid[ny][nx]);
+ }
+ }
+ return neighbors;
+ }
+
+ async function reconstructPath(node) {
+ const path = [];
+ while (node.parent) {
+ path.push(node);
+ node = node.parent;
+ }
+ path.reverse();
+
+ for (const n of path) {
+ if (!n.isEnd) {
+ n.element.classList.remove('visited');
+ n.element.classList.add('path');
+ await sleep(getDelay());
+ }
+ }
+ }
+
+ function clearPath() {
+ for (let y = 0; y < ROWS; y++) {
+ for (let x = 0; x < COLS; x++) {
+ const node = grid[y][x];
+ node.isVisited = false;
+ node.g = Infinity;
+ node.f = Infinity;
+ node.parent = null;
+ node.element.classList.remove('visited', 'path');
+ }
+ }
+ }
+
+ function clearAll() {
+ clearPath();
+ for (let y = 0; y < ROWS; y++) {
+ for (let x = 0; x < COLS; x++) {
+ const node = grid[y][x];
+ if (!node.isStart && !node.isEnd) {
+ node.isWall = false;
+ node.element.classList.remove('wall');
+ }
+ }
+ }
+ }
+
+ function generateMaze() {
+ if (isRunning) return;
+ clearAll();
+
+ for (let y = 0; y < ROWS; y++) {
+ for (let x = 0; x < COLS; x++) {
+ if (Math.random() < 0.3) {
+ const node = grid[y][x];
+ if (!node.isStart && !node.isEnd) {
+ node.isWall = true;
+ node.element.classList.add('wall');
+ }
+ }
+ }
+ }
+ }
+
+ function toggleControls(enabled) {
+ document.getElementById('btnStart').disabled = !enabled;
+ document.getElementById('btnClear').disabled = !enabled;
+ document.getElementById('btnClearAll').disabled = !enabled;
+ document.getElementById('btnMaze').disabled = !enabled;
+ }
+
+ // Initialize
+ init();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6c13ff8455cc/kimi.png b/data/artifacts/6c13ff8455cc/kimi.png
new file mode 100644
index 0000000..1370f6a
Binary files /dev/null and b/data/artifacts/6c13ff8455cc/kimi.png differ
diff --git a/data/artifacts/6c13ff8455cc/qwen25-7b.html b/data/artifacts/6c13ff8455cc/qwen25-7b.html
new file mode 100644
index 0000000..ac6c7b8
--- /dev/null
+++ b/data/artifacts/6c13ff8455cc/qwen25-7b.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>A* Pathfinding Visualizer</title>
+ <style>
+ body { font-family: Arial, sans-serif; }
+ canvas { border: 2px solid black; margin-top: 10px; cursor: crosshair; }
+ #controls { display: flex; gap: 10px; margin-top: 10px; }
+ </style>
+</head>
+<body>
+ <canvas id="mazeCanvas" width="400" height="400"></canvas>
+ <div id="controls">
+ <input type="range" min="1" max="100" value="50" step="1" id="speedSlider"> Speed:
+ <button onclick="clearMaze()">Clear</button>
+ <button onclick="generateRandomMaze()">Random Maze</button>
+ </div>
+
+ <script>
+ const canvas = document.getElementById('mazeCanvas');
+ const ctx = canvas.getContext('2d');
+ const gridWidth = 40;
+ const cellSize = canvas.width / gridWidth;
+ const speedSlider = document.getElementById('speedSlider');
+ let start, end;
+
+ function drawCell(x, y) {
+ if (x === start?.x || y === start?.y) ctx.fillStyle = '#1E90FF';
+ else if (x === end?.x || y === end?.y) ctx.fillStyle = '#FF4500';
+ else if (walls[y * gridWidth + x]) ctx.fillStyle = '#000000';
+ else ctx.fillStyle = '#FFFFFF';
+ ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
+ }
+
+ function updateCanvas() {
+ for (let y = 0; y < gridWidth; y++) {
+ for (let x = 0; x < gridWidth; x++) {
+ drawCell(x, y);
+ }
+ }
+ }
+
+ canvas.addEventListener('mousedown', (e) => {
+ const mousePos = getMousePos(canvas, e);
+ if (!start && mousePos.x > -1 && mousePos.y > -1) start = {x: Math.floor(mousePos.x / cellSize), y: Math.floor(mousePos.y / cellSize)};
+ else if (!end && mousePos.x > -1 && mousePos.y > -1) end = {x: Math.floor(mousePos.x / cellSize), y: Math.floor(mousePos.y / cellSize)};
+ });
+
+ function getMousePos(canvas, evt) {
+ const rect = canvas.getBoundingClientRect();
+ return { x: evt.clientX - rect.left, y: evt.clientY - rect.top };
+ }
+
+ let walls = [];
+ for (let i = 0; i < gridWidth * gridWidth; i++) walls.push(false);
+
+ function clearMaze() {
+ start = null;
+ end = null;
+ walls.fill(false);
+ updateCanvas();
+ }
+
+ function generateRandomMaze() {
+ const randomWalls = [];
+ for (let i = 0; i < 25; i++) {
+ let x = Math.floor(Math.random() * gridWidth);
+ let y = Math.floor(Math.random() * gridWidth);
+ if (!walls[y * gridWidth + x]) randomWalls.push({x, y});
+ }
+ walls.fill(false);
+ for (const {x, y} of randomWalls) {
+ walls[y * gridWidth + x] = true;
+ }
+ updateCanvas();
+ }
+
+ function drawPath(path) {
+ path.forEach((node) => drawCell(node.x, node.y));
+ }
+
+ speedSlider.addEventListener('input', () => {
+ setTimeout(() => {
+ if (start && end) aStar(start, end);
+ }, 100 - speedSlider.value);
+ });
+
+ function aStar(start, end) {
+ const openSet = [start];
+ const cameFrom = {};
+ const gScore = { [`${start.x},${start.y}`]: 0 };
+ const fScore = { [`${start.x},${start.y}`]: heuristic(start, end) };
+
+ while (openSet.length > 0) {
+ let lowestFIndex = 0;
+ for (let i = 0; i < openSet.length; i++) {
+ if (fScore[`${openSet[i].x},${openSet[i].y}`] < fScore[`${openSet[lowestFIndex].x},${openSet[lowestFIndex].y}`]) {
+ lowestFIndex = i;
+ }
+ }
+
+ const current = openSet[lowestFIndex];
+ if (current.x === end.x && current.y === end.y) {
+ let currentNode = current;
+ const path = [];
+ while (currentNode) {
+ path.push(currentNode);
+ currentNode = cameFrom[`${currentNode.x},${currentNode.y}`];
+ }
+ drawPath(path.reverse());
+ break;
+ }
+
+ openSet.splice(lowestFIndex, 1);
+ for (let dx = -1; dx <= 1; dx++) {
+ for (let dy = -1; dy <= 1; dy++) {
+ if (Math.abs(dx) + Math.abs(dy) !== 1) continue;
+ const neighborX = current.x + dx;
+ const neighborY = current.y + dy;
+ if (neighborX < 0 || neighborX >= gridWidth || neighborY < 0 || neighborY >= gridWidth) continue;
+ if (walls[neighborY * gridWidth + neighborX]) continue;
+
+ const tentativeGScore = gScore[`${current.x},${current.y}`] + 1;
+ if (!gScore[`${neighborX},${neighborY}`] || tentativeGScore < gScore[`${neighborX},${neighborY}`]) {
+ cameFrom[`${neighborX},${neighborY}`] = current;
+ gScore[`${neighborX},${neighborY}`] = tentativeGScore;
+ fScore[`${neighborX},${neighborY}`] = gScore[`${neighborX},${neighborY}`] + heuristic({x: neighborX, y: neighborY}, end);
+ if (!openSet.includes({x: neighborX, y: neighborY})) openSet.push({x: neighborX, y: neighborY});
+ }
+ }
+ }
+ }
+ }
+
+ function heuristic(a, b) {
+ const dx = Math.abs(a.x - b.x);
+ const dy = Math.abs(a.y - b.y);
+ return (dx + dy) * 10;
+ }
+
+ updateCanvas();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6c13ff8455cc/qwen25-7b.png b/data/artifacts/6c13ff8455cc/qwen25-7b.png
new file mode 100644
index 0000000..18ed8d1
Binary files /dev/null and b/data/artifacts/6c13ff8455cc/qwen25-7b.png differ
diff --git a/data/artifacts/6c13ff8455cc/qwen3-14b.html b/data/artifacts/6c13ff8455cc/qwen3-14b.html
new file mode 100644
index 0000000..53fbf50
--- /dev/null
+++ b/data/artifacts/6c13ff8455cc/qwen3-14b.html
@@ -0,0 +1,263 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>A* Pathfinding Visualizer</title>
+ <style>
+ body {
+ margin: 0;
+ font-family: Arial, sans-serif;
+ background: #1e1e1e;
+ color: #fff;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+ #controls {
+ margin: 10px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ align-items: center;
+ }
+ #controls label {
+ margin-right: 5px;
+ }
+ #controls input[type="range"] {
+ width: 150px;
+ }
+ #grid {
+ display: grid;
+ grid-template-columns: repeat(30, 20px);
+ grid-template-rows: repeat(20, 20px);
+ gap: 1px;
+ }
+ .cell {
+ width: 20px;
+ height: 20px;
+ background: #444;
+ cursor: pointer;
+ }
+ .wall {
+ background: #222;
+ }
+ .start {
+ background: #00ff00;
+ }
+ .end {
+ background: #ff0000;
+ }
+ .open {
+ background: #00ffff;
+ }
+ .closed {
+ background: #ffaa00;
+ }
+ .path {
+ background: #0000ff;
+ }
+ </style>
+</head>
+<body>
+ <div id="controls">
+ <label for="speed">Speed:</label>
+ <input type="range" id="speed" min="1" max="100" value="50" />
+ <button id="clear">Clear</button>
+ <button id="random">Random Maze</button>
+ </div>
+ <div id="grid"></div>
+
+ <script>
+ const gridSize = 30;
+ const gridElement = document.getElementById('grid');
+ const speed = document.getElementById('speed');
+ const clearButton = document.getElementById('clear');
+ const randomButton = document.getElementById('random');
+
+ let grid = [];
+ let start = null;
+ let end = null;
+ let isDrawingWall = false;
+ let isSettingStart = false;
+ let isSettingEnd = false;
+ let isRunning = false;
+
+ function createGrid() {
+ gridElement.innerHTML = '';
+ grid = [];
+ for (let row = 0; row < gridSize; row++) {
+ grid[row] = [];
+ for (let col = 0; col < gridSize; col++) {
+ const cell = document.createElement('div');
+ cell.classList.add('cell');
+ cell.dataset.row = row;
+ cell.dataset.col = col;
+ cell.addEventListener('click', () => {
+ if (isDrawingWall) {
+ toggleWall(cell);
+ } else if (isSettingStart) {
+ setStart(cell);
+ } else if (isSettingEnd) {
+ setEnd(cell);
+ }
+ });
+ cell.addEventListener('mousedown', () => {
+ if (isDrawingWall) {
+ toggleWall(cell);
+ }
+ });
+ cell.addEventListener('mouseup', () => {
+ isDrawingWall = false;
+ });
+ gridElement.appendChild(cell);
+ grid[row][col] = cell;
+ }
+ }
+ }
+
+ function toggleWall(cell) {
+ cell.classList.toggle('wall');
+ }
+
+ function setStart(cell) {
+ if (start) start.classList.remove('start');
+ cell.classList.add('start');
+ start = cell;
+ }
+
+ function setEnd(cell) {
+ if (end) end.classList.remove('end');
+ cell.classList.add('end');
+ end = cell;
+ }
+
+ function isWall(cell) {
+ return cell.classList.contains('wall');
+ }
+
+ function getCell(row, col) {
+ return grid[row][col];
+ }
+
+ function heuristic(a, b) {
+ return Math.abs(a.row - b.row) + Math.abs(a.col - b.col);
+ }
+
+ function getNeighbors(cell) {
+ const row = parseInt(cell.dataset.row);
+ const col = parseInt(cell.dataset.col);
+ const neighbors = [];
+ const directions = [
+ { row: -1, col: 0 }, { row: 1, col: 0 },
+ { row: 0, col: -1 }, { row: 0, col: 1 }
+ ];
+ for (const dir of directions) {
+ const neighborRow = row + dir.row;
+ const neighborCol = col + dir.col;
+ if (
+ neighborRow >= 0 && neighborRow < gridSize &&
+ neighborCol >= 0 && neighborCol < gridSize
+ ) {
+ const neighbor = getCell(neighborRow, neighborCol);
+ if (!isWall(neighbor)) {
+ neighbors.push({ cell: neighbor, row: neighborRow, col: neighborCol });
+ }
+ }
+ }
+ return neighbors;
+ }
+
+ function sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ function aStar() {
+ if (!start || !end) return;
+ const openSet = [start];
+ const cameFrom = {};
+ const gScore = {};
+ const fScore = {};
+ gScore[start] = 0;
+ fScore[start] = heuristic(start, end);
+
+ while (openSet.length > 0) {
+ let current = openSet.reduce((min, node) => {
+ return fScore[node] < fScore[min] ? node : min;
+ }, openSet[0]);
+
+ if (current === end) {
+ const path = [];
+ let node = current;
+ while (node in cameFrom) {
+ path.push(node);
+ node = cameFrom[node];
+ }
+ path.reverse();
+ for (const node of path) {
+ node.classList.add('path');
+ }
+ return;
+ }
+
+ openSet = openSet.filter(n => n !== current);
+ current.classList.add('closed');
+
+ for (const neighbor of getNeighbors(current)) {
+ const tentativeGScore = gScore[current] + 1;
+ if (!(neighbor.cell in gScore) || tentativeGScore < gScore[neighbor.cell]) {
+ cameFrom[neighbor.cell] = current;
+ gScore[neighbor.cell] = tentativeGScore;
+ fScore[neighbor.cell] = gScore[neighbor.cell] + heuristic(neighbor, end);
+ if (!openSet.includes(neighbor.cell)) {
+ openSet.push(neighbor.cell);
+ neighbor.cell.classList.add('open');
+ }
+ }
+ }
+
+ await sleep(1000 / parseInt(speed.value));
+ }
+ }
+
+ function randomMaze() {
+ createGrid();
+ for (let row = 0; row < gridSize; row++) {
+ for (let col = 0; col < gridSize; col++) {
+ if (Math.random() < 0.2) {
+ getCell(row, col).classList.add('wall');
+ }
+ }
+ }
+ }
+
+ clearButton.addEventListener('click', () => {
+ createGrid();
+ });
+
+ randomButton.addEventListener('click', randomMaze);
+
+ document.addEventListener('keydown', e => {
+ if (e.key === 'w') isDrawingWall = true;
+ if (e.key === 's') isSettingStart = true;
+ if (e.key === 'e') isSettingEnd = true;
+ });
+
+ document.addEventListener('keyup', e => {
+ if (e.key === 'w') isDrawingWall = false;
+ if (e.key === 's') isSettingStart = false;
+ if (e.key === 'e') isSettingEnd = false;
+ });
+
+ document.addEventListener('click', () => {
+ if (!isRunning) {
+ isRunning = true;
+ aStar();
+ isRunning = false;
+ }
+ });
+
+ createGrid();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6c13ff8455cc/qwen3-14b.png b/data/artifacts/6c13ff8455cc/qwen3-14b.png
new file mode 100644
index 0000000..0edd39f
Binary files /dev/null and b/data/artifacts/6c13ff8455cc/qwen3-14b.png differ
diff --git a/data/artifacts/905cc3052af2/gemma3-12b.html b/data/artifacts/905cc3052af2/gemma3-12b.html
new file mode 100644
index 0000000..8315833
--- /dev/null
+++ b/data/artifacts/905cc3052af2/gemma3-12b.html
@@ -0,0 +1,197 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Domino Run</title>
+<style>
+body { margin: 0; overflow: hidden; }
+canvas { background-color: #eee; display: block; }
+#controls { position: absolute; top: 10px; left: 10px; padding: 5px; font-family: sans-serif; }
+</style>
+</head>
+<body>
+
+<div id="controls">
+ <button id="startBtn">Start</button>
+ <button id="resetBtn">Reset</button>
+ <br>
+ <select id="layoutSelect">
+ <option value="0">Empty</option>
+ <option value="1">Preset 1</option>
+ <option value="2">Preset 2</option>
+ </select>
+</div>
+
+<canvas id="dominoCanvas" width="800" height="600"></canvas>
+
+<script>
+const canvas = document.getElementById("dominoCanvas");
+const ctx = canvas.getContext("2d");
+const startBtn = document.getElementById("startBtn");
+const resetBtn = document.getElementById("resetBtn");
+const layoutSelect = document.getElementById("layoutSelect");
+
+let dominoes = [];
+let mouseX, mouseY;
+let isDragging = false;
+
+const dominoWidth = 30;
+const dominoHeight = 15;
+const dominoSpacing = 2; // Space between dominoes
+
+function drawDomino(domino) {
+ ctx.fillStyle = "brown";
+ ctx.fillRect(domino.x, domino.y, dominoWidth, dominoHeight);
+ ctx.strokeStyle = "black";
+ ctx.strokeRect(domino.x, domino.y, dominoWidth, dominoHeight);
+}
+
+function checkCollisions() {
+ for (let i = 0; i < dominoes.length - 1; i++) {
+ const d1 = dominoes[i];
+ const d2 = dominoes[i + 1];
+
+ if (d1.x + dominoWidth > d2.x &&
+ d1.x < d2.x + dominoWidth &&
+ d1.y + dominoHeight > d2.y &&
+ d1.y < d2.y + dominoHeight) {
+ return true; // Overlapping
+ }
+ }
+ return false;
+}
+
+function resetDominoes() {
+ dominoes = [];
+}
+
+
+function loadPresetLayout(presetNumber) {
+ resetDominoes();
+
+ switch (presetNumber) {
+ case 1:
+ dominoes.push({ x: 50, y: 200 });
+ dominoes.push({ x: 80, y: 230 });
+ dominoes.push({ x: 110, y: 260 });
+ break;
+ case 2:
+ dominoes.push({x: 150, y: 150});
+ dominoes.push({x: 200, y: 180});
+ dominoes.push({x: 250, y: 210});
+ dominoes.push({x: 300, y: 240});
+
+ break;
+ }
+}
+
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ for (const domino of dominoes) {
+ drawDomino(domino);
+ }
+}
+
+
+
+canvas.addEventListener("mousemove", (e) => {
+ mouseX = e.offsetX;
+ mouseY = e.offsetY;
+});
+
+canvas.addEventListener("mousedown", () => {
+ isDragging = true;
+});
+
+canvas.addEventListener("mouseup", () => {
+ isDragging = false;
+});
+
+canvas.addEventListener("mouseout", () => {
+ isDragging = false;
+});
+
+
+let lastDominoX = -1; // Keep track of the x position of the last placed domino
+let lastDominoY = -1; // Keep track of the y position of the last placed domino
+
+
+
+canvas.addEventListener("click", (e) => {
+ if(isDragging){
+ const x = e.offsetX;
+ const y = e.offsetY;
+
+ //Basic validation, try to place near the last domino
+ let newX = x;
+ let newY = y;
+
+ if (lastDominoX !== -1) {
+ newX = Math.max(x, lastDominoX + dominoSpacing);
+ } else {
+ newX = x;
+ }
+
+ if (lastDominoY !== -1){
+ newY = y; //Just snap to the Y position for simplicity
+ }else{
+ newY=y;
+ }
+
+
+
+ const newDomino = { x: newX, y: newY };
+
+ //Simple collision check before adding domino
+ let isCollision = false;
+ for (const domino of dominoes) {
+ if (newDomino.x + dominoWidth > domino.x &&
+ newDomino.x < domino.x + dominoWidth &&
+ newDomino.y + dominoHeight > domino.y &&
+ newDomino.y < domino.y + dominoHeight) {
+ isCollision = true;
+ break;
+ }
+ }
+
+ if (!isCollision) {
+ dominoes.push(newDomino);
+ lastDominoX = newX;
+ lastDominoY = newY;
+ }
+ }
+});
+
+
+
+startBtn.addEventListener("click", () => {
+ if (dominoes.length > 0) {
+ dominoes[0].toppling = true; // Start the first domino falling
+ }
+});
+
+resetBtn.addEventListener("click", () => {
+ resetDominoes();
+ lastDominoX = -1;
+ lastDominoY = -1;
+});
+
+
+layoutSelect.addEventListener("change", () => {
+ const selectedPreset = parseInt(layoutSelect.value, 10);
+ loadPresetLayout(selectedPreset);
+});
+
+//Initial layout loading
+loadPresetLayout(0); // Start with an empty layout
+
+draw(); // Initial draw
+
+
+
+
+</script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/905cc3052af2/gemma3-12b.png b/data/artifacts/905cc3052af2/gemma3-12b.png
new file mode 100644
index 0000000..dc38f62
Binary files /dev/null and b/data/artifacts/905cc3052af2/gemma3-12b.png differ
diff --git a/data/artifacts/905cc3052af2/gpt.html b/data/artifacts/905cc3052af2/gpt.html
new file mode 100644
index 0000000..9e750aa
--- /dev/null
+++ b/data/artifacts/905cc3052af2/gpt.html
@@ -0,0 +1,397 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>Domino Run Physics Toy</title>
+<style>
+html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ background: #222;
+ font-family: sans-serif;
+ color: #eee;
+}
+#ui {
+ position: absolute;
+ top: 6px;
+ left: 6px;
+ right: 6px;
+ z-index: 10;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 6px;
+ pointer-events: none;
+}
+#ui button, #ui select, #ui span {
+ pointer-events: auto;
+}
+button, select {
+ background: #444;
+ color: #eee;
+ border: 1px solid #777;
+ border-radius: 4px;
+ padding: 4px 8px;
+ cursor: pointer;
+ font-size: 12px;
+}
+button:hover, select:hover {
+ background: #555;
+}
+#info {
+ font-size: 11px;
+ opacity: 0.9;
+ margin-left: 4px;
+}
+canvas {
+ display: block;
+}
+</style>
+</head>
+<body>
+<div id="ui">
+ <button id="startBtn">Start</button>
+ <button id="resetBtn">Reset</button>
+ <label style="font-size:11px;">Mode:
+ <select id="modeSel">
+ <option value="place">Place</option>
+ <option value="erase">Erase</option>
+ </select>
+ </label>
+ <label style="font-size:11px;">Preset:
+ <select id="presetSel">
+ <option value="">-- choose --</option>
+ <option value="line">Straight Line</option>
+ <option value="curve">Curved Path</option>
+ </select>
+ </label>
+ <span id="info">Click to place dominoes (in Place mode). Drag to pan, mouse wheel to zoom.</span>
+</div>
+<canvas id="cv"></canvas>
+<script>
+(function() {
+ const canvas = document.getElementById('cv');
+ const ctx = canvas.getContext('2d');
+ let width = window.innerWidth;
+ let height = window.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+
+ window.addEventListener('resize', () => {
+ width = window.innerWidth;
+ height = window.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+ });
+
+ // World / camera
+ let camX = 0, camY = 0, camScale = 1;
+ let isPanning = false;
+ let panLastX = 0, panLastY = 0;
+
+ // Domino physics parameters
+ const DOMINO_WIDTH = 12;
+ const DOMINO_HEIGHT = 40;
+ const DOMINO_MASS = 1;
+ const GRAVITY = 800; // downward accel
+ const FLOOR_Y = 0; // world floor is y=0
+ const FRICTION = 0.4;
+ const RESTITUTION = 0.2;
+
+ // Simulation
+ let dominoes = [];
+ let running = false;
+ let lastTime = null;
+
+ const modeSel = document.getElementById('modeSel');
+ const presetSel = document.getElementById('presetSel');
+ document.getElementById('startBtn').onclick = () => startSimulation();
+ document.getElementById('resetBtn').onclick = () => resetSimulation();
+ presetSel.onchange = () => {
+ const val = presetSel.value;
+ if (!val) return;
+ loadPreset(val);
+ presetSel.value = '';
+ };
+
+ // Domino object: x,y center in world, angle (rad), angular velocity, velocities, falling flag
+ function createDomino(x, y, angle) {
+ return {
+ x, y,
+ vx: 0,
+ vy: 0,
+ angle: angle || 0,
+ angVel: 0,
+ width: DOMINO_WIDTH,
+ height: DOMINO_HEIGHT,
+ mass: DOMINO_MASS,
+ falling: false,
+ settled: false
+ };
+ }
+
+ function resetSimulation() {
+ running = false;
+ lastTime = null;
+ dominoes.forEach(d => {
+ d.vx = d.vy = d.angVel = 0;
+ d.angle = 0;
+ d.falling = false;
+ d.settled = false;
+ });
+ }
+
+ function startSimulation() {
+ if (dominoes.length === 0) return;
+ resetSimulation();
+ dominoes[0].falling = true;
+ // Give slight push
+ dominoes[0].angVel = 2;
+ running = true;
+ }
+
+ function loadPreset(kind) {
+ dominoes = [];
+ camX = 0; camY = 0; camScale = 1;
+ const baseY = FLOOR_Y + DOMINO_HEIGHT / 2;
+ if (kind === 'line') {
+ const n = 25;
+ const spacing = 24;
+ const startX = -((n-1)*spacing)/2;
+ for (let i=0;i<n;i++) {
+ dominoes.push(createDomino(startX + i*spacing, baseY, 0));
+ }
+ } else if (kind === 'curve') {
+ const n = 40;
+ const radius = 250;
+ const centerX = 0;
+ const centerY = FLOOR_Y - 10;
+ const startAngle = -Math.PI * 0.1;
+ const endAngle = Math.PI * 0.9;
+ for (let i=0;i<n;i++) {
+ const t = i/(n-1);
+ const ang = startAngle + (endAngle-startAngle)*t;
+ const x = centerX + Math.cos(ang)*radius;
+ const y = baseY;
+ const d = createDomino(x, y, 0);
+ dominoes.push(d);
+ }
+ }
+ resetSimulation();
+ }
+
+ // Coordinate transforms
+ function screenToWorld(sx, sy) {
+ const x = (sx - width/2)/camScale + camX;
+ const y = (height/2 - sy)/camScale + camY;
+ return {x,y};
+ }
+
+ function worldToScreen(wx, wy) {
+ const sx = (wx - camX)*camScale + width/2;
+ const sy = height/2 - (wy - camY)*camScale;
+ return {sx, sy};
+ }
+
+ // Mouse interactions
+ let placing = false;
+ canvas.addEventListener('mousedown', (e) => {
+ if (e.button === 1 || (e.button === 0 && e.ctrlKey)) { // middle or ctrl+left: pan
+ isPanning = true;
+ panLastX = e.clientX;
+ panLastY = e.clientY;
+ return;
+ }
+ const rect = canvas.getBoundingClientRect();
+ const sx = e.clientX - rect.left;
+ const sy = e.clientY - rect.top;
+ const w = screenToWorld(sx, sy);
+ if (modeSel.value === 'place') {
+ const d = createDomino(w.x, FLOOR_Y + DOMINO_HEIGHT/2, 0);
+ dominoes.push(d);
+ placing = true;
+ } else if (modeSel.value === 'erase') {
+ // remove closest domino within radius
+ let bestIdx = -1;
+ let bestDist2 = 20*20;
+ dominoes.forEach((d, i) => {
+ const dx = d.x - w.x;
+ const dy = d.y - w.y;
+ const dist2 = dx*dx + dy*dy;
+ if (dist2 < bestDist2) {
+ bestDist2 = dist2;
+ bestIdx = i;
+ }
+ });
+ if (bestIdx >= 0) dominoes.splice(bestIdx,1);
+ }
+ });
+
+ canvas.addEventListener('mousemove', (e) => {
+ if (isPanning) {
+ const dx = e.clientX - panLastX;
+ const dy = e.clientY - panLastY;
+ panLastX = e.clientX;
+ panLastY = e.clientY;
+ camX -= dx / camScale;
+ camY += dy / camScale;
+ return;
+ }
+ });
+
+ window.addEventListener('mouseup', () => {
+ isPanning = false;
+ placing = false;
+ });
+
+ canvas.addEventListener('wheel', (e) => {
+ e.preventDefault();
+ const delta = -e.deltaY;
+ const zoomFactor = delta > 0 ? 1.1 : 0.9;
+ const rect = canvas.getBoundingClientRect();
+ const sx = e.clientX - rect.left;
+ const sy = e.clientY - rect.top;
+ const before = screenToWorld(sx, sy);
+ camScale *= zoomFactor;
+ camScale = Math.max(0.2, Math.min(4, camScale));
+ const after = screenToWorld(sx, sy);
+ camX += before.x - after.x;
+ camY += before.y - after.y;
+ }, {passive:false});
+
+ // Physics helpers
+ function updatePhysics(dt) {
+ const g = GRAVITY;
+
+ // Mark falling neighbors when one leans into another
+ for (let i=0;i<dominoes.length;i++) {
+ const d = dominoes[i];
+ if (!d.falling && !d.settled) continue;
+ // Check neighbor to the right (index+1) and left (index-1) for simple chain behavior
+ const neighbors = [i-1, i+1];
+ for (const ni of neighbors) {
+ if (ni < 0 || ni >= dominoes.length) continue;
+ const n = dominoes[ni];
+ if (n.falling || n.settled) continue;
+ const dx = n.x - d.x;
+ const dy = n.y - d.y;
+ const dist = Math.hypot(dx, dy);
+ if (dist < 30 && Math.abs(d.angle) > 0.3) {
+ n.falling = true;
+ // impart a bit of angular momentum depending on which side
+ n.angVel = (dx > 0 ? 2.2 : -2.2);
+ }
+ }
+ }
+
+ for (const d of dominoes) {
+ if (!d.falling && !d.settled) {
+ // stand upright, no physics until activated
+ continue;
+ }
+
+ if (!d.settled) {
+ // torque from gravity: approximate as torque = m*g*COM_x
+ const comX = (d.height/2) * Math.sin(d.angle);
+ const torque = -d.mass * g * comX;
+ const I = d.mass * (d.width*d.width + d.height*d.height)/12;
+ const angAcc = torque / I;
+ d.angVel += angAcc * dt;
+
+ // damping
+ d.angVel *= 0.99;
+
+ d.angle += d.angVel * dt;
+
+ // limit angle and floor collision: if lower corner hits floor, stop
+ const maxAngle = Math.PI/2 + 0.3;
+ if (d.angle > maxAngle) d.angle = maxAngle;
+ if (d.angle < -maxAngle) d.angle = -maxAngle;
+
+ // linear fall of center while tipping (small)
+ d.vy -= 0.1*g*dt;
+ d.vy *= 0.98;
+ d.y += d.vy * dt;
+
+ // if bottom corner below floor, correct
+ const bottomY = d.y - (d.height/2)*Math.cos(d.angle);
+ if (bottomY < FLOOR_Y) {
+ const diff = FLOOR_Y - bottomY;
+ d.y += diff;
+ d.vy *= -RESTITUTION;
+ }
+
+ // determine when settled: near flat and low velocities
+ if (Math.abs(d.angVel) < 0.1 &&
+ (Math.abs(d.angle) > Math.PI/2 - 0.15 || Math.abs(d.angle) < 0.05)) {
+ d.settled = true;
+ d.falling = false;
+ if (Math.abs(d.angle) < 0.05) d.angle = 0;
+ }
+ }
+ }
+ }
+
+ function drawDomino(d) {
+ const {sx, sy} = worldToScreen(d.x, d.y);
+ ctx.save();
+ ctx.translate(sx, sy);
+ ctx.rotate(-d.angle); // world y up, canvas y down
+ const w = d.width * camScale;
+ const h = d.height * camScale;
+ ctx.fillStyle = d.falling || d.settled ? '#f2b632' : '#ddd';
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ ctx.rect(-w/2, -h/2, w, h);
+ ctx.fill();
+ ctx.stroke();
+ // top mark
+ ctx.beginPath();
+ ctx.moveTo(-w/2, -h/2 + 3);
+ ctx.lineTo(w/2, -h/2 + 3);
+ ctx.strokeStyle = 'rgba(0,0,0,0.3)';
+ ctx.stroke();
+ ctx.restore();
+ }
+
+ function draw() {
+ ctx.setTransform(1,0,0,1,0,0);
+ ctx.clearRect(0,0,width,height);
+
+ // draw floor
+ const floorScreen = worldToScreen(0, FLOOR_Y);
+ ctx.strokeStyle = '#555';
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ ctx.moveTo(0, floorScreen.sy);
+ ctx.lineTo(width, floorScreen.sy);
+ ctx.stroke();
+
+ // draw dominoes
+ for (const d of dominoes) {
+ drawDomino(d);
+ }
+ }
+
+ function loop(timestamp) {
+ if (!lastTime) lastTime = timestamp;
+ const dt = Math.min(0.05, (timestamp - lastTime)/1000);
+ lastTime = timestamp;
+
+ if (running) {
+ updatePhysics(dt);
+ }
+ draw();
+
+ requestAnimationFrame(loop);
+ }
+
+ // Initialize with a default preset
+ loadPreset('line');
+ requestAnimationFrame(loop);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/905cc3052af2/gpt.png b/data/artifacts/905cc3052af2/gpt.png
new file mode 100644
index 0000000..381c099
Binary files /dev/null and b/data/artifacts/905cc3052af2/gpt.png differ
diff --git a/data/artifacts/905cc3052af2/grok.html b/data/artifacts/905cc3052af2/grok.html
new file mode 100644
index 0000000..773228f
--- /dev/null
+++ b/data/artifacts/905cc3052af2/grok.html
@@ -0,0 +1,840 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Domino Run Physics Toy</title>
+<style>
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ body {
+ font-family: "Segoe UI", system-ui, sans-serif;
+ background: #0f1221;
+ color: #e8eaf0;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ overflow: hidden;
+ }
+ h1 {
+ font-size: 1.15rem;
+ font-weight: 600;
+ letter-spacing: 0.04em;
+ margin: 12px 0 6px;
+ color: #c5c9e0;
+ }
+ .toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ align-items: center;
+ justify-content: center;
+ padding: 8px 12px 12px;
+ width: 100%;
+ max-width: 960px;
+ }
+ button, select {
+ font: inherit;
+ font-size: 0.85rem;
+ border: none;
+ border-radius: 8px;
+ padding: 8px 14px;
+ cursor: pointer;
+ background: #2a3050;
+ color: #e8eaf0;
+ transition: background 0.15s, transform 0.1s;
+ }
+ button:hover { background: #3a4268; }
+ button:active { transform: scale(0.97); }
+ button.primary {
+ background: linear-gradient(135deg, #5b6cff, #7b4dff);
+ font-weight: 600;
+ }
+ button.primary:hover { background: linear-gradient(135deg, #6b7cff, #8b5dff); }
+ button.danger { background: #4a3040; }
+ button.danger:hover { background: #6a4058; }
+ button:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
+ select {
+ background: #1e2438;
+ border: 1px solid #3a4268;
+ padding-right: 28px;
+ }
+ .hint {
+ font-size: 0.75rem;
+ color: #7a80a0;
+ width: 100%;
+ text-align: center;
+ margin-bottom: 4px;
+ }
+ .wrap {
+ position: relative;
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 12px 40px rgba(0,0,0,0.45), 0 0 0 1px #2a3050;
+ max-width: calc(100vw - 24px);
+ }
+ canvas {
+ display: block;
+ background: #161a2e;
+ cursor: crosshair;
+ touch-action: none;
+ }
+ .stats {
+ position: absolute;
+ top: 10px;
+ left: 12px;
+ font-size: 0.72rem;
+ color: #6a7090;
+ pointer-events: none;
+ line-height: 1.5;
+ }
+</style>
+</head>
+<body>
+<h1> Domino Run </h1>
+<div class="toolbar">
+ <button class="primary" id="btnStart">▶ Start</button>
+ <button class="danger" id="btnReset">↺ Reset</button>
+ <button id="btnClear">Clear</button>
+ <select id="presets">
+ <option value="">Presets…</option>
+ <option value="line">Straight Line</option>
+ <option value="curve">Gentle Curve</option>
+ <option value="spiral">Spiral</option>
+ <option value="wave">Wave</option>
+ <option value="circle">Circle Burst</option>
+ <option value="stairs">Stairs</option>
+ </select>
+ <button id="btnUndo" title="Undo last placement">Undo</button>
+</div>
+<p class="hint">Click or drag on the canvas to place dominoes · Space = Start · R = Reset</p>
+<div class="wrap">
+ <canvas id="c" width="900" height="520"></canvas>
+ <div class="stats" id="stats"></div>
+</div>
+<script>
+(function () {
+ const canvas = document.getElementById("c");
+ const ctx = canvas.getContext("2d");
+ const statsEl = document.getElementById("stats");
+ const btnStart = document.getElementById("btnStart");
+ const btnReset = document.getElementById("btnReset");
+ const btnClear = document.getElementById("btnClear");
+ const btnUndo = document.getElementById("btnUndo");
+ const presetSel = document.getElementById("presets");
+
+ const W = canvas.width;
+ const H = canvas.height;
+ const GROUND_Y = H - 48;
+ const GRAVITY = 2200;
+ const DOM_W = 10;
+ const DOM_H = 52;
+ const FRICTION = 0.12;
+ const RESTITUTION = 0.15;
+ const AIR_DRAG = 0.998;
+ const ANG_DRAG = 0.995;
+ const PLACE_MIN_DIST = 18;
+
+ let dominoes = [];
+ let running = false;
+ let placing = false;
+ let lastPlace = null;
+ let ghosts = [];
+ let particles = [];
+ let time = 0;
+ let raf = 0;
+ let savedLayout = [];
+
+ function resize() {
+ const maxW = Math.min(900, window.innerWidth - 24);
+ const scale = maxW / 900;
+ canvas.style.width = maxW + "px";
+ canvas.style.height = Math.floor(520 * scale) + "px";
+ }
+ window.addEventListener("resize", resize);
+ resize();
+
+ class Domino {
+ constructor(x, y, angle) {
+ this.x = x;
+ this.y = y;
+ this.angle = angle || 0; // upright = 0, tips around base-ish center
+ this.omega = 0;
+ this.vx = 0;
+ this.vy = 0;
+ this.fallen = false;
+ this.active = true;
+ this.w = DOM_W;
+ this.h = DOM_H;
+ this.mass = 1;
+ this.inertia = (this.mass * (this.w * this.w + this.h * this.h)) / 12;
+ this.color = hslFor(x, y);
+ this.tipForce = 0;
+ }
+
+ // Center of mass when upright-ish; we model as free rigid body after tip starts
+ corners() {
+ const hw = this.w / 2, hh = this.h / 2;
+ const c = Math.cos(this.angle), s = Math.sin(this.angle);
+ const pts = [
+ [-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]
+ ];
+ return pts.map(([px, py]) => ({
+ x: this.x + px * c - py * s,
+ y: this.y + px * s + py * c
+ }));
+ }
+
+ draw(ctx) {
+ const c = Math.cos(this.angle), s = Math.sin(this.angle);
+ ctx.save();
+ ctx.translate(this.x, this.y);
+ ctx.rotate(this.angle);
+
+ // shadow
+ ctx.fillStyle = "rgba(0,0,0,0.25)";
+ ctx.fillRect(-this.w / 2 + 3, -this.h / 2 + 4, this.w, this.h);
+
+ // body
+ const g = ctx.createLinearGradient(-this.w / 2, 0, this.w / 2, 0);
+ g.addColorStop(0, shade(this.color, -25));
+ g.addColorStop(0.45, this.color);
+ g.addColorStop(1, shade(this.color, -35));
+ ctx.fillStyle = g;
+ roundRect(ctx, -this.w / 2, -this.h / 2, this.w, this.h, 2);
+ ctx.fill();
+
+ // top highlight
+ ctx.fillStyle = "rgba(255,255,255,0.18)";
+ ctx.fillRect(-this.w / 2 + 1, -this.h / 2 + 1, this.w - 2, 6);
+
+ // dots
+ ctx.fillStyle = "rgba(255,255,255,0.35)";
+ const dy = this.h * 0.18;
+ ctx.beginPath();
+ ctx.arc(0, -dy, 1.6, 0, Math.PI * 2);
+ ctx.arc(0, dy, 1.6, 0, Math.PI * 2);
+ ctx.fill();
+
+ // edge line
+ ctx.strokeStyle = "rgba(0,0,0,0.35)";
+ ctx.lineWidth = 1;
+ roundRect(ctx, -this.w / 2, -this.h / 2, this.w, this.h, 2);
+ ctx.stroke();
+
+ ctx.restore();
+ }
+ }
+
+ function hslFor(x, y) {
+ const h = (x * 0.4 + y * 0.15 + 200) % 360;
+ return `hsl(${h|0}, 62%, 58%)`;
+ }
+
+ function shade(hsl, amt) {
+ const m = /hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/.exec(hsl);
+ if (!m) return hsl;
+ let l = Math.max(10, Math.min(90, +m[3] + amt));
+ return `hsl(${m[1]}, ${m[2]}%, ${l}%)`;
+ }
+
+ function roundRect(ctx, x, y, w, h, r) {
+ ctx.beginPath();
+ ctx.moveTo(x + r, y);
+ ctx.arcTo(x + w, y, x + w, y + h, r);
+ ctx.arcTo(x + w, y + h, x, y + h, r);
+ ctx.arcTo(x, y + h, x, y, r);
+ ctx.arcTo(x, y, x + w, y, r);
+ ctx.closePath();
+ }
+
+ function placeDomino(x, y, angle) {
+ if (x < 20 || x > W - 20 || y < 40 || y > GROUND_Y - 10) return null;
+ // snap base to ground for upright placement
+ const d = new Domino(x, GROUND_Y - DOM_H / 2, angle || 0);
+ // prevent stacking too close
+ for (const o of dominoes) {
+ const dx = o.x - d.x, dy = o.y - d.y;
+ if (dx * dx + dy * dy < PLACE_MIN_DIST * PLACE_MIN_DIST) return null;
+ }
+ dominoes.push(d);
+ return d;
+ }
+
+ function saveLayout() {
+ savedLayout = dominoes.map(d => ({ x: d.x, y: d.y, angle: 0 }));
+ }
+
+ function restoreLayout() {
+ running = false;
+ particles = [];
+ dominoes = savedLayout.map(s => new Domino(s.x, GROUND_Y - DOM_H / 2, 0));
+ updateButtons();
+ }
+
+ // ——— Physics ———
+ function getAxes(corners) {
+ const axes = [];
+ for (let i = 0; i < 4; i++) {
+ const p1 = corners[i], p2 = corners[(i + 1) % 4];
+ const edgeX = p2.x - p1.x, edgeY = p2.y - p1.y;
+ const len = Math.hypot(edgeX, edgeY) || 1;
+ axes.push({ x: -edgeY / len, y: edgeX / len });
+ }
+ return axes;
+ }
+
+ function project(corners, axis) {
+ let min = Infinity, max = -Infinity;
+ for (const p of corners) {
+ const d = p.x * axis.x + p.y * axis.y;
+ if (d < min) min = d;
+ if (d > max) max = d;
+ }
+ return { min, max };
+ }
+
+ function sat(a, b) {
+ const ca = a.corners(), cb = b.corners();
+ const axes = getAxes(ca).concat(getAxes(cb));
+ let minOverlap = Infinity;
+ let normal = null;
+ for (const axis of axes) {
+ const pa = project(ca, axis), pb = project(cb, axis);
+ const overlap = Math.min(pa.max, pb.max) - Math.max(pa.min, pb.min);
+ if (overlap <= 0) return null;
+ if (overlap < minOverlap) {
+ minOverlap = overlap;
+ normal = axis;
+ }
+ }
+ // ensure normal points from a to b
+ const dx = b.x - a.x, dy = b.y - a.y;
+ if (normal.x * dx + normal.y * dy < 0) {
+ normal = { x: -normal.x, y: -normal.y };
+ }
+ return { overlap: minOverlap, nx: normal.x, ny: normal.y };
+ }
+
+ function resolveCollision(a, b, col) {
+ const nx = col.nx, ny = col.ny;
+ const overlap = col.overlap;
+
+ // separate
+ const totalMass = a.mass + b.mass;
+ const shareA = b.mass / totalMass;
+ const shareB = a.mass / totalMass;
+ // only push if both free-ish; ground-standing ones resist more
+ const aGrounded = !a.fallen && Math.abs(a.angle) < 0.15 && Math.abs(a.omega) < 0.5;
+ const bGrounded = !b.fallen && Math.abs(b.angle) < 0.15 && Math.abs(b.omega) < 0.5;
+
+ if (!aGrounded || a.fallen) {
+ a.x -= nx * overlap * (bGrounded ? 1 : shareA);
+ a.y -= ny * overlap * (bGrounded ? 1 : shareA);
+ }
+ if (!bGrounded || b.fallen) {
+ b.x += nx * overlap * (aGrounded ? 1 : shareB);
+ b.y += ny * overlap * (aGrounded ? 1 : shareB);
+ }
+
+ // relative velocity at approx contact (centers)
+ const rvx = b.vx - a.vx;
+ const rvy = b.vy - a.vy;
+ // include angular contribution roughly
+ const rel = rvx * nx + rvy * ny;
+ // also tip transfer: if one is rotating into the other
+ const tipBoost = (a.omega * a.h * 0.3 - b.omega * b.h * 0.3);
+
+ if (rel > 0 && Math.abs(tipBoost) < 50) {
+ // separating
+ }
+
+ const e = RESTITUTION;
+ const invMass = 1 / a.mass + 1 / b.mass;
+ let j = -(1 + e) * rel / (invMass + 0.001);
+ // angular impulse transfer for domino cascade feel
+ const angFactor = 0.55;
+ j += tipBoost * 0.15;
+
+ const ix = j * nx, iy = j * ny;
+ a.vx -= ix / a.mass;
+ a.vy -= iy / a.mass;
+ b.vx += ix / b.mass;
+ b.vy += iy / b.mass;
+
+ // torque: contact assumed near mid-height edge
+ const torqueScale = 0.02;
+ a.omega -= (ix * 8 + tipBoost * 0.1) * torqueScale / a.inertia * a.mass;
+ b.omega += (ix * 8 + a.omega * angFactor) * 0.8 * torqueScale / b.inertia * b.mass;
+
+ // special cascade: upright domino hit near top gets strong tip
+ if (bGrounded && !b.fallen) {
+ const hitDir = Math.sign(a.x - b.x) || 1;
+ const impact = Math.abs(a.omega) * a.h * 0.5 + Math.hypot(a.vx, a.vy) * 0.4;
+ if (impact > 20 || Math.abs(a.angle) > 0.35) {
+ b.omega += -hitDir * Math.min(12, 3 + impact * 0.08);
+ b.fallen = true;
+ spawnSparks(b.x, b.y - b.h * 0.3, b.color);
+ }
+ }
+ if (aGrounded && !a.fallen) {
+ const hitDir = Math.sign(b.x - a.x) || 1;
+ const impact = Math.abs(b.omega) * b.h * 0.5 + Math.hypot(b.vx, b.vy) * 0.4;
+ if (impact > 20 || Math.abs(b.angle) > 0.35) {
+ a.omega += -hitDir * Math.min(12, 3 + impact * 0.08);
+ a.fallen = true;
+ spawnSparks(a.x, a.y - a.h * 0.3, a.color);
+ }
+ }
+ }
+
+ function groundCollide(d) {
+ const corners = d.corners();
+ let maxPen = 0;
+ let cx = 0, cy = 0, n = 0;
+ for (const p of corners) {
+ if (p.y > GROUND_Y) {
+ const pen = p.y - GROUND_Y;
+ if (pen > maxPen) maxPen = pen;
+ cx += p.x; cy += GROUND_Y; n++;
+ }
+ }
+ if (n === 0) return;
+
+ // lift out
+ d.y -= maxPen;
+
+ // velocity response at contact
+ if (d.vy > 0) d.vy *= -RESTITUTION * 0.5;
+ d.vx *= (1 - FRICTION);
+ d.omega *= (1 - FRICTION * 0.8);
+
+ // settle flat-ish on ground
+ const flat = Math.abs(Math.abs(d.angle) % Math.PI - Math.PI / 2);
+ // damp angle toward lying flat when mostly down
+ if (Math.abs(d.angle) > 0.9) {
+ const target = d.angle > 0 ? Math.PI / 2 : -Math.PI / 2;
+ d.angle += (target - d.angle) * 0.08;
+ d.omega *= 0.85;
+ if (Math.abs(d.omega) < 0.3 && Math.abs(d.angle - target) < 0.08) {
+ d.angle = target;
+ d.omega = 0;
+ d.vx *= 0.9;
+ d.vy = 0;
+ }
+ }
+
+ // keep center above ground based on orientation
+ const halfProj = (Math.abs(Math.cos(d.angle)) * d.h + Math.abs(Math.sin(d.angle)) * d.w) / 2;
+ const minY = GROUND_Y - halfProj;
+ if (d.y > minY) {
+ d.y = minY;
+ if (d.vy > 0) d.vy = 0;
+ }
+ }
+
+ function wallCollide(d) {
+ const hw = (Math.abs(Math.cos(d.angle)) * d.w + Math.abs(Math.sin(d.angle)) * d.h) / 2;
+ if (d.x - hw < 8) { d.x = 8 + hw; d.vx *= -0.3; d.omega *= -0.5; }
+ if (d.x + hw > W - 8) { d.x = W - 8 - hw; d.vx *= -0.3; d.omega *= -0.5; }
+ }
+
+ function spawnSparks(x, y, color) {
+ for (let i = 0; i < 6; i++) {
+ particles.push({
+ x, y,
+ vx: (Math.random() - 0.5) * 180,
+ vy: -Math.random() * 160 - 40,
+ life: 0.35 + Math.random() * 0.25,
+ age: 0,
+ color
+ });
+ }
+ }
+
+ function step(dt) {
+ time += dt;
+ if (!running) return;
+
+ const sub = 3;
+ const h = dt / sub;
+ for (let s = 0; s < sub; s++) {
+ for (const d of dominoes) {
+ if (!d.active) continue;
+
+ // gravity on CM
+ d.vy += GRAVITY * h;
+
+ // if still nearly upright and not tipping hard, apply restoring / standing friction
+ if (!d.fallen && Math.abs(d.angle) < 0.12 && Math.abs(d.omega) < 0.8) {
+ // slight upright stability until hit
+ d.omega *= 0.5;
+ d.angle *= 0.5;
+ d.vx *= 0.5;
+ // stick to ground height
+ d.y = GROUND_Y - d.h / 2;
+ d.vy = 0;
+ } else {
+ d.fallen = true;
+ // tipping: gravity torque (unstable equilibrium past vertical)
+ // torque ~ m*g*(h/2)*sin(angle) once past vertical for free fall rotation
+ const torque = d.mass * GRAVITY * (d.h / 2) * Math.sin(d.angle) * 0.45;
+ d.omega += (torque / d.inertia) * h;
+ }
+
+ d.vx *= AIR_DRAG;
+ d.vy *= AIR_DRAG;
+ d.omega *= ANG_DRAG;
+
+ d.x += d.vx * h;
+ d.y += d.vy * h;
+ d.angle += d.omega * h;
+
+ // clamp wild spins
+ if (d.omega > 25) d.omega = 25;
+ if (d.omega < -25) d.omega = -25;
+
+ groundCollide(d);
+ wallCollide(d);
+ }
+
+ // pairwise collisions
+ for (let i = 0; i < dominoes.length; i++) {
+ for (let j = i + 1; j < dominoes.length; j++) {
+ const a = dominoes[i], b = dominoes[j];
+ const dx = a.x - b.x, dy = a.y - b.y;
+ if (dx * dx + dy * dy > (DOM_H * 1.4) * (DOM_H * 1.4)) continue;
+ const col = sat(a, b);
+ if (col) resolveCollision(a, b, col);
+ }
+ }
+ }
+
+ // particles
+ for (let i = particles.length - 1; i >= 0; i--) {
+ const p = particles[i];
+ p.age += dt;
+ p.vy += GRAVITY * 0.4 * dt;
+ p.x += p.vx * dt;
+ p.y += p.vy * dt;
+ if (p.age >= p.life) particles.splice(i, 1);
+ }
+ }
+
+ // ——— Rendering ———
+ function drawBackground() {
+ // subtle grid
+ ctx.save();
+ ctx.strokeStyle = "rgba(80,90,140,0.08)";
+ ctx.lineWidth = 1;
+ for (let x = 0; x < W; x += 40) {
+ ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, GROUND_Y); ctx.stroke();
+ }
+ for (let y = 0; y < GROUND_Y; y += 40) {
+ ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
+ }
+
+ // ground
+ const gg = ctx.createLinearGradient(0, GROUND_Y, 0, H);
+ gg.addColorStop(0, "#2a3348");
+ gg.addColorStop(0.15, "#1c2235");
+ gg.addColorStop(1, "#12162a");
+ ctx.fillStyle = gg;
+ ctx.fillRect(0, GROUND_Y, W, H - GROUND_Y);
+
+ // ground line
+ ctx.strokeStyle = "#4a5580";
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ ctx.moveTo(0, GROUND_Y + 0.5);
+ ctx.lineTo(W, GROUND_Y + 0.5);
+ ctx.stroke();
+
+ // floor sheen
+ ctx.fillStyle = "rgba(100,120,180,0.06)";
+ ctx.fillRect(0, GROUND_Y, W, 6);
+
+ ctx.restore();
+ }
+
+ function drawGhosts() {
+ for (const g of ghosts) {
+ ctx.save();
+ ctx.globalAlpha = 0.35;
+ ctx.translate(g.x, g.y);
+ ctx.fillStyle = "#8af";
+ roundRect(ctx, -DOM_W / 2, -DOM_H / 2, DOM_W, DOM_H, 2);
+ ctx.fill();
+ ctx.restore();
+ }
+ }
+
+ function drawParticles() {
+ for (const p of particles) {
+ const t = 1 - p.age / p.life;
+ ctx.globalAlpha = t;
+ ctx.fillStyle = p.color;
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, 2.2 * t, 0, Math.PI * 2);
+ ctx.fill();
+ }
+ ctx.globalAlpha = 1;
+ }
+
+ function render() {
+ ctx.clearRect(0, 0, W, H);
+ drawBackground();
+
+ // soft ambient light vignette
+ const vg = ctx.createRadialGradient(W / 2, H * 0.4, 80, W / 2, H * 0.5, W * 0.7);
+ vg.addColorStop(0, "rgba(40,50,90,0.0)");
+ vg.addColorStop(1, "rgba(8,10,20,0.35)");
+ ctx.fillStyle = vg;
+ ctx.fillRect(0, 0, W, GROUND_Y);
+
+ drawGhosts();
+
+ // draw dominoes back-to-front by y then fallen
+ const sorted = dominoes.slice().sort((a, b) => a.y - b.y);
+ for (const d of sorted) d.draw(ctx);
+
+ drawParticles();
+
+ // start marker
+ if (dominoes.length && !running) {
+ const first = dominoes[0];
+ ctx.save();
+ ctx.strokeStyle = "rgba(120,200,255,0.7)";
+ ctx.lineWidth = 2;
+ ctx.setLineDash([4, 4]);
+ ctx.beginPath();
+ ctx.arc(first.x, first.y - DOM_H / 2 - 14, 8, 0, Math.PI * 2);
+ ctx.stroke();
+ ctx.setLineDash([]);
+ ctx.fillStyle = "rgba(120,200,255,0.85)";
+ ctx.font = "11px system-ui,sans-serif";
+ ctx.textAlign = "center";
+ ctx.fillText("1st", first.x, first.y - DOM_H / 2 - 28);
+ ctx.restore();
+ }
+
+ const fallen = dominoes.filter(d => d.fallen || Math.abs(d.angle) > 0.5).length;
+ statsEl.textContent = `Dominoes: ${dominoes.length}` + (running ? ` · Fallen: ${fallen}` : "") +
+ (running ? " · Running" : " · Edit mode");
+ }
+
+ // ——— Loop ———
+ let lastTs = 0;
+ function loop(ts) {
+ if (!lastTs) lastTs = ts;
+ let dt = (ts - lastTs) / 1000;
+ lastTs = ts;
+ if (dt > 0.05) dt = 0.05;
+ step(dt);
+ render();
+ raf = requestAnimationFrame(loop);
+ }
+ raf = requestAnimationFrame(loop);
+
+ // ——— Interaction ———
+ function canvasPos(e) {
+ const r = canvas.getBoundingClientRect();
+ const sx = canvas.width / r.width;
+ const sy = canvas.height / r.height;
+ const src = e.touches ? e.touches[0] : e;
+ return {
+ x: (src.clientX - r.left) * sx,
+ y: (src.clientY - r.top) * sy
+ };
+ }
+
+ function onPointerDown(e) {
+ if (running) return;
+ e.preventDefault();
+ placing = true;
+ const p = canvasPos(e);
+ lastPlace = null;
+ tryPlace(p.x, p.y);
+ }
+
+ function onPointerMove(e) {
+ if (!placing || running) {
+ if (!running) {
+ const p = canvasPos(e);
+ ghosts = [{ x: p.x, y: GROUND_Y - DOM_H / 2 }];
+ }
+ return;
+ }
+ e.preventDefault();
+ const p = canvasPos(e);
+ tryPlace(p.x, p.y);
+ ghosts = [{ x: p.x, y: GROUND_Y - DOM_H / 2 }];
+ }
+
+ function onPointerUp() {
+ placing = false;
+ lastPlace = null;
+ if (!running) saveLayout();
+ }
+
+ function tryPlace(x, y) {
+ if (lastPlace) {
+ const dx = x - lastPlace.x, dy = y - lastPlace.y;
+ if (dx * dx + dy * dy < PLACE_MIN_DIST * PLACE_MIN_DIST) return;
+ }
+ const d = placeDomino(x, y, 0);
+ if (d) lastPlace = { x: d.x, y: d.y };
+ }
+
+ canvas.addEventListener("mousedown", onPointerDown);
+ canvas.addEventListener("mousemove", onPointerMove);
+ window.addEventListener("mouseup", onPointerUp);
+ canvas.addEventListener("touchstart", onPointerDown, { passive: false });
+ canvas.addEventListener("touchmove", onPointerMove, { passive: false });
+ window.addEventListener("touchend", onPointerUp);
+ canvas.addEventListener("mouseleave", () => { ghosts = []; });
+
+ function startRun() {
+ if (dominoes.length < 1 || running) return;
+ saveLayout();
+ running = true;
+ // tip the first domino
+ const first = dominoes[0];
+ first.fallen = true;
+ // tip toward the second if exists, else to the right
+ let dir = 1;
+ if (dominoes.length > 1) {
+ dir = Math.sign(dominoes[1].x - first.x) || 1;
+ }
+ first.omega = dir * 5.5;
+ first.angle = dir * 0.08;
+ spawnSparks(first.x + dir * 6, first.y - 10, first.color);
+ updateButtons();
+ }
+
+ function updateButtons() {
+ btnStart.disabled = running || dominoes.length === 0;
+ btnUndo.disabled = running || dominoes.length === 0;
+ }
+
+ btnStart.addEventListener("click", startRun);
+ btnReset.addEventListener("click", () => {
+ restoreLayout();
+ ghosts = [];
+ });
+ btnClear.addEventListener("click", () => {
+ running = false;
+ dominoes = [];
+ savedLayout = [];
+ particles = [];
+ ghosts = [];
+ updateButtons();
+ });
+ btnUndo.addEventListener("click", () => {
+ if (running || !dominoes.length) return;
+ dominoes.pop();
+ saveLayout();
+ updateButtons();
+ });
+
+ window.addEventListener("keydown", (e) => {
+ if (e.code === "Space") { e.preventDefault(); startRun(); }
+ if (e.code === "KeyR") restoreLayout();
+ if (e.code === "KeyZ" && (e.metaKey || e.ctrlKey)) {
+ e.preventDefault();
+ if (!running && dominoes.length) { dominoes.pop(); saveLayout(); updateButtons(); }
+ }
+ });
+
+ // ——— Presets ———
+ function loadPreset(name) {
+ running = false;
+ particles = [];
+ dominoes = [];
+ ghosts = [];
+
+ const add = (x, y) => placeDomino(x, y, 0);
+
+ if (name === "line") {
+ for (let i = 0; i < 28; i++) add(80 + i * 26, GROUND_Y);
+ } else if (name === "curve") {
+ for (let i = 0; i < 30; i++) {
+ const t = i / 29;
+ const x = 70 + t * 760;
+ const y = GROUND_Y; // still on ground; curve in x-spacing via arc offset visual path
+ add(x + Math.sin(t * Math.PI) * 40, y);
+ }
+ // rebuild with curved path positions on ground (x varies with sine)
+ dominoes = [];
+ for (let i = 0; i < 32; i++) {
+ const t = i / 31;
+ const x = 60 + t * 780 + Math.sin(t * Math.PI * 1.2) * 90;
+ add(x, GROUND_Y);
+ }
+ } else if (name === "spiral") {
+ let angle = 0;
+ let r = 30;
+ const cx = W * 0.5, cy = GROUND_Y; // place along spiral projected to x on ground
+ // spiral of positions along floor in a spiral-ish x,z flattened to x with slight jitter path
+ const pts = [];
+ for (let i = 0; i < 40; i++) {
+ angle += 0.45;
+ r += 4.5;
+ pts.push({
+ x: cx + Math.cos(angle) * r * 1.8,
+ // keep on ground
+ });
+ }
+ // space along the spiral arc
+ for (const p of pts) {
+ if (p.x > 40 && p.x < W - 40) add(p.x, GROUND_Y);
+ }
+ } else if (name === "wave") {
+ for (let i = 0; i < 36; i++) {
+ const t = i / 35;
+ const x = 50 + t * 800 + Math.sin(t * Math.PI * 4) * 28;
+ add(x, GROUND_Y);
+ }
+ } else if (name === "circle") {
+ const cx = W / 2, R = 160;
+ const n = 28;
+ for (let i = 0; i < n; i++) {
+ const a = (i / n) * Math.PI * 2 - Math.PI / 2;
+ add(cx + Math.cos(a) * R, GROUND_Y);
+ }
+ // radial spokes inward
+ for (let k = 0; k < 6; k++) {
+ const a = (k / 6) * Math.PI * 2;
+ for (let j = 1; j <= 4; j++) {
+ const rr = R - j * 28;
+ add(cx + Math.cos(a) * rr, GROUND_Y);
+ }
+ }
+ } else if (name === "stairs") {
+ // groups with increasing gaps then dense
+ let x = 60;
+ for (let g = 0; g < 6; g++) {
+ const count = 4 + g;
+ for (let i = 0; i < count; i++) {
+ add(x, GROUND_Y);
+ x += 20;
+ }
+ x += 28 + g * 6;
+ }
+ }
+
+ saveLayout();
+ updateButtons();
+ presetSel.value = "";
+ }
+
+ presetSel.addEventListener("change", () => {
+ if (presetSel.value) loadPreset(presetSel.value);
+ });
+
+ // initial preset
+ loadPreset("line");
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/905cc3052af2/grok.png b/data/artifacts/905cc3052af2/grok.png
new file mode 100644
index 0000000..bdf76cf
Binary files /dev/null and b/data/artifacts/905cc3052af2/grok.png differ
diff --git a/data/artifacts/905cc3052af2/hermes3-8b.html b/data/artifacts/905cc3052af2/hermes3-8b.html
new file mode 100644
index 0000000..3bd885c
--- /dev/null
+++ b/data/artifacts/905cc3052af2/hermes3-8b.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Domino Run</title>
+ <style>
+ body {
+ font-family: Arial, sans-serif;
+ text-align: center;
+ }
+
+ canvas {
+ border: 1px solid black;
+ }
+
+ button {
+ margin: 5px;
+ }
+
+ .preset-layout {
+ display: inline-block;
+ margin: 10px;
+ padding: 10px;
+ background-color: #f0f0f0;
+ }
+ </style>
+</head>
+<body>
+ <h1>Domino Run Physics Toy</h1>
+
+ <button id="startButton" onclick="startRun()">Start</button>
+ <button id="resetButton" onclick="resetGame()">Reset</button>
+
+ <div class="preset-layout" onclick="loadLayout('preset1')">
+ Preset 1
+ </div>
+ <div class="preset-layout" onclick="loadLayout('preset2')">
+ Preset 2
+ </div>
+
+ <canvas id="dominoCanvas" width="800" height="400"></canvas>
+
+ <script>
+ const canvas = document.getElementById("dominoCanvas");
+ const ctx = canvas.getContext("2d");
+
+ const dominoWidth = 15;
+ const dominoHeight = 80;
+
+ let dominos = [];
+ let isRunning = false;
+
+ function Domino(x, y) {
+ this.x = x;
+ this.y = y;
+ this.speedX = 0;
+ this.speedY = 0;
+ }
+
+ Domino.prototype.update = function () {
+ this.x += this.speedX;
+ this.y += this.speedY;
+
+ if (this.isColliding()) {
+ this.speedX = 0;
+ this.speedY = 0;
+ }
+ };
+
+ Domino.prototype.draw = function () {
+ ctx.fillStyle = "black";
+ ctx.fillRect(this.x, this.y, dominoWidth, dominoHeight);
+ };
+
+ Domino.prototype.isColliding = function () {
+ for (let i = 0; i < dominos.length; i++) {
+ const otherDomino = dominos[i];
+ if (
+ otherDomino !== this &&
+ this.x + dominoWidth > otherDomino.x &&
+ this.x < otherDomino.x + dominoWidth &&
+ this.y + dominoHeight > otherDomino.y &&
+ this.y < otherDomino.y + dominoHeight
+ ) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ Domino.prototype.applyForce = function (x, y) {
+ this.speedX += x;
+ this.speedY += y;
+ };
+
+ function placeDomino(x, y) {
+ const newDomino = new Domino(x, y);
+ dominos.push(newDomino);
+ }
+
+ function removeDomino(index) {
+ dominos.splice(index, 1);
+ }
+
+ function startRun() {
+ isRunning = true;
+ for (let i = 0; i < dominos.length; i++) {
+ const domino = dominos[i];
+ if (i === 0) {
+ domino.applyForce(2, 0); // Topple the first domino
+ } else {
+ domino.applyForce(-1, 0); // Apply force to the rest of the dominoes
+ }
+ }
+ }
+
+ function resetGame() {
+ isRunning = false;
+ dominos = [];
+ }
+
+ function loadLayout(layout) {
+ switch (layout) {
+ case "preset1":
+ placeDomino(100, 200);
+ placeDomino(150, 180);
+ placeDomino(250, 140);
+ break;
+ case "preset2":
+ placeDomino(50, 300);
+ placeDomino(120, 260);
+ placeDomino(190, 220);
+ placeDomino(270, 180);
+ break;
+ }
+ }
+
+ canvas.addEventListener("click", function (event) {
+ const x = event.clientX - canvas.offsetLeft;
+ const y = event.clientY - canvas.offsetTop;
+
+ if (!isRunning) {
+ placeDomino(x, y);
+ }
+ });
+
+ function gameLoop() {
+ if (isRunning) {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ for (let i = 0; i < dominos.length; i++) {
+ const domino = dominos[i];
+ domino.update();
+ domino.draw();
+ }
+
+ setTimeout(gameLoop, 20);
+ }
+ }
+
+ gameLoop();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/905cc3052af2/hermes3-8b.png b/data/artifacts/905cc3052af2/hermes3-8b.png
new file mode 100644
index 0000000..af9cac6
Binary files /dev/null and b/data/artifacts/905cc3052af2/hermes3-8b.png differ
diff --git a/data/artifacts/905cc3052af2/hf-qwen-coder-32b.html b/data/artifacts/905cc3052af2/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..a292de7
--- /dev/null
+++ b/data/artifacts/905cc3052af2/hf-qwen-coder-32b.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Domino Run Physics Toy</title>
+<style>
+ body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f0f0f0; }
+ canvas { border: 1px solid #ccc; }
+ button { position: absolute; top: 10px; left: 10px; margin-right: 5px; }
+</style>
+</head>
+<body>
+<button onclick="reset()">Reset</button>
+<button onclick="startSimulation()">Start</button>
+<button onclick="loadLayout(1)">Layout 1</button>
+<button onclick="loadLayout(2)">Layout 2</button>
+<canvas id="dominoCanvas" width="800" height="600"></canvas>
+<script>
+const canvas = document.getElementById('dominoCanvas');
+const ctx = canvas.getContext('2d');
+let dominoes = [];
+let isPlacing = false;
+let simulationRunning = false;
+
+class Domino {
+ constructor(x, y) {
+ this.x = x;
+ this.y = y;
+ this.width = 30;
+ this.height = 10;
+ this.angle = 0;
+ this.angularVelocity = 0;
+ this.velocityX = 0;
+ this.velocityY = 0;
+ this.fixed = false;
+ }
+
+ draw() {
+ ctx.save();
+ ctx.translate(this.x, this.y);
+ ctx.rotate(this.angle);
+ ctx.fillStyle = 'black';
+ ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
+ ctx.restore();
+ }
+
+ update() {
+ if (!this.fixed) {
+ this.velocityY += gravity;
+ this.x += this.velocityX;
+ this.y += this.velocityY;
+ this.angle += this.angularVelocity;
+ this.angularVelocity *= friction;
+ this.velocityX *= friction;
+ this.velocityY *= friction;
+ }
+ }
+
+ collideWith(domino) {
+ const dx = domino.x - this.x;
+ const dy = domino.y - this.y;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance < (this.width / 2 + domino.width / 2)) {
+ const angle = Math.atan2(dy, dx);
+ const sinA = Math.sin(angle);
+ const cosA = Math.cos(angle);
+
+ const x0_v1_prime_temp = this.velocityX * cosA + this.velocityY * sinA;
+ const y0_v1_prime = this.velocityX * sinA - this.velocityY * cosA;
+
+ const x1_v2_prime_temp = domino.velocityX * cosA + domino.velocityY * sinA;
+ const y1_v2_prime = domino.velocityX * sinA - domino.velocityY * cosA;
+
+ const x0_v1_prime = ((this.width - domino.width) * x0_v1_prime_temp + 2 * domino.width * x1_v2_prime_temp) / (this.width + domino.width);
+ const x1_v2_prime = ((domino.width - this.width) * x1_v2_prime_temp + 2 * this.width * x0_v1_prime_temp) / (this.width + domino.width);
+
+ this.velocityX = x0_v1_prime * cosA - y0_v1_prime * sinA;
+ this.velocityY = x0_v1_prime * sinA + y0_v1_prime * cosA;
+
+ domino.velocityX = x1_v2_prime * cosA - y1_v2_prime * sinA;
+ domino.velocityY = x1_v2_prime * sinA + y1_v2_prime * cosA;
+
+ const overlap = (this.width / 2 + domino.width / 2) - distance;
+ this.x -= overlap * dx / distance;
+ this.y -= overlap * dy / distance;
+ }
+ }
+}
+
+let gravity = 0.1;
+let friction = 0.99;
+canvas.addEventListener('mousedown', (e) => {
+ if (!simulationRunning) {
+ isPlacing = true;
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ dominoes.push(new Domino(x, y));
+ }
+});
+
+canvas.addEventListener('mouseup', () => {
+ if (isPlacing && !simulationRunning) {
+ isPlacing = false;
+ }
+});
+
+function startSimulation() {
+ simulationRunning = true;
+ dominoes[0].fixed = true;
+ requestAnimationFrame(simulate);
+}
+
+function simulate() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ for (let i = 0; i < dominoes.length; i++) {
+ dominoes[i].update();
+ for (let j = i + 1; j < dominoes.length; j++) {
+ dominoes[i].collideWith(dominoes[j]);
+ }
+ dominoes[i].draw();
+ }
+
+ if (simulationRunning) requestAnimationFrame(simulate);
+}
+
+function reset() {
+ simulationRunning = false;
+ dominoes = [];
+}
+
+function loadLayout(num) {
+ reset();
+ if (num === 1) {
+ for (let i = 0; i < 20; i++) {
+ dominoes.push(new Domino(50 + i * 35, 290));
+ }
+ } else if (num === 2) {
+ for (let i = 0; i < 10; i++) {
+ dominoes.push(new Domino(50 + i * 35, 290));
+ dominoes.push(new Domino(85 + i * 35, 270));
+ }
+ }
+}
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/905cc3052af2/hf-qwen-coder-32b.png b/data/artifacts/905cc3052af2/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..4365507
Binary files /dev/null and b/data/artifacts/905cc3052af2/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/905cc3052af2/kimi.html b/data/artifacts/905cc3052af2/kimi.html
new file mode 100644
index 0000000..8d7c684
--- /dev/null
+++ b/data/artifacts/905cc3052af2/kimi.html
@@ -0,0 +1,347 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Domino Run Physics</title>
+<style>
+body {
+ margin: 0;
+ overflow: hidden;
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background: #1a1a1a;
+}
+canvas {
+ display: block;
+ background: linear-gradient(to bottom, #87CEEB 0%, #E0F6FF 60%, #8B7355 60%, #654321 100%);
+}
+#controls {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ display: flex;
+ gap: 12px;
+ background: rgba(255, 255, 255, 0.95);
+ padding: 15px;
+ border-radius: 12px;
+ box-shadow: 0 4px 15px rgba(0,0,0,0.3);
+ flex-wrap: wrap;
+ max-width: 90vw;
+}
+button {
+ padding: 10px 20px;
+ font-size: 14px;
+ font-weight: 600;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.2s;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+#startBtn {
+ background: #4CAF50;
+ color: white;
+}
+#startBtn:hover {
+ background: #45a049;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(76,175,80,0.3);
+}
+#resetBtn {
+ background: #f44336;
+ color: white;
+}
+#resetBtn:hover {
+ background: #da190b;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(244,67,54,0.3);
+}
+.preset-btn {
+ background: #2196F3;
+ color: white;
+}
+.preset-btn:hover {
+ background: #0b7dda;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(33,150,243,0.3);
+}
+#info {
+ position: absolute;
+ bottom: 20px;
+ left: 20px;
+ color: white;
+ background: rgba(0,0,0,0.6);
+ padding: 12px 18px;
+ border-radius: 8px;
+ font-size: 13px;
+ pointer-events: none;
+ backdrop-filter: blur(4px);
+}
+</style>
+</head>
+<body>
+<div id="controls">
+ <button id="startBtn">▶ Start</button>
+ <button id="resetBtn">↺ Reset</button>
+ <button class="preset-btn" onclick="loadPreset('classic')">Classic Line</button>
+ <button class="preset-btn" onclick="loadPreset('stagger')">Staggered</button>
+ <button class="preset-btn" onclick="loadPreset('dense')">Rapid Fire</button>
+</div>
+<div id="info">Click anywhere on the ground to place dominoes • Press Start to trigger the cascade</div>
+<canvas id="canvas"></canvas>
+<script>
+const canvas = document.getElementById('canvas');
+const ctx = canvas.getContext('2d');
+
+let width, height;
+let groundLevel;
+let dominoes = [];
+let animationId;
+let mouseX = 0;
+let mouseY = 0;
+
+function resize() {
+ width = window.innerWidth;
+ height = window.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+ groundLevel = height * 0.75;
+}
+window.addEventListener('resize', resize);
+resize();
+
+class Domino {
+ constructor(x, y) {
+ this.x = x;
+ this.y = y || groundLevel;
+ this.width = 16;
+ this.height = 50;
+ this.angle = 0; // 0 = upright, positive = falling right (clockwise)
+ this.angularVelocity = 0;
+ this.state = 'standing'; // standing, falling, fallen
+ this.color = '#f5f5f5';
+ this.shadowColor = 'rgba(0,0,0,0.2)';
+ }
+
+ getTopPosition() {
+ // Calculate top center based on current angle
+ // When angle=0, top is at (x, y-height)
+ // When angle=90deg (PI/2), top is at (x+height, y)
+ return {
+ x: this.x + this.height * Math.sin(this.angle),
+ y: this.y - this.height * Math.cos(this.angle)
+ };
+ }
+
+ update() {
+ if (this.state === 'falling') {
+ // Gravity torque approximation
+ // Acceleration increases as it gets further from vertical
+ const gravity = 0.002;
+ const acceleration = gravity * (1 + Math.sin(this.angle));
+ this.angularVelocity += acceleration;
+
+ // Air resistance / friction
+ this.angularVelocity *= 0.985;
+
+ // Update angle
+ this.angle += this.angularVelocity;
+
+ // Hit the ground
+ if (this.angle >= Math.PI / 2) {
+ this.angle = Math.PI / 2;
+ this.state = 'fallen';
+ this.angularVelocity = 0;
+ this.color = '#e0e0e0'; // Darker when fallen
+ }
+ }
+ }
+
+ draw(ctx) {
+ ctx.save();
+
+ // Shadow
+ if (this.state !== 'fallen') {
+ ctx.fillStyle = 'rgba(0,0,0,0.15)';
+ ctx.beginPath();
+ ctx.ellipse(this.x, this.y + 2, this.width/2, 4, 0, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ // Translate to pivot point (bottom center)
+ ctx.translate(this.x, this.y);
+ ctx.rotate(this.angle);
+
+ // Draw domino body (centered on pivot, extending upward)
+ ctx.fillStyle = this.color;
+ ctx.strokeStyle = '#333';
+ ctx.lineWidth = 1.5;
+
+ // Main rectangle
+ ctx.fillRect(-this.width/2, -this.height, this.width, this.height);
+ ctx.strokeRect(-this.width/2, -this.height, this.width, this.height);
+
+ // Detail line to show rotation
+ ctx.beginPath();
+ ctx.moveTo(0, -this.height + 5);
+ ctx.lineTo(0, -5);
+ ctx.strokeStyle = '#999';
+ ctx.lineWidth = 2;
+ ctx.stroke();
+
+ ctx.restore();
+ }
+}
+
+function checkCollisions() {
+ // Sort by x position to ensure left-to-right cascade
+ dominoes.sort((a, b) => a.x - b.x);
+
+ for (let i = 0; i < dominoes.length - 1; i++) {
+ const current = dominoes[i];
+ const next = dominoes[i + 1];
+
+ if (current.state === 'falling' && next.state === 'standing') {
+ const top = current.getTopPosition();
+
+ // Check if top of current is near the body of next
+ // Next domino body bounds (approximate)
+ const nextLeft = next.x - next.width/2;
+ const nextRight = next.x + next.width/2;
+ const nextTop = next.y - next.height;
+ const nextBottom = next.y;
+
+ // Check collision: top point intersects with next domino's rectangle
+ // With a small threshold for early triggering
+ if (top.x > nextLeft - 5 &&
+ top.x < nextRight + 5 &&
+ top.y > nextTop - 10 &&
+ top.y < nextBottom &&
+ current.angle > 0.15) { // Must be tilted enough
+
+ next.state = 'falling';
+ // Transfer some momentum
+ next.angularVelocity = current.angularVelocity * 0.7;
+ }
+ }
+ }
+}
+
+function animate() {
+ ctx.clearRect(0, 0, width, height);
+
+ // Draw ground line
+ ctx.strokeStyle = '#4a3728';
+ ctx.lineWidth = 4;
+ ctx.beginPath();
+ ctx.moveTo(0, groundLevel);
+ ctx.lineTo(width, groundLevel);
+ ctx.stroke();
+
+ // Draw ground texture
+ ctx.fillStyle = 'rgba(101, 67, 33, 0.3)';
+ ctx.fillRect(0, groundLevel, width, height - groundLevel);
+
+ // Update physics
+ dominoes.forEach(d => d.update());
+ checkCollisions();
+
+ // Draw dominoes
+ dominoes.forEach(d => d.draw(ctx));
+
+ // Draw ghost domino at mouse position
+ if (mouseY > groundLevel - 100) {
+ ctx.save();
+ ctx.globalAlpha = 0.4;
+ ctx.translate(mouseX, Math.max(mouseY, groundLevel));
+ ctx.fillStyle = '#fff';
+ ctx.strokeStyle = '#4CAF50';
+ ctx.lineWidth = 2;
+ ctx.fillRect(-8, -50, 16, 50);
+ ctx.strokeRect(-8, -50, 16, 50);
+ ctx.restore();
+ }
+
+ animationId = requestAnimationFrame(animate);
+}
+
+// Input handling
+canvas.addEventListener('mousemove', (e) => {
+ const rect = canvas.getBoundingClientRect();
+ mouseX = e.clientX - rect.left;
+ mouseY = e.clientY - rect.top;
+});
+
+canvas.addEventListener('click', (e) => {
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = Math.max(e.clientY - rect.top, groundLevel);
+
+ // Don't place too close to existing dominoes (prevent overlap)
+ const tooClose = dominoes.some(d => Math.abs(d.x - x) < 18);
+ if (!tooClose && y >= groundLevel - 10) {
+ dominoes.push(new Domino(x, groundLevel));
+ }
+});
+
+// Controls
+document.getElementById('startBtn').addEventListener('click', () => {
+ if (dominoes.length > 0) {
+ // Sort to ensure we start from leftmost
+ dominoes.sort((a, b) => a.x - b.x);
+ // Push the first one
+ dominoes[0].state = 'falling';
+ dominoes[0].angularVelocity = 0.08;
+ }
+});
+
+document.getElementById('resetBtn').addEventListener('click', () => {
+ dominoes.forEach(d => {
+ d.state = 'standing';
+ d.angle = 0;
+ d.angularVelocity = 0;
+ d.color = '#f5f5f5';
+ });
+});
+
+// Presets
+function loadPreset(type) {
+ dominoes = [];
+
+ if (type === 'classic') {
+ const startX = width * 0.2;
+ const endX = width * 0.8;
+ const count = 15;
+ const step = (endX - startX) / (count - 1);
+
+ for (let i = 0; i < count; i++) {
+ dominoes.push(new Domino(startX + i * step, groundLevel));
+ }
+ }
+ else if (type === 'stagger') {
+ // Two rows with offset
+ const startX = width * 0.2;
+ for (let i = 0; i < 8; i++) {
+ dominoes.push(new Domino(startX + i * 40, groundLevel));
+ if (i < 7) {
+ dominoes.push(new Domino(startX + i * 40 + 20, groundLevel - 30));
+ }
+ }
+ // Sort by x for proper chain
+ dominoes.sort((a, b) => a.x - b.x);
+ }
+ else if (type === 'dense') {
+ // Very close together for fast cascade
+ const startX = width * 0.15;
+ for (let i = 0; i < 25; i++) {
+ dominoes.push(new Domino(startX + i * 22, groundLevel));
+ }
+ }
+}
+
+// Start animation
+animate();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/905cc3052af2/kimi.png b/data/artifacts/905cc3052af2/kimi.png
new file mode 100644
index 0000000..8c2e3fe
Binary files /dev/null and b/data/artifacts/905cc3052af2/kimi.png differ
diff --git a/data/artifacts/905cc3052af2/qwen25-7b.html b/data/artifacts/905cc3052af2/qwen25-7b.html
new file mode 100644
index 0000000..07aa5a7
--- /dev/null
+++ b/data/artifacts/905cc3052af2/qwen25-7b.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Domino Run</title>
+ <style>
+ body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f0f0; }
+ canvas { border: 2px solid black; }
+ .controls { position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); }
+ </style>
+</head>
+<body>
+<canvas id="gameCanvas" width="800" height="600"></canvas>
+<div class="controls">
+ <button onclick="reset()">Reset</button>
+ <button onclick="setPreset('preset1')">Preset 1</button>
+ <button onclick="setPreset('preset2')">Preset 2</button>
+ <button onclick="start()">Start</button>
+</div>
+<script>
+const canvas = document.getElementById("gameCanvas");
+const ctx = canvas.getContext("2d");
+
+let dominoes = [];
+let started = false;
+
+function reset() {
+ dominoes = [];
+ draw();
+}
+
+function setPreset(preset) {
+ switch (preset) {
+ case 'preset1':
+ dominoes = [
+ {x: 50, y: 400},
+ {x: 200, y: 380},
+ {x: 350, y: 360}
+ ];
+ break;
+ case 'preset2':
+ dominoes = [
+ {x: 100, y: 400},
+ {x: 250, y: 380},
+ {x: 400, y: 360},
+ {x: 550, y: 340}
+ ];
+ break;
+ }
+ draw();
+}
+
+function start() {
+ if (!started) {
+ started = true;
+ for (let i = 0; i < dominoes.length; i++) {
+ let d = dominoes[i];
+ if (i === 0) {
+ d.isToppled = true;
+ d.velocityX = -5;
+ }
+ else {
+ setTimeout(() => dominoes[i].isToppled = true, i * 100);
+ }
+ }
+ }
+}
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ for (let d of dominoes) {
+ if (!d.isToppled) {
+ ctx.fillStyle = "#F06292";
+ ctx.fillRect(d.x, d.y, 50, 10);
+ ctx.strokeStyle = "black";
+ ctx.strokeRect(d.x, d.y, 50, 10);
+ }
+ }
+}
+
+document.addEventListener("mousedown", (e) => {
+ if (!started) {
+ let x = e.clientX - canvas.offsetLeft;
+ let y = e.clientY - canvas.offsetTop;
+ dominoes.push({x: x, y: y, isToppled: false});
+ }
+});
+
+setInterval(() => {
+ for (let d of dominoes) {
+ if (d.isToppled) {
+ d.y += 5;
+ ctx.fillStyle = "#F06292";
+ ctx.fillRect(d.x, d.y, 50, 10);
+ ctx.strokeStyle = "black";
+ ctx.strokeRect(d.x, d.y, 50, 10);
+
+ if (d.y > canvas.height) {
+ d.isToppled = false;
+ }
+ }
+ }
+}, 30);
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/905cc3052af2/qwen25-7b.png b/data/artifacts/905cc3052af2/qwen25-7b.png
new file mode 100644
index 0000000..4b811f7
Binary files /dev/null and b/data/artifacts/905cc3052af2/qwen25-7b.png differ
diff --git a/data/artifacts/905cc3052af2/qwen3-14b.html b/data/artifacts/905cc3052af2/qwen3-14b.html
new file mode 100644
index 0000000..6b88159
--- /dev/null
+++ b/data/artifacts/905cc3052af2/qwen3-14b.html
@@ -0,0 +1,362 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Domino Run Physics Toy</title>
+ <style>
+ body {
+ margin: 0;
+ background: #222;
+ color: #fff;
+ font-family: sans-serif;
+ overflow: hidden;
+ }
+ canvas {
+ display: block;
+ }
+ #controls {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ background: rgba(0,0,0,0.5);
+ padding: 10px;
+ border-radius: 5px;
+ z-index: 10;
+ }
+ button {
+ margin-right: 10px;
+ padding: 5px 10px;
+ background: #444;
+ color: #fff;
+ border: none;
+ border-radius: 3px;
+ cursor: pointer;
+ }
+ button:hover {
+ background: #666;
+ }
+ </style>
+</head>
+<body>
+ <div id="controls">
+ <button id="start">Start</button>
+ <button id="reset">Reset</button>
+ <button id="layout1">Layout 1</button>
+ <button id="layout2">Layout 2</button>
+ </div>
+ <canvas id="canvas"></canvas>
+ <script>
+ const canvas = document.getElementById("canvas");
+ 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 gravity = 980;
+ const friction = 0.99;
+ const restitution = 0.8;
+
+ 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);
+ }
+ div(s) {
+ return new Vector(this.x / s, this.y / s);
+ }
+ mag() {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ normalize() {
+ const m = this.mag();
+ return m === 0 ? this : this.div(m);
+ }
+ limit(max) {
+ const m = this.mag();
+ if (m > max) {
+ return this.normalize().mult(max);
+ }
+ return this;
+ }
+ }
+
+ class Body {
+ constructor(x, y, width, height, angle = 0) {
+ this.position = new Vector(x, y);
+ this.angle = angle;
+ this.width = width;
+ this.height = height;
+ this.mass = 1;
+ this.velocity = new Vector(0, 0);
+ this.angularVelocity = 0;
+ this.force = new Vector(0, 0);
+ }
+
+ applyForce(f) {
+ const fPerMass = f.div(this.mass);
+ this.force = this.force.add(fPerMass);
+ }
+
+ update() {
+ this.velocity = this.velocity.add(this.force.div(1));
+ this.position = this.position.add(this.velocity);
+ this.angularVelocity += this.torque;
+ this.angle += this.angularVelocity;
+ this.force = new Vector(0, 0);
+ this.torque = 0;
+ }
+
+ draw(ctx, color) {
+ ctx.save();
+ ctx.translate(this.position.x, this.position.y);
+ ctx.rotate(this.angle);
+ ctx.fillStyle = color;
+ ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
+ ctx.restore();
+ }
+
+ getBounds() {
+ const halfW = this.width / 2;
+ const halfH = this.height / 2;
+ const x = this.position.x;
+ const y = this.position.y;
+ const angle = this.angle;
+ const cos = Math.cos(angle);
+ const sin = Math.sin(angle);
+ return [
+ { x: x - halfW * cos - halfH * sin, y: y - halfW * sin + halfH * cos },
+ { x: x + halfW * cos - halfH * sin, y: y + halfW * sin + halfH * cos },
+ { x: x + halfW * cos + halfH * sin, y: y + halfW * sin - halfH * cos },
+ { x: x - halfW * cos + halfH * sin, y: y - halfW * sin - halfH * cos }
+ ];
+ }
+ }
+
+ class Domino extends Body {
+ constructor(x, y, width, height, angle = 0, isStanding = true) {
+ super(x, y, width, height, angle);
+ this.isStanding = isStanding;
+ this.standAngle = angle;
+ this.fallenAngle = angle + Math.PI / 2;
+ this.falling = false;
+ }
+
+ update() {
+ if (this.falling) {
+ super.update();
+ this.isStanding = false;
+ this.angle += Math.min(this.angularVelocity, 0.1);
+ }
+ }
+
+ draw(ctx) {
+ if (this.isStanding) {
+ super.draw(ctx, "#666");
+ } else {
+ super.draw(ctx, "#ccc");
+ }
+ }
+ }
+
+ class World {
+ constructor() {
+ this.bodies = [];
+ }
+
+ addBody(body) {
+ this.bodies.push(body);
+ }
+
+ update() {
+ for (let body of this.bodies) {
+ body.update();
+ }
+ }
+
+ draw(ctx) {
+ for (let body of this.bodies) {
+ body.draw(ctx);
+ }
+ }
+
+ applyGravity() {
+ for (let body of this.bodies) {
+ body.applyForce(new Vector(0, body.mass * gravity));
+ }
+ }
+
+ applyFriction() {
+ for (let body of this.bodies) {
+ body.velocity = body.velocity.mult(friction);
+ }
+ }
+
+ checkCollisions() {
+ for (let i = 0; i < this.bodies.length; i++) {
+ for (let j = i + 1; j < this.bodies.length; j++) {
+ const a = this.bodies[i];
+ const b = this.bodies[j];
+ this.checkCollision(a, b);
+ }
+ }
+ }
+
+ checkCollision(a, b) {
+ const aBounds = a.getBounds();
+ const bBounds = b.getBounds();
+
+ const minA = aBounds[0];
+ const maxA = aBounds[2];
+ const minB = bBounds[0];
+ const maxB = bBounds[2];
+
+ const overlapX = Math.max(0, (maxA.x - minB.x), (maxB.x - minA.x));
+ const overlapY = Math.max(0, (maxA.y - minB.y), (maxB.y - minA.y));
+
+ if (overlapX === 0 || overlapY === 0) return;
+
+ const normal = new Vector(overlapX, overlapY).normalize();
+ const depth = overlapX + overlapY;
+
+ const restitution = Math.min(a.restitution, b.restitution);
+ const relativeVelocity = b.velocity.sub(a.velocity);
+ const velAlongNormal = relativeVelocity.dot(normal);
+
+ if (velAlongNormal > 0) return;
+
+ const e = restitution;
+ const j = -(1 + e) * velAlongNormal / (1 / a.mass + 1 / b.mass);
+
+ const impulse = normal.mult(j);
+ a.velocity = a.velocity.sub(impulse.div(a.mass));
+ b.velocity = b.velocity.add(impulse.div(b.mass));
+
+ const penetration = depth / 2;
+ a.position = a.position.sub(normal.mult(penetration));
+ b.position = b.position.add(normal.mult(penetration));
+ }
+ }
+
+ const world = new World();
+ let dominoes = [];
+ let isRunning = false;
+ let lastTime = 0;
+ const dominoWidth = 30;
+ const dominoHeight = 100;
+ const dominoSpacing = 50;
+
+ const presets = {
+ "layout1": [
+ { x: 200, y: 300 },
+ { x: 250, y: 300 },
+ { x: 300, y: 300 },
+ { x: 350, y: 300 },
+ { x: 400, y: 300 },
+ { x: 450, y: 300 },
+ { x: 500, y: 300 },
+ { x: 550, y: 300 },
+ { x: 600, y: 300 },
+ { x: 650, y: 300 }
+ ],
+ "layout2": [
+ { x: 200, y: 300 },
+ { x: 250, y: 320 },
+ { x: 300, y: 340 },
+ { x: 350, y: 360 },
+ { x: 400, y: 380 },
+ { x: 450, y: 400 },
+ { x: 500, y: 420 },
+ { x: 550, y: 440 },
+ { x: 600, y: 460 },
+ { x: 650, y: 480 }
+ ]
+ };
+
+ function resetDominoes() {
+ dominoes.forEach(d => {
+ d.isStanding = true;
+ d.falling = false;
+ d.angle = d.standAngle;
+ d.velocity = new Vector(0, 0);
+ d.angularVelocity = 0;
+ });
+ }
+
+ function setupPresets(preset) {
+ dominoes.forEach(d => world.removeBody(d));
+ dominoes = [];
+ const positions = presets[preset];
+ for (let i = 0; i < positions.length; i++) {
+ const { x, y } = positions[i];
+ const domino = new Domino(x, y, dominoWidth, dominoHeight, Math.PI / 2);
+ domino.standAngle = Math.PI / 2;
+ domino.fallenAngle = Math.PI / 2 + Math.PI / 2;
+ world.addBody(domino);
+ dominoes.push(domino);
+ }
+ }
+
+ function updateDominoes(time) {
+ if (!isRunning) return;
+ const dt = (time - lastTime) / 1000;
+ world.applyGravity();
+ world.update();
+ world.applyFriction();
+ world.checkCollisions();
+ lastTime = time;
+ requestAnimationFrame(updateDominoes);
+ }
+
+ function startDominoChain() {
+ isRunning = true;
+ lastTime = performance.now();
+ requestAnimationFrame(updateDominoes);
+ }
+
+ function toggleDomino(x, y) {
+ const domino = dominoes.find(d => {
+ const dx = d.position.x - x;
+ const dy = d.position.y - y;
+ return Math.sqrt(dx * dx + dy * dy) < dominoWidth / 2;
+ });
+ if (domino) {
+ if (domino.isStanding) {
+ domino.isStanding = false;
+ domino.falling = true;
+ domino.velocity = new Vector(0, 10);
+ }
+ }
+ }
+
+ canvas.addEventListener("click", (e) => {
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ toggleDomino(x, y);
+ });
+
+ document.getElementById("start").addEventListener("click", startDominoChain);
+ document.getElementById("reset").addEventListener("click", () => {
+ resetDominoes();
+ isRunning = false;
+ });
+ document.getElementById("layout1").addEventListener("click", () => setupPresets("layout1"));
+ document.getElementById("layout2").addEventListener("click", () => setupPresets("layout2"));
+
+ setupPresets("layout1");
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/905cc3052af2/qwen3-14b.png b/data/artifacts/905cc3052af2/qwen3-14b.png
new file mode 100644
index 0000000..8369709
Binary files /dev/null and b/data/artifacts/905cc3052af2/qwen3-14b.png differ
diff --git a/data/artifacts/e3c185541c0d/claude-code.html b/data/artifacts/e3c185541c0d/claude-code.html
new file mode 100644
index 0000000..a5873c7
--- /dev/null
+++ b/data/artifacts/e3c185541c0d/claude-code.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>Matrix Digital Rain</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ overflow: hidden;
+ background: #000;
+ }
+ #rain {
+ display: block;
+ position: fixed;
+ inset: 0;
+ background: #000;
+ }
+ #themeBtn {
+ position: fixed;
+ top: 14px;
+ right: 14px;
+ z-index: 10;
+ font-family: "Courier New", monospace;
+ font-size: 13px;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ padding: 8px 14px;
+ color: var(--accent, #00ff66);
+ background: rgba(0, 0, 0, 0.55);
+ border: 1px solid var(--accent, #00ff66);
+ border-radius: 4px;
+ cursor: pointer;
+ transition: box-shadow 0.25s, color 0.25s, border-color 0.25s;
+ box-shadow: 0 0 8px rgba(0, 255, 102, 0.35), inset 0 0 6px rgba(0, 255, 102, 0.15);
+ user-select: none;
+ }
+ #themeBtn:hover {
+ box-shadow: 0 0 16px var(--accent, #00ff66), inset 0 0 10px rgba(255, 255, 255, 0.08);
+ }
+</style>
+</head>
+<body>
+<canvas id="rain"></canvas>
+<button id="themeBtn" type="button" aria-label="Cycle color theme">Theme: Green</button>
+<script>
+(function () {
+ "use strict";
+
+ var canvas = document.getElementById("rain");
+ var ctx = canvas.getContext("2d");
+ var btn = document.getElementById("themeBtn");
+
+ // Glyph set: katakana + digits + a few latin caps, classic Matrix mix
+ var GLYPHS = (function () {
+ var s = "";
+ for (var i = 0x30A0; i <= 0x30FF; i++) s += String.fromCharCode(i); // katakana block
+ s += "0123456789";
+ s += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ s += ":・.\"=*+-<>¦|";
+ return s;
+ })();
+
+ var THEMES = [
+ { name: "Green", head: "#d8ffe8", trail: [0, 255, 102] },
+ { name: "Amber", head: "#fff3d6", trail: [255, 176, 0] },
+ { name: "Cyan", head: "#e0feff", trail: [0, 229, 255] }
+ ];
+ var themeIndex = 0;
+
+ var fontSize = 18;
+ var columns = [];
+ var W = 0, H = 0, colCount = 0;
+ var dpr = Math.min(window.devicePixelRatio || 1, 2);
+
+ function makeColumn(i, randomizeStart) {
+ return {
+ x: i * fontSize,
+ // head position in rows; start above the screen at random depths
+ y: randomizeStart ? Math.floor(Math.random() * (H / fontSize)) : -Math.floor(Math.random() * 40),
+ speed: 0.6 + Math.random() * 0.9, // rows per frame-ish
+ acc: 0,
+ lastGlyph: ""
+ };
+ }
+
+ function resize() {
+ W = window.innerWidth;
+ H = window.innerHeight;
+ canvas.width = Math.floor(W * dpr);
+ canvas.height = Math.floor(H * dpr);
+ canvas.style.width = W + "px";
+ canvas.style.height = H + "px";
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+
+ // Scale glyph size a little with viewport so phones aren't overcrowded
+ fontSize = Math.max(14, Math.min(22, Math.floor(W / 60)));
+ colCount = Math.ceil(W / fontSize);
+
+ var next = [];
+ for (var i = 0; i < colCount; i++) {
+ next[i] = columns[i] ? columns[i] : makeColumn(i, true);
+ next[i].x = i * fontSize;
+ }
+ columns = next;
+
+ // Fresh black slate so trails don't smear weirdly after resize
+ ctx.fillStyle = "#000";
+ ctx.fillRect(0, 0, W, H);
+ ctx.font = fontSize + "px 'Courier New', monospace";
+ ctx.textBaseline = "top";
+ }
+
+ function randGlyph() {
+ return GLYPHS.charAt((Math.random() * GLYPHS.length) | 0);
+ }
+
+ function setThemeButton() {
+ var t = THEMES[themeIndex];
+ var rgb = "rgb(" + t.trail[0] + "," + t.trail[1] + "," + t.trail[2] + ")";
+ btn.textContent = "Theme: " + t.name;
+ btn.style.setProperty("--accent", rgb);
+ btn.style.boxShadow = "0 0 8px rgba(" + t.trail.join(",") + ",0.35), inset 0 0 6px rgba(" + t.trail.join(",") + ",0.15)";
+ }
+
+ btn.addEventListener("click", function () {
+ themeIndex = (themeIndex + 1) % THEMES.length;
+ setThemeButton();
+ });
+
+ var last = 0;
+ var FRAME = 1000 / 30; // classic rain reads best around 30fps
+
+ function draw(now) {
+ requestAnimationFrame(draw);
+ if (now - last < FRAME) return;
+ last = now;
+
+ var t = THEMES[themeIndex];
+ var r = t.trail[0], g = t.trail[1], b = t.trail[2];
+
+ // Fade pass — this is what creates the trails
+ ctx.fillStyle = "rgba(0, 0, 0, 0.08)";
+ ctx.fillRect(0, 0, W, H);
+
+ ctx.font = fontSize + "px 'Courier New', monospace";
+
+ for (var i = 0; i < colCount; i++) {
+ var col = columns[i];
+ col.acc += col.speed;
+ if (col.acc < 1) continue;
+ var steps = Math.floor(col.acc);
+ col.acc -= steps;
+
+ for (var s = 0; s < steps; s++) {
+ col.y++;
+ var px = col.x;
+ var py = col.y * fontSize;
+ if (py > H + fontSize * 4) {
+ // Recycle: restart above the top, mostly when off-screen, with a random chance to linger
+ if (Math.random() > 0.02) continue;
+ columns[i] = makeColumn(i, false);
+ col = columns[i];
+ continue;
+ }
+ if (py < -fontSize) continue;
+
+ // Re-dim the previous head so only the newest char glows
+ if (col.lastGlyph && py - fontSize >= -fontSize) {
+ ctx.shadowBlur = 0;
+ ctx.fillStyle = "rgba(0,0,0,1)";
+ ctx.fillRect(px, py - fontSize, fontSize, fontSize);
+ ctx.fillStyle = "rgba(" + r + "," + g + "," + b + ",0.85)";
+ ctx.fillText(col.lastGlyph, px, py - fontSize);
+ }
+
+ // Bright glowing head
+ var glyph = randGlyph();
+ col.lastGlyph = glyph;
+ ctx.shadowColor = "rgb(" + r + "," + g + "," + b + ")";
+ ctx.shadowBlur = fontSize * 0.7;
+ ctx.fillStyle = t.head;
+ ctx.fillText(glyph, px, py);
+ ctx.shadowBlur = 0;
+ }
+
+ // Occasionally mutate a random glyph in the visible trail for shimmer
+ if (Math.random() < 0.06) {
+ var rowsVisible = Math.floor(H / fontSize);
+ var ry = col.y - 2 - ((Math.random() * 18) | 0);
+ if (ry > 0 && ry < rowsVisible) {
+ ctx.fillStyle = "rgba(0,0,0,1)";
+ ctx.fillRect(col.x, ry * fontSize, fontSize, fontSize);
+ ctx.fillStyle = "rgba(" + r + "," + g + "," + b + ",0.6)";
+ ctx.fillText(randGlyph(), col.x, ry * fontSize);
+ }
+ }
+ }
+ }
+
+ window.addEventListener("resize", resize);
+ resize();
+ setThemeButton();
+ requestAnimationFrame(draw);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e3c185541c0d/claude-code.png b/data/artifacts/e3c185541c0d/claude-code.png
new file mode 100644
index 0000000..a6ecfb8
Binary files /dev/null and b/data/artifacts/e3c185541c0d/claude-code.png differ
diff --git a/data/artifacts/e3c185541c0d/gemma3-12b.html b/data/artifacts/e3c185541c0d/gemma3-12b.html
new file mode 100644
index 0000000..5b5195e
--- /dev/null
+++ b/data/artifacts/e3c185541c0d/gemma3-12b.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Matrix Rain</title>
+ <style>
+ body {
+ margin: 0;
+ overflow: hidden;
+ background-color: black;
+ }
+
+ #rainCanvas {
+ display: block;
+ }
+
+ #colorToggle {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ padding: 5px 10px;
+ background-color: rgba(255, 255, 255, 0.3);
+ border: 1px solid white;
+ cursor: pointer;
+ }
+ </style>
+</head>
+<body>
+
+ <div id="colorToggle">Color Toggle (Green/Amber/Cyan)</div>
+ <canvas id="rainCanvas"></canvas>
+
+
+ <script>
+ const canvas = document.getElementById('rainCanvas');
+ const ctx = canvas.getContext('2d');
+ let colorTheme = 'green'; // green, amber, cyan
+
+ const toggleColorTheme = () => {
+ if (colorTheme === 'green') {
+ colorTheme = 'amber';
+ } else if (colorTheme === 'amber') {
+ colorTheme = 'cyan';
+ } else {
+ colorTheme = 'green';
+ }
+
+ render(); // Re-render with the new color theme.
+ };
+
+
+ const colors = {
+ 'green': '#0F0',
+ 'amber': '#FFC',
+ 'cyan': '#0FF'
+ };
+
+ let drops = [];
+ const numDrops = 100; // Adjust for performance/density
+ const dropLength = 20;
+
+
+ function init() {
+ for (let i = 0; i < numDrops; i++) {
+ drops.push({
+ x: Math.random() * canvas.width,
+ y: Math.random() * canvas.height,
+ length: Math.floor(Math.random() * dropLength) + 1, // Random length between 1 and dropLength
+ speed: Math.random() * 5 + 2,
+ color: colors[colorTheme]
+ });
+ }
+ }
+
+ function render() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
+
+
+ for (let i = 0; i < drops.length; i++) {
+ const drop = drops[i];
+
+ // Head glow effect
+ ctx.beginPath();
+ ctx.arc(drop.x, drop.y - 5, 3, 0, Math.PI * 2); // Small circle for head glow
+ ctx.fillStyle = 'rgba(0, 255, 0, 0.4)'; // Greenish glow
+ if (colorTheme === 'amber') { ctx.fillStyle = 'rgba(255, 215, 0, 0.4)'; } // Amber Glow
+ if (colorTheme === 'cyan') { ctx.fillStyle = 'rgba(0, 255, 255, 0.4)'; } // Cyan Glow
+
+ ctx.fill();
+
+
+ // Draw the drop itself
+ ctx.beginPath();
+ ctx.moveTo(drop.x, drop.y);
+ for (let j = 1; j <= drop.length; j++) {
+ ctx.lineTo(drop.x + Math.sin((j / drop.length) * Math.PI * 2) * 5, drop.y + j * drop.speed);
+ }
+ ctx.strokeStyle = drop.color;
+ ctx.lineWidth = 1;
+ ctx.stroke();
+
+ }
+ }
+
+
+
+ let animationFrameId;
+
+
+ function animate() {
+ render();
+ animationFrameId = requestAnimationFrame(animate);
+ }
+
+ // Resize handler
+ window.addEventListener('resize', () => {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ drops = []; // Clear existing drops and re-initialize them
+ init();
+ });
+
+
+ document.getElementById('colorToggle').addEventListener('click', toggleColorTheme);
+
+ // Initial setup: set canvas dimensions and start animation
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ init();
+ animate();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e3c185541c0d/gemma3-12b.png b/data/artifacts/e3c185541c0d/gemma3-12b.png
new file mode 100644
index 0000000..bdbc83f
Binary files /dev/null and b/data/artifacts/e3c185541c0d/gemma3-12b.png differ
diff --git a/data/artifacts/e3c185541c0d/gpt.html b/data/artifacts/e3c185541c0d/gpt.html
new file mode 100644
index 0000000..8d234c7
--- /dev/null
+++ b/data/artifacts/e3c185541c0d/gpt.html
@@ -0,0 +1,184 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>Matrix Digital Rain</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ background: #000;
+ overflow: hidden;
+ height: 100%;
+ width: 100%;
+ font-family: system-ui, sans-serif;
+ }
+ #matrixCanvas {
+ display: block;
+ width: 100vw;
+ height: 100vh;
+ }
+ .toolbar {
+ position: fixed;
+ top: 10px;
+ right: 10px;
+ z-index: 10;
+ display: flex;
+ gap: 8px;
+ background: rgba(0,0,0,0.65);
+ border: 1px solid rgba(255,255,255,0.1);
+ border-radius: 999px;
+ padding: 4px 8px;
+ align-items: center;
+ backdrop-filter: blur(6px);
+ }
+ .toolbar-label {
+ color: #ccc;
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ }
+ .theme-btn {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ border: 1px solid rgba(255,255,255,0.4);
+ cursor: pointer;
+ box-sizing: border-box;
+ outline: none;
+ padding: 0;
+ margin: 0;
+ }
+ .theme-btn[data-theme="green"] {
+ background: radial-gradient(circle at 30% 30%, #b4ffb4 0, #00ff00 30%, #006600 100%);
+ }
+ .theme-btn[data-theme="amber"] {
+ background: radial-gradient(circle at 30% 30%, #ffe6a1 0, #ffbf00 30%, #cc7a00 100%);
+ }
+ .theme-btn[data-theme="cyan"] {
+ background: radial-gradient(circle at 30% 30%, #d1ffff 0, #00f0ff 30%, #006a80 100%);
+ }
+ .theme-btn.active {
+ box-shadow: 0 0 0 1px #fff, 0 0 10px rgba(255,255,255,0.9);
+ }
+</style>
+</head>
+<body>
+<canvas id="matrixCanvas"></canvas>
+<div class="toolbar">
+ <span class="toolbar-label">Theme</span>
+ <button class="theme-btn" data-theme="green" aria-label="Green theme"></button>
+ <button class="theme-btn" data-theme="amber" aria-label="Amber theme"></button>
+ <button class="theme-btn" data-theme="cyan" aria-label="Cyan theme"></button>
+</div>
+<script>
+(function() {
+ const canvas = document.getElementById('matrixCanvas');
+ const ctx = canvas.getContext('2d');
+
+ // Glyph set: Katakana + digits + misc symbols
+ const glyphs = 'アァカサタナハマヤャラワガザダバパイィキシチニヒミリヰギジヂビピウゥクスツヌフムユュルグズブプエェケセテネヘメレヱゲゼデベペオォコソトノホモヨョロヲゴゾドボポヴン0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$%&*+-<>?/';
+
+ let width, height, columns, columnWidth, columnHeights;
+ let fontSize;
+ let theme = 'green';
+
+ const themes = {
+ green: {
+ trail: 'rgba(0, 0, 0, 0.18)',
+ head: '#d4ffd4',
+ body: '#00ff00',
+ tail: '#006600'
+ },
+ amber: {
+ trail: 'rgba(0, 0, 0, 0.18)',
+ head: '#fff1c2',
+ body: '#ffbf00',
+ tail: '#cc7a00'
+ },
+ cyan: {
+ trail: 'rgba(0, 0, 0, 0.18)',
+ head: '#e3ffff',
+ body: '#00f0ff',
+ tail: '#006a80'
+ }
+ };
+
+ function resize() {
+ const dpr = window.devicePixelRatio || 1;
+ width = window.innerWidth;
+ height = window.innerHeight;
+
+ canvas.width = width * dpr;
+ canvas.height = height * dpr;
+ canvas.style.width = width + 'px';
+ canvas.style.height = height + 'px';
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+
+ fontSize = Math.max(14, Math.floor(width / 80));
+ columnWidth = fontSize;
+ columns = Math.floor(width / columnWidth);
+ columnHeights = [];
+
+ for (let i = 0; i < columns; i++) {
+ columnHeights[i] = Math.random() * -20;
+ }
+
+ ctx.font = fontSize + 'px monospace';
+ }
+
+ function draw() {
+ const t = themes[theme];
+ ctx.fillStyle = t.trail;
+ ctx.fillRect(0, 0, width, height);
+
+ for (let i = 0; i < columns; i++) {
+ const x = i * columnWidth;
+ const y = columnHeights[i] * fontSize;
+
+ const char = glyphs.charAt(Math.floor(Math.random() * glyphs.length));
+
+ const gradient = ctx.createLinearGradient(x, y - fontSize * 1.5, x, y + fontSize * 1.5);
+ gradient.addColorStop(0, 'rgba(0,0,0,0)');
+ gradient.addColorStop(0.2, t.tail);
+ gradient.addColorStop(0.6, t.body);
+ gradient.addColorStop(1, t.head);
+
+ ctx.fillStyle = gradient;
+ ctx.fillText(char, x, y);
+
+ if (y > height + Math.random() * 200) {
+ columnHeights[i] = Math.random() * -20;
+ } else {
+ columnHeights[i] += 1;
+ }
+ }
+
+ requestAnimationFrame(draw);
+ }
+
+ function setTheme(name) {
+ if (!themes[name]) return;
+ theme = name;
+ const buttons = document.querySelectorAll('.theme-btn');
+ buttons.forEach(btn => {
+ btn.classList.toggle('active', btn.getAttribute('data-theme') === name);
+ });
+ }
+
+ // Event listeners
+ window.addEventListener('resize', resize);
+ document.querySelectorAll('.theme-btn').forEach(btn => {
+ btn.addEventListener('click', () => {
+ setTheme(btn.getAttribute('data-theme'));
+ });
+ });
+
+ // Init
+ resize();
+ setTheme('green');
+ requestAnimationFrame(draw);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e3c185541c0d/gpt.png b/data/artifacts/e3c185541c0d/gpt.png
new file mode 100644
index 0000000..b0e2874
Binary files /dev/null and b/data/artifacts/e3c185541c0d/gpt.png differ
diff --git a/data/artifacts/e3c185541c0d/grok.html b/data/artifacts/e3c185541c0d/grok.html
new file mode 100644
index 0000000..4408b0a
--- /dev/null
+++ b/data/artifacts/e3c185541c0d/grok.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Matrix Rain</title>
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+html,body{width:100%;height:100%;overflow:hidden;background:#000}
+canvas{display:block;width:100%;height:100%}
+#toggle{
+ position:fixed;top:12px;right:12px;z-index:10;
+ background:rgba(0,0,0,0.65);border:1px solid currentColor;
+ color:#0f0;font:12px/1 monospace;padding:8px 12px;
+ cursor:pointer;border-radius:4px;letter-spacing:0.05em;
+ text-transform:uppercase;opacity:0.85;transition:opacity .2s,color .3s,border-color .3s
+}
+#toggle:hover{opacity:1}
+</style>
+</head>
+<body>
+<button id="toggle" type="button">theme: green</button>
+<canvas id="c"></canvas>
+<script>
+(function(){
+ const canvas=document.getElementById('c');
+ const ctx=canvas.getContext('2d');
+ const toggle=document.getElementById('toggle');
+
+ const themes={
+ green:{head:'#dfffd8',trail:'#00ff41',fade:'rgba(0,0,0,0.08)',btn:'#0f0'},
+ amber:{head:'#fff3d0',trail:'#ffb000',fade:'rgba(0,0,0,0.08)',btn:'#ffb000'},
+ cyan:{head:'#d8ffff',trail:'#00e5ff',fade:'rgba(0,0,0,0.08)',btn:'#0ef'}
+ };
+ const themeNames=['green','amber','cyan'];
+ let themeIdx=0;
+ let theme=themes.green;
+
+ const glyphs='アイウエ��カキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワンヲァィゥェォッャュョー゙゚0123456789ABCDEF<>*+:=/\\|';
+ const fontSize=16;
+ let columns=0;
+ let drops=[];
+ let speeds=[];
+ let w=0,h=0;
+
+ function resize(){
+ w=window.innerWidth;
+ h=window.innerHeight;
+ canvas.width=w;
+ canvas.height=h;
+ const newCols=Math.ceil(w/fontSize);
+ const oldDrops=drops;
+ const oldSpeeds=speeds;
+ drops=new Array(newCols);
+ speeds=new Array(newCols);
+ for(let i=0;i<newCols;i++){
+ drops[i]=i<oldDrops.length?oldDrops[i]:Math.random()*-50;
+ speeds[i]=i<oldSpeeds.length?oldSpeeds[i]:0.5+Math.random()*0.9;
+ }
+ columns=newCols;
+ ctx.font=fontSize+'px monospace';
+ }
+
+ function randGlyph(){
+ return glyphs[(Math.random()*glyphs.length)|0];
+ }
+
+ function draw(){
+ ctx.fillStyle=theme.fade;
+ ctx.fillRect(0,0,w,h);
+
+ for(let i=0;i<columns;i++){
+ const x=i*fontSize;
+ const y=drops[i]*fontSize;
+
+ ctx.fillStyle=theme.head;
+ ctx.shadowColor=theme.head;
+ ctx.shadowBlur=10;
+ const head=randGlyph();
+ ctx.fillText(head,x,y);
+ ctx.shadowBlur=0;
+
+ ctx.fillStyle=theme.trail;
+ for(let t=1;t<18;t++){
+ const ty=y-t*fontSize;
+ if(ty<0)break;
+ const alpha=1-t/18;
+ ctx.globalAlpha=alpha*0.85;
+ ctx.fillText(randGlyph(),x,ty);
+ }
+ ctx.globalAlpha=1;
+
+ if(y>h&&Math.random()>0.975){
+ drops[i]=Math.random()*-30;
+ speeds[i]=0.5+Math.random()*0.9;
+ }
+ drops[i]+=speeds[i];
+ }
+ requestAnimationFrame(draw);
+ }
+
+ toggle.addEventListener('click',function(){
+ themeIdx=(themeIdx+1)%themeNames.length;
+ const name=themeNames[themeIdx];
+ theme=themes[name];
+ toggle.textContent='theme: '+name;
+ toggle.style.color=theme.btn;
+ toggle.style.borderColor=theme.btn;
+ });
+ toggle.style.color=theme.btn;
+ toggle.style.borderColor=theme.btn;
+
+ window.addEventListener('resize',resize);
+ resize();
+ ctx.fillStyle='#000';
+ ctx.fillRect(0,0,w,h);
+ requestAnimationFrame(draw);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e3c185541c0d/grok.png b/data/artifacts/e3c185541c0d/grok.png
new file mode 100644
index 0000000..76e357e
Binary files /dev/null and b/data/artifacts/e3c185541c0d/grok.png differ
diff --git a/data/artifacts/e3c185541c0d/hermes3-8b.html b/data/artifacts/e3c185541c0d/hermes3-8b.html
new file mode 100644
index 0000000..807c256
--- /dev/null
+++ b/data/artifacts/e3c185541c0d/hermes3-8b.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Matrix Digital Rain</title>
+ <style>
+ body {
+ margin: 0;
+ overflow: hidden;
+ background-color: black;
+ font-family: "Arial Unicode MS", Arial, sans-serif;
+ }
+
+ canvas {
+ display: block;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="matrix"></canvas>
+
+ <script>
+ const canvas = document.getElementById('matrix');
+ let ctx;
+
+ window.addEventListener('resize', resizeCanvas);
+
+ function resizeCanvas() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ init();
+ }
+
+ function init() {
+ if (!canvas) return;
+
+ ctx = canvas.getContext('2d');
+
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
+ resizeCanvas();
+
+ document.body.className = '';
+
+ let colors = ['green', 'amber', 'cyan'];
+ setInterval(() => {
+ document.body.classList.toggle(colors[Math.floor(Math.random() * 3)]);
+ }, 1000);
+ }
+
+ init();
+
+ function animate() {
+ ctx.fillStyle = "rgba(0, 0, 0, 0.02)";
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ let glows = [];
+ let glyphs = 'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめも'.split('');
+
+ for (let i = 0; i < 100; i++) {
+ let x = (canvas.width / 4 * Math.random()) | 0;
+ let y = (canvas.height / 2 - 10) | 0;
+ let glowX = (canvas.width / 2) | 0;
+ let color = 'rgba(0, 200, 0, 1)';
+ if (document.body.classList.contains('amber')) {
+ color = 'rgba(255, 200, 0, 1)';
+ } else if (document.body.classList.contains('cyan')) {
+ color = 'rgba(0, 200, 255, 1)';
+ }
+
+ glows.push({x: glowX, y, color});
+ if (glyphs.length > i) {
+ let g = glyphs[i];
+ ctx.fillStyle = color;
+ ctx.fillText(g, x, y);
+ }
+ }
+
+ for (let i = 0; i < glows.length; i++) {
+ let g = glows[i];
+ ctx.fillStyle = 'rgba(255, 255, 255, 1)';
+ ctx.fillRect(g.x - 1, g.y - 2, 3, 5);
+ ctx.fillStyle = g.color;
+ ctx.fillRect(g.x - 4, g.y - 6, 14, 13);
+
+ if (g.y > canvas.height + 50) {
+ glows.splice(i, 1);
+ i--;
+ }
+ }
+
+ requestAnimationFrame(animate);
+ }
+
+ animate();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e3c185541c0d/hermes3-8b.png b/data/artifacts/e3c185541c0d/hermes3-8b.png
new file mode 100644
index 0000000..ad57eb4
Binary files /dev/null and b/data/artifacts/e3c185541c0d/hermes3-8b.png differ
diff --git a/data/artifacts/e3c185541c0d/hf-qwen-coder-32b.html b/data/artifacts/e3c185541c0d/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..03ba9ec
--- /dev/null
+++ b/data/artifacts/e3c185541c0d/hf-qwen-coder-32b.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Matrix Digital Rain</title>
+<style>
+body { margin: 0; overflow: hidden; background-color: black; display: flex; justify-content: center; align-items: center; }
+canvas { display: block; }
+button { position: absolute; top: 20px; right: 20px; z-index: 100; background-color: rgba(0,0,0,0.5); color: white; border: none; padding: 10px; cursor: pointer; }
+</style>
+</head>
+<body>
+<button onclick="toggleTheme()">Toggle Theme</button>
+<canvas id="matrixCanvas"></canvas>
+<script>
+const canvas = document.getElementById('matrixCanvas');
+const ctx = canvas.getContext('2d');
+let columns;
+let font_size = 10;
+let themes = ['green', 'amber', 'cyan'];
+let currentThemeIndex = 0;
+
+function setCanvasSize() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ columns = Math.floor(canvas.width / font_size);
+}
+
+window.addEventListener('resize', setCanvasSize);
+
+const katakana = '゠アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンヺ・ーヽヾヿ';
+const glyphs = katakana.split('');
+let drops = [];
+
+function toggleTheme() {
+ currentThemeIndex = (currentThemeIndex + 1) % themes.length;
+}
+
+function initializeDrops() {
+ for (let x = 0; x < columns; x++) {
+ drops[x] = 1;
+ }
+}
+
+function draw() {
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ let color = themes[currentThemeIndex];
+ ctx.fillStyle = `${color}`;
+ ctx.font = font_size + 'px arial';
+
+ for (let x = 0; x < drops.length; x++) {
+ const text = glyphs[Math.floor(Math.random() * glyphs.length)];
+ ctx.fillText(text, x * font_size, drops[x] * font_size);
+
+ if (drops[x] * font_size > canvas.height && Math.random() > 0.975) {
+ drops[x] = 0;
+ }
+ drops[x]++;
+ }
+}
+
+function mainLoop() {
+ draw();
+ requestAnimationFrame(mainLoop);
+}
+
+setCanvasSize();
+initializeDrops();
+mainLoop();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e3c185541c0d/hf-qwen-coder-32b.png b/data/artifacts/e3c185541c0d/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..21db791
Binary files /dev/null and b/data/artifacts/e3c185541c0d/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/e3c185541c0d/kimi.html b/data/artifacts/e3c185541c0d/kimi.html
new file mode 100644
index 0000000..44d4ff5
--- /dev/null
+++ b/data/artifacts/e3c185541c0d/kimi.html
@@ -0,0 +1,184 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Matrix Digital Rain</title>
+ <style>
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+
+ body {
+ background: black;
+ overflow: hidden;
+ font-family: monospace;
+ }
+
+ canvas {
+ display: block;
+ position: fixed;
+ top: 0;
+ left: 0;
+ }
+
+ #controls {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ z-index: 100;
+ }
+
+ button {
+ background: rgba(0, 0, 0, 0.8);
+ border: 2px solid currentColor;
+ color: #0F0;
+ padding: 10px 20px;
+ font-family: monospace;
+ font-size: 14px;
+ cursor: pointer;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ transition: all 0.3s;
+ box-shadow: 0 0 10px currentColor;
+ }
+
+ button:hover {
+ background: currentColor;
+ color: black;
+ box-shadow: 0 0 20px currentColor;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="matrix"></canvas>
+ <div id="controls">
+ <button id="themeBtn" onclick="cycleTheme()">Theme: Green</button>
+ </div>
+
+ <script>
+ const canvas = document.getElementById('matrix');
+ const ctx = canvas.getContext('2d');
+ const themeBtn = document.getElementById('themeBtn');
+
+ const fontSize = 14;
+ let columns = 0;
+ let drops = [];
+
+ const themes = {
+ green: {
+ name: 'Green',
+ primary: '#0F0',
+ glow: '#FFF',
+ button: '#0F0'
+ },
+ amber: {
+ name: 'Amber',
+ primary: '#FFB000',
+ glow: '#FFE0A0',
+ button: '#FFB000'
+ },
+ cyan: {
+ name: 'Cyan',
+ primary: '#0FF',
+ glow: '#E0FFFF',
+ button: '#0FF'
+ }
+ };
+
+ let currentTheme = 'green';
+
+ const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ function resize() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ columns = Math.floor(canvas.width / fontSize);
+
+ drops = [];
+ for (let i = 0; i < columns; i++) {
+ drops[i] = {
+ y: Math.random() * -100,
+ speed: Math.random() * 0.5 + 0.5,
+ length: Math.floor(Math.random() * 15 + 5)
+ };
+ }
+ }
+
+ function getChar() {
+ return chars[Math.floor(Math.random() * chars.length)];
+ }
+
+ function draw() {
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ ctx.font = fontSize + 'px monospace';
+
+ const theme = themes[currentTheme];
+
+ for (let i = 0; i < drops.length; i++) {
+ const drop = drops[i];
+ const x = i * fontSize;
+
+ for (let j = 0; j < drop.length; j++) {
+ const y = drop.y - j * fontSize;
+ if (y < 0 || y > canvas.height) continue;
+
+ if (j === 0) {
+ ctx.fillStyle = theme.glow;
+ ctx.shadowBlur = 15;
+ ctx.shadowColor = theme.primary;
+ } else {
+ ctx.fillStyle = theme.primary;
+ ctx.globalAlpha = (1 - (j / drop.length)) * 0.8;
+ ctx.shadowBlur = 0;
+ }
+
+ ctx.fillText(getChar(), x, y);
+ ctx.globalAlpha = 1;
+ ctx.shadowBlur = 0;
+ }
+
+ drop.y += drop.speed * fontSize;
+
+ if (drop.y - (drop.length * fontSize) > canvas.height) {
+ if (Math.random() > 0.975) {
+ drop.y = -fontSize;
+ drop.speed = Math.random() * 0.5 + 0.5;
+ drop.length = Math.floor(Math.random() * 15 + 5);
+ }
+ }
+ }
+ }
+
+ function animate() {
+ draw();
+ requestAnimationFrame(animate);
+ }
+
+ function cycleTheme() {
+ const themeKeys = Object.keys(themes);
+ const currentIndex = themeKeys.indexOf(currentTheme);
+ const nextIndex = (currentIndex + 1) % themeKeys.length;
+ currentTheme = themeKeys[nextIndex];
+
+ const theme = themes[currentTheme];
+ themeBtn.textContent = `Theme: ${theme.name}`;
+ themeBtn.style.color = theme.button;
+ themeBtn.style.borderColor = theme.button;
+ themeBtn.style.boxShadow = `0 0 10px ${theme.button}`;
+ }
+
+ window.addEventListener('resize', resize);
+ window.addEventListener('keydown', (e) => {
+ if (e.key === 't' || e.key === 'T') cycleTheme();
+ });
+
+ resize();
+ animate();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e3c185541c0d/kimi.png b/data/artifacts/e3c185541c0d/kimi.png
new file mode 100644
index 0000000..cf263a8
Binary files /dev/null and b/data/artifacts/e3c185541c0d/kimi.png differ
diff --git a/data/artifacts/e3c185541c0d/qwen25-7b.html b/data/artifacts/e3c185541c0d/qwen25-7b.html
new file mode 100644
index 0000000..236c48d
--- /dev/null
+++ b/data/artifacts/e3c185541c0d/qwen25-7b.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Matrix Digital Rain</title>
+ <style>
+ body {
+ margin: 0;
+ overflow: hidden;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ background-color: #0f253a;
+ }
+ canvas {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: -1;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="matrix" width="100%" height="100%"></canvas>
+ <script>
+ const canvas = document.getElementById('matrix');
+ const ctx = canvas.getContext('2d');
+
+ function randomKatakana() {
+ return String.fromCharCode(12353 + Math.floor(Math.random() * 96));
+ }
+
+ let glyphs = [];
+ window.addEventListener('resize', resizeCanvas);
+
+ function resizeCanvas() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ glyphs.length = 0;
+
+ for (let x = -canvas.width; x < window.innerWidth; x += 32) {
+ glyphs.push({ x, y: Math.random() * canvas.height, dirX: Math.random() * 2 - 1, dirY: -Math.random(), speed: Math.random() + 0.5 });
+ }
+ }
+
+ resizeCanvas();
+
+ function drawKatakana(glyph) {
+ ctx.fillStyle = 'green';
+ ctx.fillText(randomKatakana(), glyph.x, glyph.y);
+ }
+
+ function animate() {
+ ctx.fillStyle = 'rgba(240, 189, 75, 0.3)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ for (const glyph of glyphs) {
+ drawKatakana(glyph);
+ glyph.y += glyph.speed;
+ if (glyph.y > canvas.height || Math.random() < 0.1) {
+ glyph.x = -32 + Math.random() * window.innerWidth;
+ glyph.y = 0 - 32;
+ }
+ }
+
+ requestAnimationFrame(animate);
+ }
+
+ function toggleColorTheme(event) {
+ const body = document.body;
+ if (body.classList.contains('green-theme')) {
+ body.classList.remove('green-theme');
+ body.classList.add('amber-cyan-theme');
+ } else {
+ body.classList.remove('amber-cyan-theme');
+ body.classList.add('green-theme');
+ }
+ }
+
+ const themeSwitcher = document.createElement('div');
+ themeSwitcher.style.position = 'absolute';
+ themeSwitcher.style.top = '10px';
+ themeSwitcher.style.left = '10px';
+ themeSwitcher.innerHTML = '<span onclick="toggleColorTheme(event)">Toggle Theme</span>';
+ body.appendChild(themeSwitcher);
+
+ animate();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e3c185541c0d/qwen25-7b.png b/data/artifacts/e3c185541c0d/qwen25-7b.png
new file mode 100644
index 0000000..ef42d34
Binary files /dev/null and b/data/artifacts/e3c185541c0d/qwen25-7b.png differ
diff --git a/data/artifacts/e3c185541c0d/qwen3-14b.html b/data/artifacts/e3c185541c0d/qwen3-14b.html
new file mode 100644
index 0000000..c2da902
--- /dev/null
+++ b/data/artifacts/e3c185541c0d/qwen3-14b.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>Matrix Rain</title>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ background-color: #000;
+ height: 100%;
+ width: 100%;
+ font-family: monospace;
+ }
+ canvas {
+ display: block;
+ }
+ #themeToggle {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 10;
+ padding: 8px 16px;
+ font-size: 16px;
+ background: rgba(0,0,0,0.5);
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ </style>
+</head>
+<body>
+ <button id="themeToggle">Toggle Theme</button>
+ <canvas id="matrixCanvas"></canvas>
+ <script>
+ const canvas = document.getElementById("matrixCanvas");
+ const ctx = canvas.getContext("2d");
+ const toggleButton = document.getElementById("themeToggle");
+
+ let theme = "green";
+ const themes = {
+ green: { color: "#0f0", glow: "#0f0", trail: "rgba(0,255,0,0.1)" },
+ amber: { color: "#ff0", glow: "#ff0", trail: "rgba(255,255,0,0.1)" },
+ cyan: { color: "#0ff", glow: "#0ff", trail: "rgba(0,255,255,0.1)" }
+ };
+
+ function resizeCanvas() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ }
+
+ window.addEventListener("resize", resizeCanvas);
+ resizeCanvas();
+
+ const glyphs = "ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン";
+ const glyphCount = glyphs.length;
+ const columns = Math.floor(canvas.width / 20);
+ const drops = Array(columns).fill(1);
+
+ function drawMatrix() {
+ ctx.fillStyle = themes[theme].trail;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ ctx.fillStyle = themes[theme].glow;
+ ctx.font = "16px monospace";
+ ctx.textAlign = "left";
+ ctx.textBaseline = "top";
+
+ for (let i = 0; i < drops.length; i++) {
+ const text = glyphs.charAt(Math.floor(Math.random() * glyphCount));
+ const x = i * 20;
+ const y = drops[i] * 16;
+
+ ctx.fillText(text, x, y);
+
+ if (y > canvas.height && Math.random() > 0.975) {
+ drops[i] = 0;
+ }
+
+ drops[i]++;
+ }
+ }
+
+ function animate() {
+ drawMatrix();
+ requestAnimationFrame(animate);
+ }
+
+ toggleButton.addEventListener("click", () => {
+ theme = themes[theme] === themes.green ? "amber" : theme === "amber" ? "cyan" : "green";
+ });
+
+ animate();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/e3c185541c0d/qwen3-14b.png b/data/artifacts/e3c185541c0d/qwen3-14b.png
new file mode 100644
index 0000000..0e3ecba
Binary files /dev/null and b/data/artifacts/e3c185541c0d/qwen3-14b.png differ
diff --git a/data/artifacts/f7fa0a2c495b/claude-code.html b/data/artifacts/f7fa0a2c495b/claude-code.html
new file mode 100644
index 0000000..02a7b65
--- /dev/null
+++ b/data/artifacts/f7fa0a2c495b/claude-code.html
@@ -0,0 +1,303 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Kaleidoscope</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ overflow: hidden;
+ background: #0a0a12;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ user-select: none;
+ -webkit-user-select: none;
+ }
+ #stage {
+ position: fixed;
+ inset: 0;
+ cursor: crosshair;
+ display: block;
+ touch-action: none;
+ }
+ #ui {
+ position: fixed;
+ top: 12px;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ background: rgba(15, 15, 30, 0.72);
+ border: 1px solid rgba(255, 255, 255, 0.14);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border-radius: 999px;
+ padding: 8px 18px;
+ color: #e8e8f2;
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.45);
+ z-index: 10;
+ flex-wrap: wrap;
+ justify-content: center;
+ max-width: calc(100vw - 32px);
+ }
+ #ui label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 13px;
+ letter-spacing: 0.02em;
+ white-space: nowrap;
+ }
+ #segVal {
+ display: inline-block;
+ min-width: 2ch;
+ text-align: center;
+ font-weight: 600;
+ font-variant-numeric: tabular-nums;
+ color: #fff;
+ }
+ input[type="range"] {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 130px;
+ height: 4px;
+ border-radius: 2px;
+ background: linear-gradient(90deg, #ff5f6d, #ffc371, #47e891, #4facfe, #b06efc);
+ outline: none;
+ cursor: pointer;
+ }
+ input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: #fff;
+ border: 2px solid rgba(0, 0, 0, 0.35);
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
+ cursor: pointer;
+ }
+ input[type="range"]::-moz-range-thumb {
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: #fff;
+ border: 2px solid rgba(0, 0, 0, 0.35);
+ cursor: pointer;
+ }
+ button {
+ font: inherit;
+ font-size: 13px;
+ font-weight: 600;
+ letter-spacing: 0.03em;
+ color: #e8e8f2;
+ background: rgba(255, 255, 255, 0.09);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 999px;
+ padding: 6px 16px;
+ cursor: pointer;
+ transition: background 0.15s, transform 0.1s;
+ }
+ button:hover { background: rgba(255, 255, 255, 0.2); }
+ button:active { transform: scale(0.95); }
+ #hueDot {
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ border: 1.5px solid rgba(255, 255, 255, 0.7);
+ box-shadow: 0 0 8px currentColor;
+ flex: 0 0 auto;
+ }
+ #hint {
+ position: fixed;
+ bottom: 18px;
+ left: 50%;
+ transform: translateX(-50%);
+ color: rgba(255, 255, 255, 0.45);
+ font-size: 13px;
+ letter-spacing: 0.05em;
+ pointer-events: none;
+ transition: opacity 1s;
+ z-index: 5;
+ }
+</style>
+</head>
+<body>
+<canvas id="stage"></canvas>
+<div id="ui">
+ <span id="hueDot"></span>
+ <label>Mirrors <input type="range" id="segments" min="2" max="24" step="1" value="8"><span id="segVal">8</span></label>
+ <label>Size <input type="range" id="brush" min="1" max="24" step="1" value="4"></label>
+ <button id="clearBtn" type="button">Clear</button>
+ <button id="saveBtn" type="button">Save PNG</button>
+</div>
+<div id="hint">draw with your mouse — strokes mirror around the center</div>
+<script>
+(function () {
+ "use strict";
+
+ var canvas = document.getElementById("stage");
+ var ctx = canvas.getContext("2d");
+ var segSlider = document.getElementById("segments");
+ var segVal = document.getElementById("segVal");
+ var brushSlider = document.getElementById("brush");
+ var clearBtn = document.getElementById("clearBtn");
+ var saveBtn = document.getElementById("saveBtn");
+ var hueDot = document.getElementById("hueDot");
+ var hint = document.getElementById("hint");
+
+ var dpr = Math.max(1, window.devicePixelRatio || 1);
+ var W = 0, H = 0, CX = 0, CY = 0;
+ var hue = 0;
+ var drawing = false;
+ var hasDrawn = false;
+ var prev = null; // previous point in center-relative coords
+
+ var BG = "#0a0a12";
+
+ function paintBackground() {
+ ctx.save();
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ ctx.fillStyle = BG;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.restore();
+ }
+
+ function resize() {
+ // Preserve existing art across resizes
+ var snapshot = null;
+ if (canvas.width > 0 && canvas.height > 0 && hasDrawn) {
+ snapshot = document.createElement("canvas");
+ snapshot.width = canvas.width;
+ snapshot.height = canvas.height;
+ snapshot.getContext("2d").drawImage(canvas, 0, 0);
+ }
+ W = window.innerWidth;
+ H = window.innerHeight;
+ CX = W / 2;
+ CY = H / 2;
+ canvas.width = Math.round(W * dpr);
+ canvas.height = Math.round(H * dpr);
+ canvas.style.width = W + "px";
+ canvas.style.height = H + "px";
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ ctx.lineCap = "round";
+ ctx.lineJoin = "round";
+ paintBackground();
+ if (snapshot) {
+ ctx.save();
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ ctx.drawImage(snapshot, 0, 0, canvas.width, canvas.height);
+ ctx.restore();
+ }
+ }
+
+ function getPoint(e) {
+ var x = (e.touches ? e.touches[0].clientX : e.clientX) - CX;
+ var y = (e.touches ? e.touches[0].clientY : e.clientY) - CY;
+ return { x: x, y: y };
+ }
+
+ function strokeSegment(a, b) {
+ var n = parseInt(segSlider.value, 10);
+ var size = parseInt(brushSlider.value, 10);
+ var color = "hsl(" + (hue % 360) + ", 90%, 62%)";
+ var glow = "hsla(" + (hue % 360) + ", 90%, 62%, 0.35)";
+ var step = (Math.PI * 2) / n;
+
+ ctx.lineWidth = size;
+ ctx.strokeStyle = color;
+ ctx.shadowColor = glow;
+ ctx.shadowBlur = size * 1.5;
+
+ for (var i = 0; i < n; i++) {
+ ctx.save();
+ ctx.translate(CX, CY);
+ ctx.rotate(step * i);
+ // direct copy
+ ctx.beginPath();
+ ctx.moveTo(a.x, a.y);
+ ctx.lineTo(b.x, b.y);
+ ctx.stroke();
+ // mirrored copy within the wedge (true kaleidoscope reflection)
+ ctx.scale(1, -1);
+ ctx.beginPath();
+ ctx.moveTo(a.x, a.y);
+ ctx.lineTo(b.x, b.y);
+ ctx.stroke();
+ ctx.restore();
+ }
+ ctx.shadowBlur = 0;
+ }
+
+ function start(e) {
+ if (e.target !== canvas) return;
+ drawing = true;
+ prev = getPoint(e);
+ // dot on click
+ strokeSegment(prev, { x: prev.x + 0.01, y: prev.y + 0.01 });
+ hasDrawn = true;
+ hint.style.opacity = "0";
+ e.preventDefault();
+ }
+
+ function move(e) {
+ if (!drawing) return;
+ var p = getPoint(e);
+ strokeSegment(prev, p);
+ prev = p;
+ hasDrawn = true;
+ e.preventDefault();
+ }
+
+ function end() {
+ drawing = false;
+ prev = null;
+ }
+
+ canvas.addEventListener("mousedown", start);
+ window.addEventListener("mousemove", move);
+ window.addEventListener("mouseup", end);
+ canvas.addEventListener("touchstart", start, { passive: false });
+ canvas.addEventListener("touchmove", move, { passive: false });
+ window.addEventListener("touchend", end);
+ window.addEventListener("resize", resize);
+
+ segSlider.addEventListener("input", function () {
+ segVal.textContent = segSlider.value;
+ });
+
+ clearBtn.addEventListener("click", function () {
+ paintBackground();
+ hasDrawn = false;
+ });
+
+ saveBtn.addEventListener("click", function () {
+ var a = document.createElement("a");
+ a.download = "kaleidoscope.png";
+ a.href = canvas.toDataURL("image/png");
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ });
+
+ // color cycling over time
+ var last = performance.now();
+ function tick(now) {
+ var dt = now - last;
+ last = now;
+ hue = (hue + dt * 0.04) % 360; // full cycle every ~9s
+ hueDot.style.background = "hsl(" + hue + ", 90%, 62%)";
+ hueDot.style.color = "hsl(" + hue + ", 90%, 62%)";
+ requestAnimationFrame(tick);
+ }
+
+ resize();
+ requestAnimationFrame(tick);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/f7fa0a2c495b/claude-code.png b/data/artifacts/f7fa0a2c495b/claude-code.png
new file mode 100644
index 0000000..7faf7ed
Binary files /dev/null and b/data/artifacts/f7fa0a2c495b/claude-code.png differ
diff --git a/data/artifacts/f7fa0a2c495b/gemma3-12b.html b/data/artifacts/f7fa0a2c495b/gemma3-12b.html
new file mode 100644
index 0000000..c35a295
--- /dev/null
+++ b/data/artifacts/f7fa0a2c495b/gemma3-12b.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Kaleidoscope Painter</title>
+<style>
+body { margin: 0; overflow: hidden; }
+canvas { display: block; }
+#controls { position: absolute; top: 10px; left: 10px; background-color: rgba(255, 255, 255, 0.8); padding: 5px; border-radius: 5px; }
+button { margin: 2px; cursor: pointer; }
+</style>
+</head>
+<body>
+
+<div id="controls">
+ <label for="segments">Segments:</label>
+ <input type="number" id="segments" value="4" min="2" max="16">
+ <button id="clear">Clear</button>
+ <button id="save">Save</button>
+</div>
+
+<canvas id="kaleidoscopeCanvas"></canvas>
+
+<script>
+const canvas = document.getElementById('kaleidoscopeCanvas');
+const ctx = canvas.getContext('2d');
+const controls = document.getElementById('controls');
+const segmentsInput = document.getElementById('segments');
+const clearButton = document.getElementById('clear');
+const saveButton = document.getElementById('save');
+
+let segments = parseInt(segmentsInput.value);
+let colorCycleIndex = 0;
+let currentColor = [255, 0, 0]; // Red initial color
+
+function updateColor() {
+ colorCycleIndex++;
+ if (colorCycleIndex >= 360) {
+ colorCycleIndex = 0;
+ }
+
+ const hue = colorCycleIndex;
+ currentColor = [Math.round(255 * Math.cos((hue * Math.PI / 180)) / 2 + 127.5),
+ Math.round(255 * Math.sin((hue * Math.PI / 180)) / 2 + 127.5),
+ 127.5];
+}
+
+function drawLineSymmetric(x, y) {
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ for (let i = 0; i < segments; i++) {
+ const angle = (360 / segments) * i;
+ const centerX = canvas.width / 2;
+ const centerY = canvas.height / 2;
+
+ const mirroredX = centerX + Math.cos(angle * Math.PI / 180) * (x - centerX);
+ const mirroredY = centerY + Math.sin(angle * Math.PI / 180) * (y - centerY);
+
+ ctx.lineTo(mirroredX, mirroredY);
+ }
+ ctx.stroke();
+}
+
+
+canvas.addEventListener('mousemove', function(event) {
+ updateColor();
+
+ ctx.strokeStyle = `rgb(${currentColor[0]}, ${currentColor[1]}, ${currentColor[2]})`;
+ ctx.lineWidth = 2;
+ ctx.lineCap = 'round';
+
+ const rect = canvas.getBoundingClientRect();
+ const x = event.clientX - rect.left;
+ const y = event.clientY - rect.top;
+
+ drawLineSymmetric(x, y);
+});
+
+
+clearButton.addEventListener('click', function() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+});
+
+saveButton.addEventListener('click', function() {
+ const image = canvas.toDataURL("image/png");
+ const link = document.createElement('a');
+ link.download = 'kaleidoscope.png';
+ link.href = image;
+ link.click();
+});
+
+segmentsInput.addEventListener('change', function() {
+ segments = parseInt(this.value);
+ canvas.width = canvas.width; // Redraw the canvas to clear it when segments change.
+});
+
+
+window.onload = () => {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
+ window.addEventListener('resize', function() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ });
+};
+</script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/f7fa0a2c495b/gemma3-12b.png b/data/artifacts/f7fa0a2c495b/gemma3-12b.png
new file mode 100644
index 0000000..1a2c7c1
Binary files /dev/null and b/data/artifacts/f7fa0a2c495b/gemma3-12b.png differ
diff --git a/data/artifacts/f7fa0a2c495b/gpt.html b/data/artifacts/f7fa0a2c495b/gpt.html
new file mode 100644
index 0000000..77506d2
--- /dev/null
+++ b/data/artifacts/f7fa0a2c495b/gpt.html
@@ -0,0 +1,226 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>Kaleidoscope Drawing Toy</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ background: #111;
+ color: #eee;
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ height: 100%;
+ overflow: hidden;
+ }
+ #toolbar {
+ position: fixed;
+ top: 8px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: rgba(20,20,20,0.9);
+ padding: 6px 10px;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ z-index: 10;
+ border: 1px solid rgba(255,255,255,0.1);
+ box-shadow: 0 0 10px rgba(0,0,0,0.7);
+ }
+ #toolbar label {
+ font-size: 12px;
+ white-space: nowrap;
+ }
+ #segments {
+ width: 120px;
+ }
+ button {
+ background: #333;
+ color: #eee;
+ border: 1px solid #555;
+ border-radius: 4px;
+ padding: 4px 10px;
+ font-size: 12px;
+ cursor: pointer;
+ }
+ button:hover {
+ background: #444;
+ }
+ button:active {
+ background: #555;
+ }
+ #canvas {
+ display: block;
+ }
+</style>
+</head>
+<body>
+<div id="toolbar">
+ <label>
+ Segments:
+ <input id="segments" type="range" min="2" max="48" step="1" value="12">
+ <span id="segVal">12</span>
+ </label>
+ <button id="clearBtn">Clear</button>
+ <button id="saveBtn">Save PNG</button>
+</div>
+<canvas id="canvas"></canvas>
+<script>
+(function() {
+ const canvas = document.getElementById('canvas');
+ const ctx = canvas.getContext('2d');
+ const segSlider = document.getElementById('segments');
+ const segValSpan = document.getElementById('segVal');
+ const clearBtn = document.getElementById('clearBtn');
+ const saveBtn = document.getElementById('saveBtn');
+
+ let width, height, cx, cy;
+ let isDrawing = false;
+ let lastX = 0, lastY = 0;
+ let hue = 0;
+ let hueSpeed = 0.3;
+ let lineWidth = 2.0;
+ let lastTime = performance.now();
+ let segments = parseInt(segSlider.value, 10);
+
+ function resize() {
+ const dpr = window.devicePixelRatio || 1;
+ width = window.innerWidth;
+ height = window.innerHeight;
+ canvas.style.width = width + 'px';
+ canvas.style.height = height + 'px';
+ canvas.width = width * dpr;
+ canvas.height = height * dpr;
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ cx = width / 2;
+ cy = height / 2;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, width, height);
+ }
+
+ window.addEventListener('resize', resize);
+ resize();
+
+ segSlider.addEventListener('input', () => {
+ segments = parseInt(segSlider.value, 10);
+ segValSpan.textContent = segments;
+ });
+
+ function clearCanvas() {
+ ctx.save();
+ ctx.setTransform(1,0,0,1,0,0);
+ ctx.clearRect(0,0,canvas.width,canvas.height);
+ ctx.restore();
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, width, height);
+ }
+
+ clearBtn.addEventListener('click', clearCanvas);
+
+ function savePNG() {
+ const link = document.createElement('a');
+ link.download = 'kaleidoscope.png';
+ link.href = canvas.toDataURL('image/png');
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ }
+
+ saveBtn.addEventListener('click', savePNG);
+
+ function getPos(evt) {
+ if (evt.touches && evt.touches.length) {
+ return { x: evt.touches[0].clientX, y: evt.touches[0].clientY };
+ }
+ return { x: evt.clientX, y: evt.clientY };
+ }
+
+ function startDraw(evt) {
+ evt.preventDefault();
+ const p = getPos(evt);
+ isDrawing = true;
+ lastX = p.x;
+ lastY = p.y;
+ }
+
+ function stopDraw(evt) {
+ if (!isDrawing) return;
+ evt.preventDefault();
+ isDrawing = false;
+ }
+
+ function draw(evt) {
+ if (!isDrawing) return;
+ evt.preventDefault();
+ const p = getPos(evt);
+ const x = p.x;
+ const y = p.y;
+ if (x === lastX && y === lastY) return;
+
+ const now = performance.now();
+ const dt = now - lastTime;
+ lastTime = now;
+
+ hue = (hue + hueSpeed * dt) % 360;
+ const color = 'hsl(' + hue.toFixed(1) + ', 100%, 60%)';
+
+ const dx1 = lastX - cx;
+ const dy1 = lastY - cy;
+ const dx2 = x - cx;
+ const dy2 = y - cy;
+
+ const baseAngle = (2 * Math.PI) / segments;
+
+ ctx.save();
+ ctx.translate(cx, cy);
+ ctx.strokeStyle = color;
+ ctx.lineWidth = lineWidth;
+
+ for (let i = 0; i < segments; i++) {
+ const angle = baseAngle * i;
+
+ // normal
+ ctx.save();
+ ctx.rotate(angle);
+ ctx.beginPath();
+ ctx.moveTo(dx1, dy1);
+ ctx.lineTo(dx2, dy2);
+ ctx.stroke();
+ ctx.restore();
+
+ // mirrored (flip horizontally)
+ ctx.save();
+ ctx.rotate(angle);
+ ctx.scale(-1, 1);
+ ctx.beginPath();
+ ctx.moveTo(dx1, dy1);
+ ctx.lineTo(dx2, dy2);
+ ctx.stroke();
+ ctx.restore();
+ }
+
+ ctx.restore();
+
+ lastX = x;
+ lastY = y;
+ }
+
+ canvas.addEventListener('mousedown', startDraw);
+ canvas.addEventListener('touchstart', startDraw, { passive: false });
+
+ window.addEventListener('mouseup', stopDraw);
+ window.addEventListener('mouseleave', stopDraw);
+ window.addEventListener('touchend', stopDraw, { passive: false });
+ window.addEventListener('touchcancel', stopDraw, { passive: false });
+
+ canvas.addEventListener('mousemove', draw);
+ canvas.addEventListener('touchmove', draw, { passive: false });
+
+ clearCanvas();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/f7fa0a2c495b/gpt.png b/data/artifacts/f7fa0a2c495b/gpt.png
new file mode 100644
index 0000000..b5caab0
Binary files /dev/null and b/data/artifacts/f7fa0a2c495b/gpt.png differ
diff --git a/data/artifacts/f7fa0a2c495b/grok.html b/data/artifacts/f7fa0a2c495b/grok.html
new file mode 100644
index 0000000..7c74b42
--- /dev/null
+++ b/data/artifacts/f7fa0a2c495b/grok.html
@@ -0,0 +1,314 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Kaleidoscope Drawing Toy</title>
+<style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ html, body {
+ width: 100%; height: 100%;
+ overflow: hidden;
+ background: #0a0a12;
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
+ color: #e8e8f0;
+ }
+ #wrap {
+ position: relative;
+ width: 100%; height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ canvas {
+ display: block;
+ cursor: crosshair;
+ touch-action: none;
+ }
+ #ui {
+ position: absolute;
+ top: 16px;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ align-items: center;
+ justify-content: center;
+ padding: 10px 16px;
+ background: rgba(18, 18, 32, 0.82);
+ border: 1px solid rgba(255,255,255,0.08);
+ border-radius: 14px;
+ backdrop-filter: blur(12px);
+ box-shadow: 0 8px 32px rgba(0,0,0,0.45);
+ z-index: 10;
+ user-select: none;
+ }
+ #ui label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 13px;
+ letter-spacing: 0.02em;
+ color: #b8b8d0;
+ }
+ #ui input[type="range"] {
+ width: 110px;
+ accent-color: #7c6af0;
+ cursor: pointer;
+ }
+ #segVal {
+ min-width: 22px;
+ font-variant-numeric: tabular-nums;
+ color: #c4b8ff;
+ font-weight: 600;
+ }
+ #ui button {
+ border: none;
+ border-radius: 8px;
+ padding: 8px 14px;
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+ background: #2a2a44;
+ color: #e8e8f0;
+ transition: background 0.15s, transform 0.1s;
+ }
+ #ui button:hover { background: #3a3a5c; }
+ #ui button:active { transform: scale(0.97); }
+ #ui button.primary {
+ background: linear-gradient(135deg, #6b5ce7, #9b59b6);
+ color: #fff;
+ }
+ #ui button.primary:hover {
+ background: linear-gradient(135deg, #7b6cf7, #ab69c6);
+ }
+ #swatch {
+ width: 18px; height: 18px;
+ border-radius: 50%;
+ border: 2px solid rgba(255,255,255,0.35);
+ box-shadow: 0 0 10px currentColor;
+ }
+ #hint {
+ position: absolute;
+ bottom: 18px;
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: 12px;
+ color: rgba(180,180,210,0.55);
+ pointer-events: none;
+ letter-spacing: 0.04em;
+ }
+</style>
+</head>
+<body>
+<div id="wrap">
+ <canvas id="c"></canvas>
+ <div id="ui">
+ <label>
+ Segments
+ <input type="range" id="segments" min="2" max="24" value="8" step="1">
+ <span id="segVal">8</span>
+ </label>
+ <span id="swatch" title="Current color"></span>
+ <button type="button" id="clearBtn">Clear</button>
+ <button type="button" id="saveBtn" class="primary">Save PNG</button>
+ </div>
+ <div id="hint">Move or drag to paint · color cycles automatically</div>
+</div>
+<script>
+(function () {
+ const canvas = document.getElementById('c');
+ const ctx = canvas.getContext('2d');
+ const segInput = document.getElementById('segments');
+ const segVal = document.getElementById('segVal');
+ const swatch = document.getElementById('swatch');
+ const clearBtn = document.getElementById('clearBtn');
+ const saveBtn = document.getElementById('saveBtn');
+
+ let W = 0, H = 0, CX = 0, CY = 0;
+ let drawing = false;
+ let lastX = null, lastY = null;
+ let segments = 8;
+ let hue = 0;
+ let dpr = 1;
+
+ function resize() {
+ dpr = Math.min(window.devicePixelRatio || 1, 2);
+ W = window.innerWidth;
+ H = window.innerHeight;
+ canvas.width = Math.floor(W * dpr);
+ canvas.height = Math.floor(H * dpr);
+ canvas.style.width = W + 'px';
+ canvas.style.height = H + 'px';
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ CX = W / 2;
+ CY = H / 2;
+ paintBg();
+ }
+
+ function paintBg() {
+ ctx.fillStyle = '#0a0a12';
+ ctx.fillRect(0, 0, W, H);
+ // subtle center guide
+ ctx.save();
+ ctx.strokeStyle = 'rgba(255,255,255,0.04)';
+ ctx.beginPath();
+ ctx.arc(CX, CY, Math.min(W, H) * 0.35, 0, Math.PI * 2);
+ ctx.stroke();
+ ctx.restore();
+ }
+
+ function cycleColor(dt) {
+ hue = (hue + dt * 0.04) % 360;
+ const c = 'hsl(' + hue + ', 85%, 60%)';
+ swatch.style.background = c;
+ swatch.style.color = c;
+ return c;
+ }
+
+ // continuous hue tick even when not drawing
+ let lastT = performance.now();
+ function tick(t) {
+ const dt = t - lastT;
+ lastT = t;
+ cycleColor(dt);
+ requestAnimationFrame(tick);
+ }
+ requestAnimationFrame(tick);
+
+ function getPos(e) {
+ if (e.touches && e.touches.length) {
+ return { x: e.touches[0].clientX, y: e.touches[0].clientY };
+ }
+ return { x: e.clientX, y: e.clientY };
+ }
+
+ function paintStroke(x0, y0, x1, y1) {
+ const color = 'hsl(' + hue + ', 90%, 58%)';
+ const dx0 = x0 - CX, dy0 = y0 - CY;
+ const dx1 = x1 - CX, dy1 = y1 - CY;
+ const n = segments;
+ const step = (Math.PI * 2) / n;
+
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.lineWidth = 2.4;
+ ctx.strokeStyle = color;
+ ctx.globalCompositeOperation = 'lighter';
+
+ for (let i = 0; i < n; i++) {
+ const a = i * step;
+ const cos = Math.cos(a), sin = Math.sin(a);
+
+ // rotated pair
+ const rx0 = dx0 * cos - dy0 * sin;
+ const ry0 = dx0 * sin + dy0 * cos;
+ const rx1 = dx1 * cos - dy1 * sin;
+ const ry1 = dx1 * sin + dy1 * cos;
+
+ ctx.beginPath();
+ ctx.moveTo(CX + rx0, CY + ry0);
+ ctx.lineTo(CX + rx1, CY + ry1);
+ ctx.stroke();
+
+ // mirrored across the segment bisector (reflection)
+ ctx.beginPath();
+ ctx.moveTo(CX + rx0, CY - ry0);
+ ctx.lineTo(CX + rx1, CY - ry1);
+ ctx.stroke();
+ }
+
+ ctx.globalCompositeOperation = 'source-over';
+ }
+
+ function onDown(e) {
+ e.preventDefault();
+ drawing = true;
+ const p = getPos(e);
+ lastX = p.x;
+ lastY = p.y;
+ }
+
+ function onMove(e) {
+ e.preventDefault();
+ const p = getPos(e);
+ if (!drawing) {
+ // freehand trail without press still paints lightly for toy feel
+ if (lastX != null && lastY != null) {
+ const dist = Math.hypot(p.x - lastX, p.y - lastY);
+ if (dist > 0.5 && dist < 80) {
+ paintStroke(lastX, lastY, p.x, p.y);
+ }
+ }
+ lastX = p.x;
+ lastY = p.y;
+ return;
+ }
+ if (lastX != null && lastY != null) {
+ paintStroke(lastX, lastY, p.x, p.y);
+ }
+ lastX = p.x;
+ lastY = p.y;
+ }
+
+ function onUp(e) {
+ drawing = false;
+ }
+
+ function onLeave() {
+ drawing = false;
+ lastX = null;
+ lastY = null;
+ }
+
+ canvas.addEventListener('mousedown', onDown);
+ canvas.addEventListener('mousemove', onMove);
+ window.addEventListener('mouseup', onUp);
+ canvas.addEventListener('mouseleave', onLeave);
+
+ canvas.addEventListener('touchstart', onDown, { passive: false });
+ canvas.addEventListener('touchmove', onMove, { passive: false });
+ canvas.addEventListener('touchend', onUp);
+ canvas.addEventListener('touchcancel', onUp);
+
+ segInput.addEventListener('input', function () {
+ segments = parseInt(segInput.value, 10) || 8;
+ segVal.textContent = String(segments);
+ });
+
+ clearBtn.addEventListener('click', function () {
+ paintBg();
+ });
+
+ saveBtn.addEventListener('click', function () {
+ // export at device pixels with solid background
+ const exportCanvas = document.createElement('canvas');
+ exportCanvas.width = canvas.width;
+ exportCanvas.height = canvas.height;
+ const ex = exportCanvas.getContext('2d');
+ ex.fillStyle = '#0a0a12';
+ ex.fillRect(0, 0, exportCanvas.width, exportCanvas.height);
+ ex.drawImage(canvas, 0, 0);
+ const a = document.createElement('a');
+ a.download = 'kaleidoscope-' + Date.now() + '.png';
+ a.href = exportCanvas.toDataURL('image/png');
+ a.click();
+ });
+
+ window.addEventListener('resize', function () {
+ // preserve drawing by snapshot
+ const tmp = document.createElement('canvas');
+ tmp.width = canvas.width;
+ tmp.height = canvas.height;
+ tmp.getContext('2d').drawImage(canvas, 0, 0);
+ const oldW = W, oldH = H;
+ resize();
+ ctx.drawImage(tmp, 0, 0, oldW * dpr, oldH * dpr, 0, 0, W, H);
+ });
+
+ resize();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/f7fa0a2c495b/grok.png b/data/artifacts/f7fa0a2c495b/grok.png
new file mode 100644
index 0000000..f2edc01
Binary files /dev/null and b/data/artifacts/f7fa0a2c495b/grok.png differ
diff --git a/data/artifacts/f7fa0a2c495b/hermes3-8b.html b/data/artifacts/f7fa0a2c495b/hermes3-8b.html
new file mode 100644
index 0000000..b1caf74
--- /dev/null
+++ b/data/artifacts/f7fa0a2c495b/hermes3-8b.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Kaleidoscope Drawing Toy</title>
+ <style>
+ body {
+ margin: 0;
+ overflow: hidden;
+ }
+
+ #canvas {
+ cursor: none;
+ }
+
+ button {
+ position: fixed;
+ bottom: 10px;
+ left: 10px;
+ }
+ </style>
+</head>
+<body>
+ <button id="clear">Clear</button>
+ <button id="save">Save PNG</button>
+ <canvas id="canvas" width="800" height="600"></canvas>
+
+ <script>
+ const canvas = document.getElementById('canvas');
+ const ctx = canvas.getContext('2d');
+ let centerX = canvas.width / 2;
+ let centerY = canvas.height / 2;
+
+ function drawKaleidoscope() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ const segments = 8;
+ for (let i = 0; i < segments * 2 + 1; i++) {
+ if (i >= segments) {
+ ctx.translate(centerX, centerY);
+ ctx.scale(-1, 1);
+ }
+
+ const angle = Math.PI / segments * i;
+
+ const x = centerX + Math.cos(angle) * 250;
+ const y = centerY + Math.sin(angle) * 250;
+
+ ctx.beginPath();
+ ctx.arc(centerX, centerY, Math.sqrt(Math.pow(centerX - x, 2) + Math.pow(centerY - y, 2)), 0, 2 * Math.PI);
+ ctx.strokeStyle = `hsl(${Date.now() % 360}, 100%, 50%)`;
+ ctx.stroke();
+ }
+ }
+
+ canvas.addEventListener('mousemove', (e) => {
+ const rect = canvas.getBoundingClientRect();
+ centerX = e.clientX - rect.left;
+ centerY = e.clientY - rect.top;
+
+ drawKaleidoscope();
+ });
+
+ document.getElementById('clear').addEventListener('click', () => {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ });
+
+ document.getElementById('save').addEventListener('click', () => {
+ const link = document.createElement('a');
+ link.href = canvas.toDataURL("image/png");
+ link.download = "kaleidoscope";
+ link.click();
+ });
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/f7fa0a2c495b/hermes3-8b.png b/data/artifacts/f7fa0a2c495b/hermes3-8b.png
new file mode 100644
index 0000000..4bccc00
Binary files /dev/null and b/data/artifacts/f7fa0a2c495b/hermes3-8b.png differ
diff --git a/data/artifacts/f7fa0a2c495b/hf-qwen-coder-32b.html b/data/artifacts/f7fa0a2c495b/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..3df4b75
--- /dev/null
+++ b/data/artifacts/f7fa0a2c495b/hf-qwen-coder-32b.html
@@ -0,0 +1,544 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Kaleidoscope Drawing Toy</title>
+<style>
+ body { display: flex; justify-content: center; align-items: center; height: 100vh; background: #000; margin: 0; }
+ canvas { border: 2px solid #fff; cursor: crosshair; }
+ button { position: absolute; top: 10px; right: 10px; color: #fff; background: rgba(0,0,0,0.5); border: none; padding: 10px; cursor: pointer; }
+</style>
+</head>
+<body>
+<canvas id="kaleidoscope"></canvas>
+<button onclick="saveCanvas()">Save PNG</button>
+<script>
+const canvas = document.getElementById('kaleidoscope');
+const ctx = canvas.getContext('2d');
+let segments = 6;
+let lastX, lastY;
+let hue = 0;
+
+function resizeCanvas() {
+ canvas.width = window.innerWidth - 40;
+ canvas.height = window.innerHeight - 40;
+}
+window.addEventListener('resize', resizeCanvas);
+resizeCanvas();
+
+function draw(x, y) {
+ ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
+ hue = (hue + 1) % 360;
+
+ for (let i = 0; i < segments; i++) {
+ const angle = (Math.PI * 2 / segments) * i;
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180));
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180));
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 2);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 2);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 3);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 3);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 4);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 4);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 5);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 5);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 6);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 6);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 7);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 7);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 8);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 8);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 9);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 9);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 10);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 10);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 11);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 11);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 12);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 12);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 13);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 13);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 14);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 14);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 15);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 15);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 16);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 16);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 17);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 17);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 18);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 18);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 19);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 19);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 20);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 20);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 21);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 21);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 22);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 22);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 23);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 23);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 24);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 24);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 25);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(-angle * (Math.PI / 180) * 25);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+
+ ctx.beginPath();
+ ctx.moveTo(canvas.width / 2, canvas.height / 2);
+ ctx.lineTo(lastX - canvas.width / 2, lastY - canvas.height / 2);
+ ctx.lineTo(x - canvas.width / 2, y - canvas.height / 2);
+ ctx.stroke();
+
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle * (Math.PI / 180) * 26);
+ ctx.translate(-
\ No newline at end of file
diff --git a/data/artifacts/f7fa0a2c495b/hf-qwen-coder-32b.png b/data/artifacts/f7fa0a2c495b/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..98a98c0
Binary files /dev/null and b/data/artifacts/f7fa0a2c495b/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/f7fa0a2c495b/kimi.html b/data/artifacts/f7fa0a2c495b/kimi.html
new file mode 100644
index 0000000..fc372fe
--- /dev/null
+++ b/data/artifacts/f7fa0a2c495b/kimi.html
@@ -0,0 +1,276 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+ <title>Kaleidoscope Drawing Toy</title>
+ <style>
+ * {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ }
+ body {
+ background: #000;
+ overflow: hidden;
+ touch-action: none;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ }
+ canvas {
+ display: block;
+ cursor: crosshair;
+ }
+ #controls {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ background: rgba(20, 20, 20, 0.85);
+ backdrop-filter: blur(10px);
+ padding: 20px;
+ border-radius: 12px;
+ color: #fff;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.5);
+ border: 1px solid rgba(255,255,255,0.1);
+ user-select: none;
+ z-index: 10;
+ }
+ .control-group {
+ margin-bottom: 15px;
+ }
+ label {
+ display: block;
+ font-size: 13px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ margin-bottom: 8px;
+ color: #aaa;
+ }
+ #segVal {
+ color: #fff;
+ font-weight: bold;
+ margin-left: 5px;
+ }
+ input[type="range"] {
+ width: 200px;
+ height: 6px;
+ background: #333;
+ outline: none;
+ border-radius: 3px;
+ -webkit-appearance: none;
+ }
+ input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 18px;
+ height: 18px;
+ background: #0af;
+ border-radius: 50%;
+ cursor: pointer;
+ transition: background 0.2s;
+ }
+ input[type="range"]::-webkit-slider-thumb:hover {
+ background: #0cf;
+ }
+ input[type="range"]::-moz-range-thumb {
+ width: 18px;
+ height: 18px;
+ background: #0af;
+ border-radius: 50%;
+ cursor: pointer;
+ border: none;
+ }
+ .buttons {
+ display: flex;
+ gap: 10px;
+ margin-top: 20px;
+ }
+ button {
+ flex: 1;
+ padding: 10px 16px;
+ border: none;
+ border-radius: 6px;
+ background: #222;
+ color: #fff;
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s;
+ border: 1px solid rgba(255,255,255,0.1);
+ }
+ button:hover {
+ background: #333;
+ transform: translateY(-1px);
+ }
+ button:active {
+ transform: translateY(0);
+ }
+ #clear {
+ background: #421;
+ }
+ #clear:hover {
+ background: #632;
+ }
+ #save {
+ background: #0a4;
+ }
+ #save:hover {
+ background: #0c6;
+ }
+ @media (max-width: 600px) {
+ #controls {
+ top: 10px;
+ left: 10px;
+ right: 10px;
+ padding: 15px;
+ }
+ input[type="range"] {
+ width: 100%;
+ }
+ }
+ </style>
+</head>
+<body>
+ <canvas id="c"></canvas>
+ <div id="controls">
+ <div class="control-group">
+ <label>Mirror Segments: <span id="segVal">6</span></label>
+ <input type="range" id="segments" min="2" max="24" value="6">
+ </div>
+ <div class="buttons">
+ <button id="clear">Clear</button>
+ <button id="save">Save PNG</button>
+ </div>
+ </div>
+
+ <script>
+ const canvas = document.getElementById('c');
+ const ctx = canvas.getContext('2d');
+ const segInput = document.getElementById('segments');
+ const segVal = document.getElementById('segVal');
+
+ let width, height, cx, cy;
+ let isDrawing = false;
+ let lastX = 0, lastY = 0;
+ let hue = 0;
+ let segments = 6;
+
+ function resize() {
+ width = window.innerWidth;
+ height = window.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+ cx = width / 2;
+ cy = height / 2;
+
+ // Fill black on init, preserve on resize if possible (but canvas clears on resize)
+ ctx.fillStyle = '#000000';
+ ctx.fillRect(0, 0, width, height);
+ }
+
+ window.addEventListener('resize', resize);
+ resize();
+
+ // Initial black background
+ ctx.fillStyle = '#000000';
+ ctx.fillRect(0, 0, width, height);
+
+ segInput.addEventListener('input', (e) => {
+ segments = parseInt(e.target.value);
+ segVal.textContent = segments;
+ });
+
+ document.getElementById('clear').addEventListener('click', () => {
+ ctx.fillStyle = '#000000';
+ ctx.fillRect(0, 0, width, height);
+ });
+
+ document.getElementById('save').addEventListener('click', () => {
+ const link = document.createElement('a');
+ link.download = 'kaleidoscope-' + Date.now() + '.png';
+ link.href = canvas.toDataURL('image/png');
+ link.click();
+ });
+
+ function getPos(e) {
+ if (e.touches && e.touches.length > 0) {
+ return {
+ x: e.touches[0].clientX,
+ y: e.touches[0].clientY
+ };
+ }
+ return {
+ x: e.clientX,
+ y: e.clientY
+ };
+ }
+
+ function start(e) {
+ isDrawing = true;
+ const pos = getPos(e);
+ lastX = pos.x - cx;
+ lastY = pos.y - cy;
+ }
+
+ function move(e) {
+ if (!isDrawing) return;
+ e.preventDefault();
+
+ const pos = getPos(e);
+ const x = pos.x - cx;
+ const y = pos.y - cy;
+
+ ctx.lineWidth = 3;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
+ ctx.shadowBlur = 8;
+ ctx.shadowColor = ctx.strokeStyle;
+ ctx.globalCompositeOperation = 'screen';
+
+ const step = (Math.PI * 2) / segments;
+
+ ctx.save();
+ ctx.translate(cx, cy);
+
+ for (let i = 0; i < segments; i++) {
+ ctx.save();
+ ctx.rotate(i * step);
+
+ // Draw original
+ ctx.beginPath();
+ ctx.moveTo(lastX, lastY);
+ ctx.lineTo(x, y);
+ ctx.stroke();
+
+ // Draw mirror (reflect across x-axis)
+ ctx.scale(1, -1);
+ ctx.beginPath();
+ ctx.moveTo(lastX, lastY);
+ ctx.lineTo(x, y);
+ ctx.stroke();
+
+ ctx.restore();
+ }
+
+ ctx.restore();
+
+ lastX = x;
+ lastY = y;
+ hue = (hue + 2) % 360;
+ }
+
+ function end() {
+ isDrawing = false;
+ }
+
+ canvas.addEventListener('mousedown', start);
+ canvas.addEventListener('mousemove', move);
+ canvas.addEventListener('mouseup', end);
+ canvas.addEventListener('mouseout', end);
+
+ canvas.addEventListener('touchstart', start, {passive: false});
+ canvas.addEventListener('touchmove', move, {passive: false});
+ canvas.addEventListener('touchend', end);
+ canvas.addEventListener('touchcancel', end);
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/f7fa0a2c495b/kimi.png b/data/artifacts/f7fa0a2c495b/kimi.png
new file mode 100644
index 0000000..bd910f1
Binary files /dev/null and b/data/artifacts/f7fa0a2c495b/kimi.png differ
diff --git a/data/artifacts/f7fa0a2c495b/qwen25-7b.html b/data/artifacts/f7fa0a2c495b/qwen25-7b.html
new file mode 100644
index 0000000..ee47f58
--- /dev/null
+++ b/data/artifacts/f7fa0a2c495b/qwen25-7b.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Kaleidoscope Drawing Toy</title>
+ <style>
+ body { margin: 0; overflow: hidden; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #282c34; }
+ canvas { border: 1px solid white; cursor: crosshair; }
+ </style>
+</head>
+<body>
+ <canvas id="kaleidoscope" width="600" height="600"></canvas>
+ <script>
+ const canvas = document.getElementById('kaleidoscope');
+ const ctx = canvas.getContext('2d');
+ const width = canvas.width;
+ const height = canvas.height;
+ let segments = 8;
+ let angleStep = (Math.PI * 2) / segments;
+ let colors = ['#FF5733', '#F0B27A', '#66BB6A'];
+ let currentColorIndex = 0;
+
+ function drawKaleidoscope() {
+ ctx.clearRect(0, 0, width, height);
+ const angle = Math.PI * 2 / segments;
+ for (let i = 0; i < segments; i++) {
+ const x1 = Math.cos(i * angle) * 250 + width / 2;
+ const y1 = Math.sin(i * angle) * 250 + height / 2;
+ const x2 = Math.cos((i + 1) * angle) * 250 + width / 2;
+ const y2 = Math.sin((i + 1) * angle) * 250 + height / 2;
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ for (let j = 0; j < segments; j++) {
+ const x3 = Math.cos(j * angleStep + i * angle) * 250 + width / 2;
+ const y3 = Math.sin(j * angleStep + i * angle) * 250 + height / 2;
+ ctx.lineTo(x3, y3);
+ }
+ ctx.closePath();
+ ctx.strokeStyle = colors[currentColorIndex];
+ ctx.stroke();
+ }
+ currentColorIndex = (currentColorIndex + 1) % colors.length;
+ }
+
+ function drawStroke(event) {
+ const x = event.clientX - canvas.offsetLeft;
+ const y = event.clientY - canvas.offsetTop;
+ if (x >= 0 && x < width && y >= 0 && y < height) {
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ for (let i = 1; i <= segments; i++) {
+ const angleStep = Math.PI * 2 / segments;
+ const x1 = Math.cos(i * angleStep) * 50 + x;
+ const y1 = Math.sin(i * angleStep) * 50 + y;
+ ctx.lineTo(x1, y1);
+ }
+ ctx.closePath();
+ ctx.strokeStyle = colors[currentColorIndex];
+ ctx.stroke();
+ }
+ }
+
+ function saveCanvasAsPNG() {
+ const link = document.createElement('a');
+ link.href = canvas.toDataURL('image/png');
+ link.download = 'kaleidoscope.png';
+ link.click();
+ }
+
+ window.addEventListener('mousemove', drawStroke);
+ setInterval(drawKaleidoscope, 100);
+
+ canvas.addEventListener('click', () => {
+ ctx.clearRect(0, 0, width, height);
+ currentColorIndex = 0;
+ });
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/f7fa0a2c495b/qwen25-7b.png b/data/artifacts/f7fa0a2c495b/qwen25-7b.png
new file mode 100644
index 0000000..ec6ad2b
Binary files /dev/null and b/data/artifacts/f7fa0a2c495b/qwen25-7b.png differ
diff --git a/data/artifacts/f7fa0a2c495b/qwen3-14b.html b/data/artifacts/f7fa0a2c495b/qwen3-14b.html
new file mode 100644
index 0000000..456066d
--- /dev/null
+++ b/data/artifacts/f7fa0a2c495b/qwen3-14b.html
@@ -0,0 +1,201 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Kaleidoscope Drawing Toy</title>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ width: 100%;
+ height: 100%;
+ background: #111;
+ color: #fff;
+ font-family: sans-serif;
+ }
+ canvas {
+ display: block;
+ width: 100%;
+ height: 100%;
+ touch-action: none;
+ }
+ #controls {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ background: rgba(0,0,0,0.5);
+ padding: 10px;
+ border-radius: 5px;
+ font-size: 14px;
+ }
+ input[type="range"] {
+ width: 120px;
+ }
+ button {
+ margin-left: 10px;
+ padding: 5px 10px;
+ font-size: 14px;
+ }
+ </style>
+</head>
+<body>
+<canvas id="canvas"></canvas>
+<div id="controls">
+ <label>Segments: <input type="range" id="segments" min="2" max="12" value="6"></label>
+ <button id="clear">Clear</button>
+ <button id="save">Save as PNG</button>
+</div>
+<script>
+ const canvas = document.getElementById('canvas');
+ const ctx = canvas.getContext('2d');
+ const controls = document.getElementById('controls');
+ const segmentsInput = document.getElementById('segments');
+ const clearButton = document.getElementById('clear');
+ const saveButton = document.getElementById('save');
+
+ let lastX = -1, lastY = -1;
+ let color = 0;
+ let segments = parseInt(segmentsInput.value);
+ let mouseDown = false;
+ let imageData = null;
+
+ function resizeCanvas() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ if (imageData) {
+ ctx.putImageData(imageData, 0, 0);
+ }
+ }
+
+ function drawSymmetric(x, y, x1, y1) {
+ const centerX = canvas.width / 2;
+ const centerY = canvas.height / 2;
+ const angle = (Math.PI * 2) / segments;
+ const dx = x - centerX;
+ const dy = y - centerY;
+ const dx1 = x1 - centerX;
+ const dy1 = y1 - centerY;
+ for (let i = 0; i < segments; i++) {
+ const cos = Math.cos(i * angle);
+ const sin = Math.sin(i * angle);
+ const rx = dx * cos - dy * sin;
+ const ry = dx * sin + dy * cos;
+ const rx1 = dx1 * cos - dy1 * sin;
+ const ry1 = dx1 * sin + dy1 * cos;
+ ctx.beginPath();
+ ctx.moveTo(centerX + rx, centerY + ry);
+ ctx.lineTo(centerX + rx1, centerY + ry1);
+ ctx.strokeStyle = `hsl(${(color + i * 360 / segments) % 360}, 100%, 50%)`;
+ ctx.stroke();
+ }
+ }
+
+ function drawLine(x, y, x1, y1) {
+ drawSymmetric(x, y, x1, y1);
+ }
+
+ function animate() {
+ color = (color + 0.5) % 360;
+ requestAnimationFrame(animate);
+ }
+
+ function clearCanvas() {
+ imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ }
+
+ function savePNG() {
+ const link = document.createElement('a');
+ link.download = 'kaleidoscope.png';
+ link.href = canvas.toDataURL();
+ link.click();
+ }
+
+ function handleMouseMove(e) {
+ if (!mouseDown) return;
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ if (lastX === -1 || lastY === -1) {
+ lastX = x;
+ lastY = y;
+ return;
+ }
+ drawLine(lastX, lastY, x, y);
+ lastX = x;
+ lastY = y;
+ }
+
+ function handleMouseDown(e) {
+ mouseDown = true;
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ lastX = x;
+ lastY = y;
+ }
+
+ function handleMouseUp() {
+ mouseDown = false;
+ lastX = -1;
+ lastY = -1;
+ }
+
+ function handleTouchStart(e) {
+ e.preventDefault();
+ mouseDown = true;
+ const rect = canvas.getBoundingClientRect();
+ const x = e.touches[0].clientX - rect.left;
+ const y = e.touches[0].clientY - rect.top;
+ lastX = x;
+ lastY = y;
+ }
+
+ function handleTouchMove(e) {
+ e.preventDefault();
+ if (!mouseDown) return;
+ const rect = canvas.getBoundingClientRect();
+ const x = e.touches[0].clientX - rect.left;
+ const y = e.touches[0].clientY - rect.top;
+ if (lastX === -1 || lastY === -1) {
+ lastX = x;
+ lastY = y;
+ return;
+ }
+ drawLine(lastX, lastY, x, y);
+ lastX = x;
+ lastY = y;
+ }
+
+ function handleTouchEnd() {
+ mouseDown = false;
+ lastX = -1;
+ lastY = -1;
+ }
+
+ function handleSegmentChange() {
+ segments = parseInt(segmentsInput.value);
+ }
+
+ function init() {
+ resizeCanvas();
+ animate();
+ clearCanvas();
+ canvas.addEventListener('mousemove', handleMouseMove);
+ canvas.addEventListener('mousedown', handleMouseDown);
+ canvas.addEventListener('mouseup', handleMouseUp);
+ canvas.addEventListener('mouseleave', handleMouseUp);
+ canvas.addEventListener('touchstart', handleTouchStart);
+ canvas.addEventListener('touchmove', handleTouchMove);
+ canvas.addEventListener('touchend', handleTouchEnd);
+ clearButton.addEventListener('click', clearCanvas);
+ saveButton.addEventListener('click', savePNG);
+ segmentsInput.addEventListener('input', handleSegmentChange);
+ window.addEventListener('resize', resizeCanvas);
+ }
+
+ init();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/f7fa0a2c495b/qwen3-14b.png b/data/artifacts/f7fa0a2c495b/qwen3-14b.png
new file mode 100644
index 0000000..27b53a3
Binary files /dev/null and b/data/artifacts/f7fa0a2c495b/qwen3-14b.png differ
diff --git a/data/challenges.json b/data/challenges.json
index 0876765..21641a4 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -4098,7 +4098,7 @@
"category": "Games",
"designTools": false,
"created_at": "2026-07-23T16:32:53.392Z",
- "winner": null,
+ "winner": "claude-code",
"runs": [
{
"model": "qwen3-14b",
@@ -4274,7 +4274,8 @@
],
"judging": false,
"judged_at": "2026-07-23T16:39:15.187Z",
- "aiPick": "claude-code"
+ "aiPick": "claude-code",
+ "voted_at": "2026-07-23T16:54:27.161Z"
},
{
"id": "edbe5de71e26",
@@ -4283,7 +4284,7 @@
"category": "Custom",
"designTools": false,
"created_at": "2026-07-23T16:35:42.481Z",
- "winner": null,
+ "winner": "qwen3-14b",
"runs": [
{
"model": "qwen3-14b",
@@ -4459,7 +4460,8 @@
],
"judging": false,
"judged_at": "2026-07-23T16:43:13.537Z",
- "aiPick": "qwen3-14b"
+ "aiPick": "qwen3-14b",
+ "voted_at": "2026-07-23T16:54:27.158Z"
},
{
"id": "f1756a933e20",
@@ -4468,7 +4470,7 @@
"category": "Custom",
"designTools": false,
"created_at": "2026-07-23T16:40:16.308Z",
- "winner": null,
+ "winner": "claude-code",
"runs": [
{
"model": "qwen3-14b",
@@ -4644,7 +4646,8 @@
],
"judging": false,
"judged_at": "2026-07-23T16:43:32.989Z",
- "aiPick": "claude-code"
+ "aiPick": "claude-code",
+ "voted_at": "2026-07-23T16:54:27.154Z"
},
{
"id": "2ca024f41f3c",
@@ -4653,7 +4656,7 @@
"category": "Custom",
"designTools": false,
"created_at": "2026-07-23T16:43:51.781Z",
- "winner": null,
+ "winner": "hf-qwen-coder-32b",
"runs": [
{
"model": "qwen3-14b",
@@ -4779,7 +4782,14 @@
"finished_at": "2026-07-23T16:46:37.149Z",
"queued_at": "2026-07-23T16:43:51.797Z",
"bytes": 6680,
- "thumb": true
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The model fulfills the challenge requirements but lacks some visual polish and interactivity.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
},
{
"model": "gpt",
@@ -4791,7 +4801,14 @@
"finished_at": "2026-07-23T16:44:26.637Z",
"queued_at": "2026-07-23T16:43:51.798Z",
"bytes": 9131,
- "thumb": true
+ "thumb": true,
+ "aiScore": 7,
+ "aiReason": "The model fulfills the challenge requirements but lacks some visual polish and interactivity.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7
+ },
+ "aiSpread": 0
},
{
"model": "grok",
@@ -4803,11 +4820,20 @@
"finished_at": "2026-07-23T16:44:33.441Z",
"queued_at": "2026-07-23T16:43:51.800Z",
"bytes": 9583,
- "thumb": true
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The model fulfills the challenge requirements but lacks some visual polish and interactivity.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 2.5
}
],
- "judging": true,
- "judged_at": null
+ "judging": false,
+ "judged_at": "2026-07-23T16:52:29.677Z",
+ "aiPick": "hf-qwen-coder-32b",
+ "voted_at": "2026-07-23T16:54:27.151Z"
},
{
"id": "c2d8bc75e725",
@@ -4816,7 +4842,7 @@
"category": "Custom",
"designTools": false,
"created_at": "2026-07-23T16:47:05.089Z",
- "winner": null,
+ "winner": "claude-code",
"runs": [
{
"model": "qwen3-14b",
@@ -4885,7 +4911,14 @@
"finished_at": "2026-07-23T16:47:18.623Z",
"queued_at": "2026-07-23T16:47:05.102Z",
"bytes": 2401,
- "thumb": true
+ "thumb": true,
+ "aiScore": 6.8,
+ "aiReason": "The page is functional but lacks visual appeal and does not include live WPM or accuracy metrics.",
+ "aiScores": {
+ "qwen2.5vl:7b": 6,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
},
{
"model": "hf-qwen-coder-32b",
@@ -4897,7 +4930,14 @@
"finished_at": "2026-07-23T16:49:09.648Z",
"queued_at": "2026-07-23T16:47:05.105Z",
"bytes": 3154,
- "thumb": true
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The page is functional but lacks visual appeal and does not highlight characters as the user types.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 2.5
},
{
"model": "claude-code",
@@ -4909,7 +4949,14 @@
"finished_at": "2026-07-23T16:47:57.347Z",
"queued_at": "2026-07-23T16:47:05.108Z",
"bytes": 10946,
- "thumb": true
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The page is clean and fulfills the challenge requirements effectively.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
},
{
"model": "kimi",
@@ -4921,7 +4968,14 @@
"finished_at": "2026-07-23T16:48:52.866Z",
"queued_at": "2026-07-23T16:47:05.111Z",
"bytes": 7071,
- "thumb": true
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The page is functional but lacks visual appeal and interactivity.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
},
{
"model": "gpt",
@@ -4933,7 +4987,14 @@
"finished_at": "2026-07-23T16:47:30.552Z",
"queued_at": "2026-07-23T16:47:05.113Z",
"bytes": 11395,
- "thumb": true
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The page is clean and fulfills the challenge requirements effectively.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
},
{
"model": "grok",
@@ -4945,11 +5006,20 @@
"finished_at": "2026-07-23T16:47:44.290Z",
"queued_at": "2026-07-23T16:47:05.115Z",
"bytes": 9916,
- "thumb": true
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The page is functional but lacks visual appeal and does not highlight errors or display WPM/accuracy in a visually engaging way.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 2.5
}
],
- "judging": true,
- "judged_at": null
+ "judging": false,
+ "judged_at": "2026-07-23T16:52:48.547Z",
+ "aiPick": "claude-code",
+ "voted_at": "2026-07-23T16:54:27.146Z"
},
{
"id": "1fd68f0d37b7",
@@ -4958,7 +5028,7 @@
"category": "Games",
"designTools": false,
"created_at": "2026-07-23T16:49:39.482Z",
- "winner": null,
+ "winner": "claude-code",
"runs": [
{
"model": "qwen3-14b",
@@ -4970,27 +5040,52 @@
"finished_at": "2026-07-23T16:50:06.809Z",
"queued_at": "2026-07-23T16:49:39.485Z",
"bytes": 3032,
- "thumb": true
+ "thumb": true,
+ "aiScore": 6.3,
+ "aiReason": "The page has basic functionality but lacks visual quality and interactivity.",
+ "aiScores": {
+ "qwen2.5vl:7b": 5,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 2.5
},
{
"model": "gemma3-12b",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 31,
+ "cost": 0,
"started_at": "2026-07-23T16:50:06.811Z",
- "finished_at": null,
- "queued_at": "2026-07-23T16:49:39.486Z"
+ "finished_at": "2026-07-23T16:50:37.320Z",
+ "queued_at": "2026-07-23T16:49:39.486Z",
+ "bytes": 4043,
+ "thumb": true,
+ "aiScore": 5.3,
+ "aiReason": "The page is empty and does not display any audio visualization or color theme toggle.",
+ "aiScores": {
+ "qwen2.5vl:7b": 4,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 2.5
},
{
"model": "hermes3-8b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-23T16:49:39.489Z"
+ "seconds": 19,
+ "cost": 0,
+ "started_at": "2026-07-23T16:50:37.323Z",
+ "finished_at": "2026-07-23T16:50:56.645Z",
+ "queued_at": "2026-07-23T16:49:39.489Z",
+ "bytes": 3438,
+ "thumb": true,
+ "aiScore": 5.8,
+ "aiReason": "The page successfully implements the audio visualizer and color theme toggle but lacks a polished design or additional features.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 4.5
+ },
+ "aiSpread": 2.5
},
{
"model": "qwen25-7b",
@@ -5002,57 +5097,1148 @@
"finished_at": "2026-07-23T16:49:57.321Z",
"queued_at": "2026-07-23T16:49:39.492Z",
"bytes": 3106,
- "thumb": true
+ "thumb": true,
+ "aiScore": 6.8,
+ "aiReason": "The page successfully implements the audio visualizer and theme toggle but lacks a live waveform visualization.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 0.5
},
{
"model": "hf-qwen-coder-32b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-23T16:49:39.495Z"
+ "seconds": 44,
+ "cost": 0,
+ "started_at": "2026-07-23T16:50:56.649Z",
+ "finished_at": "2026-07-23T16:51:40.213Z",
+ "queued_at": "2026-07-23T16:49:39.495Z",
+ "bytes": 3383,
+ "thumb": true,
+ "aiScore": 6.3,
+ "aiReason": "The page successfully asks for microphone permission and toggles theme but lacks the audio visualization component.",
+ "aiScores": {
+ "qwen2.5vl:7b": 6,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 0.5
},
{
"model": "claude-code",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 48,
+ "cost": 0,
"started_at": "2026-07-23T16:49:39.511Z",
- "finished_at": null,
- "queued_at": "2026-07-23T16:49:39.499Z"
+ "finished_at": "2026-07-23T16:50:27.780Z",
+ "queued_at": "2026-07-23T16:49:39.499Z",
+ "bytes": 10533,
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The page is self-contained and fulfills the challenge requirements effectively. The visual quality is good with a clear theme toggle and instructions.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
},
{
"model": "kimi",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 101,
+ "cost": 0.0156,
"started_at": "2026-07-23T16:49:39.514Z",
- "finished_at": null,
- "queued_at": "2026-07-23T16:49:39.502Z"
+ "finished_at": "2026-07-23T16:51:20.509Z",
+ "queued_at": "2026-07-23T16:49:39.502Z",
+ "bytes": 8250,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The page is functional but lacks visual appeal and interactivity.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
},
{
"model": "gpt",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 38,
+ "cost": 0.0611,
"started_at": "2026-07-23T16:49:39.516Z",
- "finished_at": null,
- "queued_at": "2026-07-23T16:49:39.504Z"
+ "finished_at": "2026-07-23T16:50:17.578Z",
+ "queued_at": "2026-07-23T16:49:39.504Z",
+ "bytes": 14627,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The page fulfills the challenge requirements but lacks visual appeal and could benefit from more polished design elements.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
},
{
"model": "grok",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 55,
+ "cost": 0.0738,
"started_at": "2026-07-23T16:49:39.517Z",
- "finished_at": null,
- "queued_at": "2026-07-23T16:49:39.506Z"
+ "finished_at": "2026-07-23T16:50:34.279Z",
+ "queued_at": "2026-07-23T16:49:39.506Z",
+ "bytes": 14968,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The page is functional and meets the requirements but lacks visual appeal.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
+ }
+ ],
+ "judging": false,
+ "judged_at": "2026-07-23T16:53:10.199Z",
+ "aiPick": "claude-code",
+ "voted_at": "2026-07-23T16:54:27.140Z"
+ },
+ {
+ "id": "0e969e848172",
+ "title": "All-Models — Gravity Sandbox",
+ "prompt": "Build a single-file HTML page: a 2D gravity sandbox. Click to place planets with mass; they attract each other and orbiting bodies trace colored trails; N-body simulation with a clear button and a trail toggle. Canvas-based, self-contained, no external libraries.",
+ "category": "Custom",
+ "designTools": false,
+ "created_at": "2026-07-23T16:54:42.524Z",
+ "winner": "claude-code",
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 26,
+ "cost": 0,
+ "started_at": "2026-07-23T16:54:42.552Z",
+ "finished_at": "2026-07-23T16:55:08.092Z",
+ "queued_at": "2026-07-23T16:54:42.526Z",
+ "bytes": 3555,
+ "thumb": true,
+ "aiScore": 4.3,
+ "aiReason": "The page is functional but lacks visual appeal and detail.",
+ "aiScores": {
+ "qwen2.5vl:7b": 4,
+ "minicpm-v:latest": 4.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "gemma3-12b",
+ "status": "done",
+ "error": null,
+ "seconds": 35,
+ "cost": 0,
+ "started_at": "2026-07-23T16:55:08.095Z",
+ "finished_at": "2026-07-23T16:55:43.244Z",
+ "queued_at": "2026-07-23T16:54:42.529Z",
+ "bytes": 3682,
+ "thumb": true,
+ "aiScore": 6.8,
+ "aiReason": "The model fulfills the challenge requirements but lacks visual appeal and interactivity.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "done",
+ "error": null,
+ "seconds": 17,
+ "cost": 0,
+ "started_at": "2026-07-23T16:55:43.249Z",
+ "finished_at": "2026-07-23T16:56:00.743Z",
+ "queued_at": "2026-07-23T16:54:42.532Z",
+ "bytes": 4448,
+ "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": 22,
+ "cost": 0,
+ "started_at": "2026-07-23T16:54:42.555Z",
+ "finished_at": "2026-07-23T16:55:04.487Z",
+ "queued_at": "2026-07-23T16:54:42.534Z",
+ "bytes": 3195,
+ "thumb": true,
+ "aiScore": 5.8,
+ "aiReason": "The page is functional but lacks visual appeal and detail.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 4.5
+ },
+ "aiSpread": 2.5
+ },
+ {
+ "model": "hf-qwen-coder-32b",
+ "status": "done",
+ "error": null,
+ "seconds": 44,
+ "cost": 0,
+ "started_at": "2026-07-23T16:56:00.746Z",
+ "finished_at": "2026-07-23T16:56:44.814Z",
+ "queued_at": "2026-07-23T16:54:42.537Z",
+ "bytes": 2922,
+ "thumb": true,
+ "aiScore": 5,
+ "aiReason": "The page is functional but lacks visual appeal and detail.",
+ "aiScores": {
+ "qwen2.5vl:7b": 6,
+ "minicpm-v:latest": 4
+ },
+ "aiSpread": 2
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 58,
+ "cost": 0,
+ "started_at": "2026-07-23T16:54:42.558Z",
+ "finished_at": "2026-07-23T16:55:40.257Z",
+ "queued_at": "2026-07-23T16:54:42.539Z",
+ "bytes": 12221,
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The model successfully implements the challenge requirements and has a clear visual representation of an N-body simulation.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "kimi",
+ "status": "done",
+ "error": null,
+ "seconds": 88,
+ "cost": 0.0123,
+ "started_at": "2026-07-23T16:54:42.576Z",
+ "finished_at": "2026-07-23T16:56:10.168Z",
+ "queued_at": "2026-07-23T16:54:42.544Z",
+ "bytes": 8837,
+ "thumb": true,
+ "aiScore": 6.8,
+ "aiReason": "The model fulfills the challenge requirements but lacks visual appeal and detail.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "gpt",
+ "status": "done",
+ "error": null,
+ "seconds": 24,
+ "cost": 0.0414,
+ "started_at": "2026-07-23T16:54:42.578Z",
+ "finished_at": "2026-07-23T16:55:06.250Z",
+ "queued_at": "2026-07-23T16:54:42.547Z",
+ "bytes": 9810,
+ "thumb": true,
+ "aiScore": 7.8,
+ "aiReason": "The model fulfills the challenge requirements and has good visual quality.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 2.5
+ },
+ {
+ "model": "grok",
+ "status": "done",
+ "error": null,
+ "seconds": 35,
+ "cost": 0.0552,
+ "started_at": "2026-07-23T16:54:42.580Z",
+ "finished_at": "2026-07-23T16:55:17.971Z",
+ "queued_at": "2026-07-23T16:54:42.550Z",
+ "bytes": 10355,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The model fulfills the challenge 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-23T17:03:34.459Z",
+ "aiPick": "claude-code",
+ "voted_at": "2026-07-23T17:08:46.439Z"
+ },
+ {
+ "id": "f7fa0a2c495b",
+ "title": "All-Models — Kaleidoscope Draw",
+ "prompt": "Build a single-file HTML page: a kaleidoscope drawing toy. Mouse movement paints symmetric mirrored strokes around the center (adjustable number of mirror segments); color cycles over time; clear and save-PNG buttons. Canvas-based, self-contained, no external libraries.",
+ "category": "Custom",
+ "designTools": false,
+ "created_at": "2026-07-23T16:57:37.737Z",
+ "winner": "claude-code",
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 34,
+ "cost": 0,
+ "started_at": "2026-07-23T16:57:38.873Z",
+ "finished_at": "2026-07-23T16:58:13.166Z",
+ "queued_at": "2026-07-23T16:57:37.741Z",
+ "bytes": 5137,
+ "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": "gemma3-12b",
+ "status": "done",
+ "error": null,
+ "seconds": 25,
+ "cost": 0,
+ "started_at": "2026-07-23T16:58:13.168Z",
+ "finished_at": "2026-07-23T16:58:38.204Z",
+ "queued_at": "2026-07-23T16:57:37.744Z",
+ "bytes": 2973,
+ "thumb": true,
+ "aiScore": 5.3,
+ "aiReason": "The page is functional but lacks visual appeal and does not fully utilize the canvas for a kaleidoscope effect.",
+ "aiScores": {
+ "qwen2.5vl:7b": 4,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 2.5
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "done",
+ "error": null,
+ "seconds": 8,
+ "cost": 0,
+ "started_at": "2026-07-23T16:58:38.208Z",
+ "finished_at": "2026-07-23T16:58:46.573Z",
+ "queued_at": "2026-07-23T16:57:37.748Z",
+ "bytes": 2220,
+ "thumb": true,
+ "aiScore": 6.3,
+ "aiReason": "The page is functional but lacks visual appeal and does not fully utilize the canvas for its intended purpose.",
+ "aiScores": {
+ "qwen2.5vl:7b": 6,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 20,
+ "cost": 0,
+ "started_at": "2026-07-23T16:57:37.765Z",
+ "finished_at": "2026-07-23T16:57:57.677Z",
+ "queued_at": "2026-07-23T16:57:37.751Z",
+ "bytes": 3145,
+ "thumb": true,
+ "aiScore": 6.5,
+ "aiReason": "The page is functional but lacks the clear and save-PNG buttons as specified.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 1
+ },
+ {
+ "model": "hf-qwen-coder-32b",
+ "status": "done",
+ "error": null,
+ "seconds": 265,
+ "cost": 0,
+ "started_at": "2026-07-23T16:58:46.577Z",
+ "finished_at": "2026-07-23T17:03:11.906Z",
+ "queued_at": "2026-07-23T16:57:37.754Z",
+ "bytes": 20662,
+ "thumb": true,
+ "aiScore": 5.3,
+ "aiReason": "The page is functional but lacks visual appeal and does not fully utilize the canvas for a kaleidoscope effect.",
+ "aiScores": {
+ "qwen2.5vl:7b": 4,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 2.5
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 42,
+ "cost": 0,
+ "started_at": "2026-07-23T16:57:37.768Z",
+ "finished_at": "2026-07-23T16:58:19.718Z",
+ "queued_at": "2026-07-23T16:57:37.756Z",
+ "bytes": 8123,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The page is fully functional and visually appealing, fulfilling all the requirements of the challenge.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
+ },
+ {
+ "model": "kimi",
+ "status": "done",
+ "error": null,
+ "seconds": 119,
+ "cost": 0.0176,
+ "started_at": "2026-07-23T16:57:37.774Z",
+ "finished_at": "2026-07-23T16:59:36.937Z",
+ "queued_at": "2026-07-23T16:57:37.758Z",
+ "bytes": 8109,
+ "thumb": true,
+ "aiScore": 7.8,
+ "aiReason": "The page is self-contained and fulfills all the requirements of the challenge. The visual quality is good.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 2.5
+ },
+ {
+ "model": "gpt",
+ "status": "done",
+ "error": null,
+ "seconds": 14,
+ "cost": 0.0223,
+ "started_at": "2026-07-23T16:57:37.776Z",
+ "finished_at": "2026-07-23T16:57:51.859Z",
+ "queued_at": "2026-07-23T16:57:37.760Z",
+ "bytes": 5350,
+ "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": "grok",
+ "status": "done",
+ "error": null,
+ "seconds": 34,
+ "cost": 0.0422,
+ "started_at": "2026-07-23T16:57:37.777Z",
+ "finished_at": "2026-07-23T16:58:11.872Z",
+ "queued_at": "2026-07-23T16:57:37.763Z",
+ "bytes": 8187,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The page is self-contained and fulfills the challenge requirements well, though it lacks some visual flair.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
+ }
+ ],
+ "judging": false,
+ "judged_at": "2026-07-23T17:08:05.946Z",
+ "aiPick": "claude-code",
+ "voted_at": "2026-07-23T17:08:46.434Z"
+ },
+ {
+ "id": "6c13ff8455cc",
+ "title": "All-Models — Pathfinding Visualizer",
+ "prompt": "Build a single-file HTML page: an A* pathfinding visualizer on a grid. Click-drag to draw walls, set start/end, then animate the search exploring nodes and drawing the shortest path. Speed slider, clear, and random-maze buttons. Self-contained, no external libraries.",
+ "category": "Games",
+ "designTools": false,
+ "created_at": "2026-07-23T17:03:58.048Z",
+ "winner": "claude-code",
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 41,
+ "cost": 0,
+ "started_at": "2026-07-23T17:03:58.480Z",
+ "finished_at": "2026-07-23T17:04:39.555Z",
+ "queued_at": "2026-07-23T17:03:58.050Z",
+ "bytes": 6833,
+ "thumb": true,
+ "aiScore": 5.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": 4.5
+ },
+ "aiSpread": 2.5
+ },
+ {
+ "model": "gemma3-12b",
+ "status": "done",
+ "error": null,
+ "seconds": 71,
+ "cost": 0,
+ "started_at": "2026-07-23T17:04:39.560Z",
+ "finished_at": "2026-07-23T17:05:50.356Z",
+ "queued_at": "2026-07-23T17:03:58.055Z",
+ "bytes": 8909,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The page is functional but lacks visual appeal and interactivity.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "done",
+ "error": null,
+ "seconds": 27,
+ "cost": 0,
+ "started_at": "2026-07-23T17:05:50.360Z",
+ "finished_at": "2026-07-23T17:06:17.590Z",
+ "queued_at": "2026-07-23T17:03:58.059Z",
+ "bytes": 7459,
+ "thumb": true,
+ "aiScore": 6.5,
+ "aiReason": "The page is functional but lacks visual appeal and clarity.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 1
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 35,
+ "cost": 0,
+ "started_at": "2026-07-23T17:03:58.078Z",
+ "finished_at": "2026-07-23T17:04:33.445Z",
+ "queued_at": "2026-07-23T17:03:58.062Z",
+ "bytes": 5916,
+ "thumb": true,
+ "aiScore": 5.8,
+ "aiReason": "The page is empty and lacks the required functionality for pathfinding visualization.",
+ "aiScores": {
+ "qwen2.5vl:7b": 4,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 3.5
+ },
+ {
+ "model": "hf-qwen-coder-32b",
+ "status": "done",
+ "error": null,
+ "seconds": 73,
+ "cost": 0,
+ "started_at": "2026-07-23T17:06:17.595Z",
+ "finished_at": "2026-07-23T17:07:30.698Z",
+ "queued_at": "2026-07-23T17:03:58.064Z",
+ "bytes": 5439,
+ "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": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 77,
+ "cost": 0,
+ "started_at": "2026-07-23T17:03:58.080Z",
+ "finished_at": "2026-07-23T17:05:14.831Z",
+ "queued_at": "2026-07-23T17:03:58.066Z",
+ "bytes": 14002,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The visualizer is fully functional and meets all the requirements of the challenge.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
+ },
+ {
+ "model": "kimi",
+ "status": "done",
+ "error": null,
+ "seconds": 147,
+ "cost": 0.0223,
+ "started_at": "2026-07-23T17:03:58.086Z",
+ "finished_at": "2026-07-23T17:06:25.402Z",
+ "queued_at": "2026-07-23T17:03:58.069Z",
+ "bytes": 16443,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The visualizer is functional and meets the requirements but lacks some polish in terms of design.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "gpt",
+ "status": "done",
+ "error": null,
+ "seconds": 49,
+ "cost": 0.0587,
+ "started_at": "2026-07-23T17:03:58.089Z",
+ "finished_at": "2026-07-23T17:04:47.221Z",
+ "queued_at": "2026-07-23T17:03:58.072Z",
+ "bytes": 14347,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The visualizer is functional but lacks color contrast and user-friendly interface elements.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "grok",
+ "status": "done",
+ "error": null,
+ "seconds": 65,
+ "cost": 0.09,
+ "started_at": "2026-07-23T17:03:58.092Z",
+ "finished_at": "2026-07-23T17:05:02.701Z",
+ "queued_at": "2026-07-23T17:03:58.075Z",
+ "bytes": 17172,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The visualizer is fully functional and meets all the requirements of the challenge.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
+ }
+ ],
+ "judging": false,
+ "judged_at": "2026-07-23T17:08:33.850Z",
+ "aiPick": "claude-code",
+ "voted_at": "2026-07-23T17:08:46.429Z"
+ },
+ {
+ "id": "e3c185541c0d",
+ "title": "All-Models — Matrix Rain",
+ "prompt": "Build a single-file HTML page: the classic Matrix digital rain. Columns of falling green katakana/glyphs with head-glow and fading trails, responsive to window size; a color-theme toggle (green/amber/cyan). Canvas-based, self-contained, no external libraries.",
+ "category": "Custom",
+ "designTools": false,
+ "created_at": "2026-07-23T17:09:08.904Z",
+ "winner": "claude-code",
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 22,
+ "cost": 0,
+ "started_at": "2026-07-23T17:09:08.934Z",
+ "finished_at": "2026-07-23T17:09:30.591Z",
+ "queued_at": "2026-07-23T17:09:08.908Z",
+ "bytes": 2662,
+ "thumb": true,
+ "aiScore": 6.3,
+ "aiReason": "The page is visually empty and does not display the intended digital rain effect.",
+ "aiScores": {
+ "qwen2.5vl:7b": 5,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 2.5
+ },
+ {
+ "model": "gemma3-12b",
+ "status": "done",
+ "error": null,
+ "seconds": 26,
+ "cost": 0,
+ "started_at": "2026-07-23T17:09:30.594Z",
+ "finished_at": "2026-07-23T17:09:56.522Z",
+ "queued_at": "2026-07-23T17:09:08.912Z",
+ "bytes": 3205,
+ "thumb": true,
+ "aiScore": 7.8,
+ "aiReason": "The image successfully fulfills the challenge by displaying falling green glyphs with trails and a color toggle option.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 2.5
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "done",
+ "error": null,
+ "seconds": 13,
+ "cost": 0,
+ "started_at": "2026-07-23T17:09:56.525Z",
+ "finished_at": "2026-07-23T17:10:09.430Z",
+ "queued_at": "2026-07-23T17:09:08.916Z",
+ "bytes": 2923,
+ "thumb": true,
+ "aiScore": 6.8,
+ "aiReason": "The page is responsive and canvas-based but lacks the fading trails and color theme toggle.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 16,
+ "cost": 0,
+ "started_at": "2026-07-23T17:09:08.936Z",
+ "finished_at": "2026-07-23T17:09:25.364Z",
+ "queued_at": "2026-07-23T17:09:08.919Z",
+ "bytes": 2880,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The page is responsive and canvas-based but lacks the fading trails and color theme toggle.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "hf-qwen-coder-32b",
+ "status": "done",
+ "error": null,
+ "seconds": 36,
+ "cost": 0,
+ "started_at": "2026-07-23T17:10:09.433Z",
+ "finished_at": "2026-07-23T17:10:45.834Z",
+ "queued_at": "2026-07-23T17:09:08.922Z",
+ "bytes": 2152,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The page successfully implements the Matrix digital rain effect with responsive columns of green katakana/glyphs and a color-theme toggle. The visual quality is high, though some trails might be sligh",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 41,
+ "cost": 0,
+ "started_at": "2026-07-23T17:09:08.939Z",
+ "finished_at": "2026-07-23T17:09:49.540Z",
+ "queued_at": "2026-07-23T17:09:08.924Z",
+ "bytes": 6342,
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The page successfully implements the Matrix digital rain effect using canvas and meets all specified requirements.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "kimi",
+ "status": "done",
+ "error": null,
+ "seconds": 68,
+ "cost": 0.0096,
+ "started_at": "2026-07-23T17:09:08.942Z",
+ "finished_at": "2026-07-23T17:10:16.514Z",
+ "queued_at": "2026-07-23T17:09:08.927Z",
+ "bytes": 5753,
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The page successfully implements the Matrix digital rain effect using canvas and meets all specified requirements.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "gpt",
+ "status": "done",
+ "error": null,
+ "seconds": 15,
+ "cost": 0.0217,
+ "started_at": "2026-07-23T17:09:08.944Z",
+ "finished_at": "2026-07-23T17:09:23.712Z",
+ "queued_at": "2026-07-23T17:09:08.929Z",
+ "bytes": 4905,
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The page successfully displays the digital rain effect with responsive columns and color theme toggle.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "grok",
+ "status": "done",
+ "error": null,
+ "seconds": 16,
+ "cost": 0.0189,
+ "started_at": "2026-07-23T17:09:08.947Z",
+ "finished_at": "2026-07-23T17:09:24.741Z",
+ "queued_at": "2026-07-23T17:09:08.931Z",
+ "bytes": 3477,
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The page successfully implements the Matrix digital rain effect using canvas and meets all specified requirements.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
+ }
+ ],
+ "judging": false,
+ "judged_at": "2026-07-23T17:11:32.270Z",
+ "aiPick": "claude-code",
+ "voted_at": "2026-07-23T17:11:39.378Z"
+ },
+ {
+ "id": "905cc3052af2",
+ "title": "All-Models — Physics Domino Run",
+ "prompt": "Build a single-file HTML page: a domino-run physics toy. Click to place dominoes along a path; a start button topples the first and they cascade with 2D physics; reset button. Include a couple of preset layouts. Canvas-based, self-contained, no external libraries.",
+ "category": "Games",
+ "designTools": false,
+ "created_at": "2026-07-23T17:12:00.388Z",
+ "winner": "grok",
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 61,
+ "cost": 0,
+ "started_at": "2026-07-23T17:12:00.429Z",
+ "finished_at": "2026-07-23T17:13:01.007Z",
+ "queued_at": "2026-07-23T17:12:00.393Z",
+ "bytes": 9715,
+ "thumb": true,
+ "aiScore": 5.8,
+ "aiReason": "The page is functional but lacks visual appeal and detail.",
+ "aiScores": {
+ "qwen2.5vl:7b": 5,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 1.5
+ },
+ {
+ "model": "gemma3-12b",
+ "status": "done",
+ "error": null,
+ "seconds": 35,
+ "cost": 0,
+ "started_at": "2026-07-23T17:13:01.013Z",
+ "finished_at": "2026-07-23T17:13:35.775Z",
+ "queued_at": "2026-07-23T17:12:00.400Z",
+ "bytes": 4293,
+ "thumb": true,
+ "aiScore": 4.5,
+ "aiReason": "The page is empty and does not display any dominoes or a start button.",
+ "aiScores": {
+ "qwen2.5vl:7b": 3,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 3
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "done",
+ "error": null,
+ "seconds": 15,
+ "cost": 0,
+ "started_at": "2026-07-23T17:13:35.778Z",
+ "finished_at": "2026-07-23T17:13:50.467Z",
+ "queued_at": "2026-07-23T17:12:00.406Z",
+ "bytes": 4524,
+ "thumb": true,
+ "aiScore": 5.8,
+ "aiReason": "The page is functional but lacks visual elements and interactivity.",
+ "aiScores": {
+ "qwen2.5vl:7b": 5,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 1.5
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 21,
+ "cost": 0,
+ "started_at": "2026-07-23T17:12:00.431Z",
+ "finished_at": "2026-07-23T17:12:21.181Z",
+ "queued_at": "2026-07-23T17:12:00.410Z",
+ "bytes": 2787,
+ "thumb": true,
+ "aiScore": 5.3,
+ "aiReason": "The page is functional but lacks visual appeal and detail.",
+ "aiScores": {
+ "qwen2.5vl:7b": 4,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 2.5
+ },
+ {
+ "model": "hf-qwen-coder-32b",
+ "status": "done",
+ "error": null,
+ "seconds": 63,
+ "cost": 0,
+ "started_at": "2026-07-23T17:13:50.471Z",
+ "finished_at": "2026-07-23T17:14:53.754Z",
+ "queued_at": "2026-07-23T17:12:00.414Z",
+ "bytes": 4263,
+ "thumb": true,
+ "aiScore": 5.3,
+ "aiReason": "The layout is simple but lacks the interactive elements and visual quality expected for a physics toy.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 3.5
+ },
+ "aiSpread": 3.5
+ },
+ {
+ "model": "claude-code",
+ "status": "error",
+ "error": "claude CLI: Command failed: /Users/macstudio3/.local/bin/claude -p You are competing in a model arena against other AI models. Challenge:\n\nBuild a single-file HTML page: a ",
+ "seconds": 301,
+ "cost": null,
+ "started_at": "2026-07-23T17:12:00.434Z",
+ "finished_at": "2026-07-23T17:17:01.036Z",
+ "queued_at": "2026-07-23T17:12:00.417Z"
+ },
+ {
+ "model": "kimi",
+ "status": "done",
+ "error": null,
+ "seconds": 176,
+ "cost": 0.0232,
+ "started_at": "2026-07-23T17:12:00.437Z",
+ "finished_at": "2026-07-23T17:14:56.477Z",
+ "queued_at": "2026-07-23T17:12:00.420Z",
+ "bytes": 9899,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The model fulfills the challenge by including all required elements but lacks some visual polish and detail.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "gpt",
+ "status": "done",
+ "error": null,
+ "seconds": 31,
+ "cost": 0.0442,
+ "started_at": "2026-07-23T17:12:00.439Z",
+ "finished_at": "2026-07-23T17:12:31.082Z",
+ "queued_at": "2026-07-23T17:12:00.423Z",
+ "bytes": 10398,
+ "thumb": true,
+ "aiScore": 7,
+ "aiReason": "The page is functional and meets the basic requirements but lacks visual appeal.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7
+ },
+ "aiSpread": 0
+ },
+ {
+ "model": "grok",
+ "status": "done",
+ "error": null,
+ "seconds": 95,
+ "cost": 0.1293,
+ "started_at": "2026-07-23T17:12:00.440Z",
+ "finished_at": "2026-07-23T17:13:35.050Z",
+ "queued_at": "2026-07-23T17:12:00.426Z",
+ "bytes": 24347,
+ "thumb": true,
+ "aiScore": 7.8,
+ "aiReason": "The model fulfills the challenge by including all required elements and has a clean visual quality.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 2.5
+ }
+ ],
+ "judging": false,
+ "judged_at": "2026-07-23T17:17:39.735Z",
+ "aiPick": "grok",
+ "voted_at": "2026-07-23T17:17:41.520Z"
+ },
+ {
+ "id": "48026228790d",
+ "title": "All-Models — Voronoi Shatter",
+ "prompt": "Build a single-file HTML page: an interactive Voronoi shatter effect. Points scattered on a canvas form Voronoi cells; click adds a point and re-tessellates with a smooth animation; each cell gets a gradient fill. Show/hide seed points toggle. Self-contained, no external libraries.",
+ "category": "Custom",
+ "designTools": false,
+ "created_at": "2026-07-23T17:17:55.835Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 25,
+ "cost": 0,
+ "started_at": "2026-07-23T17:17:55.873Z",
+ "finished_at": "2026-07-23T17:18:20.812Z",
+ "queued_at": "2026-07-23T17:17:55.840Z",
+ "bytes": 3474,
+ "thumb": true
+ },
+ {
+ "model": "gemma3-12b",
+ "status": "done",
+ "error": null,
+ "seconds": 31,
+ "cost": 0,
+ "started_at": "2026-07-23T17:18:20.817Z",
+ "finished_at": "2026-07-23T17:18:52.059Z",
+ "queued_at": "2026-07-23T17:17:55.844Z",
+ "bytes": 3724
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "done",
+ "error": null,
+ "seconds": 29,
+ "cost": 0,
+ "started_at": "2026-07-23T17:18:52.063Z",
+ "finished_at": "2026-07-23T17:19:21.214Z",
+ "queued_at": "2026-07-23T17:17:55.847Z",
+ "bytes": 8846,
+ "thumb": true
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 15,
+ "cost": 0,
+ "started_at": "2026-07-23T17:17:55.877Z",
+ "finished_at": "2026-07-23T17:18:11.068Z",
+ "queued_at": "2026-07-23T17:17:55.854Z",
+ "bytes": 2278,
+ "thumb": true
+ },
+ {
+ "model": "hf-qwen-coder-32b",
+ "status": "running",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": "2026-07-23T17:19:21.219Z",
+ "finished_at": null,
+ "queued_at": "2026-07-23T17:17:55.858Z"
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 92,
+ "cost": 0,
+ "started_at": "2026-07-23T17:17:55.879Z",
+ "finished_at": "2026-07-23T17:19:28.376Z",
+ "queued_at": "2026-07-23T17:17:55.862Z",
+ "bytes": 8756,
+ "thumb": true
+ },
+ {
+ "model": "kimi",
+ "status": "running",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": "2026-07-23T17:17:55.883Z",
+ "finished_at": null,
+ "queued_at": "2026-07-23T17:17:55.865Z"
+ },
+ {
+ "model": "gpt",
+ "status": "done",
+ "error": null,
+ "seconds": 27,
+ "cost": 0.0341,
+ "started_at": "2026-07-23T17:17:55.885Z",
+ "finished_at": "2026-07-23T17:18:22.582Z",
+ "queued_at": "2026-07-23T17:17:55.868Z",
+ "bytes": 9299,
+ "thumb": true
+ },
+ {
+ "model": "grok",
+ "status": "done",
+ "error": null,
+ "seconds": 69,
+ "cost": 0.0841,
+ "started_at": "2026-07-23T17:17:55.887Z",
+ "finished_at": "2026-07-23T17:19:05.291Z",
+ "queued_at": "2026-07-23T17:17:55.871Z",
+ "bytes": 15551,
+ "thumb": true
}
]
}
diff --git a/data/costlog.jsonl b/data/costlog.jsonl
index 09bafc6..de73a4e 100644
--- a/data/costlog.jsonl
+++ b/data/costlog.jsonl
@@ -79,3 +79,23 @@
{"ts":"2026-07-23T16:47:30.549Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":136,"output_tokens":3089,"cost_usd":0.043484}
{"ts":"2026-07-23T16:47:44.287Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":342,"output_tokens":2918,"cost_usd":0.044796}
{"ts":"2026-07-23T16:48:52.862Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":137,"output_tokens":5366,"cost_usd":0.013497}
+{"ts":"2026-07-23T16:50:17.575Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":138,"output_tokens":4344,"cost_usd":0.061058}
+{"ts":"2026-07-23T16:50:34.252Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":346,"output_tokens":4854,"cost_usd":0.073848}
+{"ts":"2026-07-23T16:51:20.507Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":140,"output_tokens":6217,"cost_usd":0.015627}
+{"ts":"2026-07-23T16:55:06.247Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":136,"output_tokens":2938,"cost_usd":0.04137}
+{"ts":"2026-07-23T16:55:17.968Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":344,"output_tokens":3611,"cost_usd":0.055197}
+{"ts":"2026-07-23T16:56:10.166Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":137,"output_tokens":4905,"cost_usd":0.012345}
+{"ts":"2026-07-23T16:57:51.855Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":134,"output_tokens":1579,"cost_usd":0.02234}
+{"ts":"2026-07-23T16:58:11.866Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":341,"output_tokens":2743,"cost_usd":0.042168}
+{"ts":"2026-07-23T16:59:36.933Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":135,"output_tokens":7024,"cost_usd":0.017641}
+{"ts":"2026-07-23T17:04:47.217Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":140,"output_tokens":4176,"cost_usd":0.058709}
+{"ts":"2026-07-23T17:05:02.697Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":347,"output_tokens":5931,"cost_usd":0.090006}
+{"ts":"2026-07-23T17:06:25.398Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":140,"output_tokens":8869,"cost_usd":0.022256}
+{"ts":"2026-07-23T17:09:23.708Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":137,"output_tokens":1531,"cost_usd":0.021674}
+{"ts":"2026-07-23T17:09:24.738Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":344,"output_tokens":1193,"cost_usd":0.018927}
+{"ts":"2026-07-23T17:10:16.508Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":138,"output_tokens":3802,"cost_usd":0.009588}
+{"ts":"2026-07-23T17:12:31.078Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":139,"output_tokens":3141,"cost_usd":0.044217}
+{"ts":"2026-07-23T17:13:35.045Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":349,"output_tokens":8553,"cost_usd":0.129342}
+{"ts":"2026-07-23T17:14:56.473Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":142,"output_tokens":9228,"cost_usd":0.023155}
+{"ts":"2026-07-23T17:18:22.577Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":144,"output_tokens":2417,"cost_usd":0.03409}
+{"ts":"2026-07-23T17:19:05.290Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":347,"output_tokens":5540,"cost_usd":0.084141}
diff --git a/data/votes.jsonl b/data/votes.jsonl
index 5ecd22e..2d1573d 100644
--- a/data/votes.jsonl
+++ b/data/votes.jsonl
@@ -11,3 +11,14 @@
{"ts":"2026-07-23T14:37:06.356Z","challenge":"6fb7d108c932","title":"Warp Starfield Tunnel","winner":"gpt","field":["qwen25-7b","claude-code","gpt","grok"]}
{"ts":"2026-07-23T14:43:22.334Z","challenge":"6fb7d108c932","title":"Warp Starfield Tunnel","winner":"claude-code","field":["gemma3-12b","hermes3-8b","qwen25-7b","claude-code","kimi","gpt","grok"]}
{"ts":"2026-07-23T14:50:26.645Z","challenge":"10c3c876aa67","title":"Living Fractal Tree","winner":"grok","field":["gemma3-12b","hermes3-8b","qwen25-7b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T16:54:27.140Z","challenge":"1fd68f0d37b7","title":"All-Models — Audio 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-23T16:54:27.146Z","challenge":"c2d8bc75e725","title":"All-Models — Typing Speed Test","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T16:54:27.151Z","challenge":"2ca024f41f3c","title":"All-Models — Solar System Orrery","winner":"hf-qwen-coder-32b","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T16:54:27.154Z","challenge":"f1756a933e20","title":"All-Models — Retro CRT Terminal","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T16:54:27.158Z","challenge":"edbe5de71e26","title":"All-Models — Fluid Ink Sim","winner":"qwen3-14b","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T16:54:27.161Z","challenge":"6acc7c672157","title":"All-Models Showdown — Particle Constellation","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T17:08:46.429Z","challenge":"6c13ff8455cc","title":"All-Models — Pathfinding 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-23T17:08:46.434Z","challenge":"f7fa0a2c495b","title":"All-Models — Kaleidoscope Draw","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T17:08:46.439Z","challenge":"0e969e848172","title":"All-Models — Gravity Sandbox","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T17:11:39.378Z","challenge":"e3c185541c0d","title":"All-Models — Matrix Rain","winner":"claude-code","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code","kimi","gpt","grok"]}
+{"ts":"2026-07-23T17:17:41.520Z","challenge":"905cc3052af2","title":"All-Models — Physics Domino Run","winner":"grok","field":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","kimi","gpt","grok"]}
← 9e98c97 auto-save: 2026-07-23T09:50:03 (16 files) — data/challenges.
·
back to Model Arena
·
auto-save: 2026-07-23T10:50:25 (14 files) — data/challenges. 5b59fd4 →